//! Default CDF tables (§9.4) and the §8.3.1 / §8.3.2 CDF-selection
//! process for a bounded **intra-frame mode / partition** syntax group.
//!
//! The §8.2 [`crate::symbol_decoder::SymbolDecoder`] decodes a symbol
//! against a *caller-supplied* CDF array. The bytes of that array — and
//! the rule that maps a syntax-element name to the right array slot — are
//! the subject of this module:
//!
//! * **§9.4 default tables.** The spec lists, in the "Additional
//! tables" annex, the initial cumulative-distribution values copied
//! into each `Tile*Cdf` array at the start of tile parsing
//! (§8.3.1). Each array is stored with a trailing entry that
//! [`crate::symbol_decoder::SymbolDecoder::read_symbol`] uses as the
//! §8.3 adaptation counter (it starts at `0`), so a row of length
//! `N + 1` codes a symbol with `N` possible values, with
//! `row[N-1] == 1 << 15` and `row[N]` the counter.
//!
//! * **§8.3.1 init-from-defaults.** At tile start every `Tile*Cdf`
//! array "is set equal to a copy of" its `Default_*_Cdf` table.
//! [`TileCdfContext::new_from_defaults`] performs exactly that copy,
//! producing a per-tile, mutable working set the symbol decoder
//! adapts in place.
//!
//! * **§8.3.2 selection.** Given a syntax element and the surrounding
//! block context, §8.3.2 derives which row of which `Tile*Cdf` array
//! is the `cdf` passed to `read_symbol`. This module implements the
//! selection for the subset it carries: `intra_frame_y_mode`,
//! `partition`, `skip`, and `segment_id`.
//!
//! ## Scope (bounded subset)
//!
//! Two §9.4 groups currently land here:
//!
//! * **Intra-frame mode / partition** (round 16):
//! * `Default_Intra_Frame_Y_Mode_Cdf` (`intra_frame_y_mode`)
//! * `Default_Partition_W8/W16/W32/W64/W128_Cdf` (`partition`)
//! * `Default_Skip_Cdf` (`skip`)
//! * `Default_Segment_Id_Cdf` (`segment_id`)
//!
//! * **Motion-vector component** (round 17):
//! * `Default_Mv_Joint_Cdf` (`mv_joint`)
//! * `Default_Mv_Sign_Cdf` (`mv_sign`)
//! * `Default_Mv_Class_Cdf` (`mv_class`)
//! * `Default_Mv_Class0_Bit_Cdf` (`mv_class0_bit`)
//! * `Default_Mv_Class0_Fr_Cdf` (`mv_class0_fr`)
//! * `Default_Mv_Class0_Hp_Cdf` (`mv_class0_hp`)
//! * `Default_Mv_Bit_Cdf` (`mv_bit`)
//! * `Default_Mv_Fr_Cdf` (`mv_fr`)
//! * `Default_Mv_Hp_Cdf` (`mv_hp`)
//!
//! * **Inter-mode / reference-frame** (round 18): `new_mv`,
//! `zero_mv`, `ref_mv`, `drl_mode`, `is_inter`, `comp_mode`,
//! `skip_mode`, `comp_ref`, `comp_bwdref`, `single_ref`,
//! `compound_mode`, `comp_ref_type`, `uni_comp_ref`.
//!
//! * **Palette / filter-intra / CFL** (round 19):
//! * `Default_Filter_Intra_Mode_Cdf` (`filter_intra_mode`)
//! * `Default_Filter_Intra_Cdf` (`use_filter_intra`)
//! * `Default_Palette_Y_Mode_Cdf` (`has_palette_y`)
//! * `Default_Palette_Uv_Mode_Cdf` (`has_palette_uv`)
//! * `Default_Palette_Y_Size_Cdf` (`palette_size_y_minus_2`)
//! * `Default_Palette_Uv_Size_Cdf` (`palette_size_uv_minus_2`)
//! * `Default_Palette_Size_{2..8}_Y_Color_Cdf` (`palette_color_idx_y`)
//! * `Default_Palette_Size_{2..8}_Uv_Color_Cdf` (`palette_color_idx_uv`)
//! * `Default_Cfl_Sign_Cdf` (`cfl_alpha_signs`)
//! * `Default_Cfl_Alpha_Cdf` (`cfl_alpha_u` / `cfl_alpha_v`)
//!
//! * **Transform-size** (round 20):
//! * `Default_Tx_8x8_Cdf` (`tx_depth`, `maxTxDepth == 1`)
//! * `Default_Tx_16x16_Cdf` (`tx_depth`, `maxTxDepth == 2`)
//! * `Default_Tx_32x32_Cdf` (`tx_depth`, `maxTxDepth == 3`)
//! * `Default_Tx_64x64_Cdf` (`tx_depth`, `maxTxDepth == 4`)
//! * `Default_Txfm_Split_Cdf` (`txfm_split`)
//!
//! * **Inter-frame transform-type** (round 21):
//! * `Default_Inter_Tx_Type_Set1_Cdf` (`inter_tx_type`,
//! `TX_SET_INTER_1`, 4x4 / 8x8 inter blocks)
//! * `Default_Inter_Tx_Type_Set2_Cdf` (`inter_tx_type`,
//! `TX_SET_INTER_2`, 16x16 inter blocks)
//! * `Default_Inter_Tx_Type_Set3_Cdf` (`inter_tx_type`,
//! `TX_SET_INTER_3`, 4x4..32x32 reduced-set inter blocks)
//!
//! * **Motion-mode** (round 23):
//! * `Default_Motion_Mode_Cdf` (`motion_mode`, keyed by
//! `MiSize` — a straight `BLOCK_SIZES` index, no
//! neighbour-context arithmetic).
//!
//! * **Compound prediction** (round 24):
//! * `Default_Comp_Group_Idx_Cdf` (`comp_group_idx`, binary,
//! keyed by a precomputed `ctx ∈ 0..COMP_GROUP_IDX_CONTEXTS`).
//! * `Default_Compound_Idx_Cdf` (`compound_idx`, binary, keyed
//! by a precomputed `ctx ∈ 0..COMPOUND_IDX_CONTEXTS`).
//! * `Default_Compound_Type_Cdf` (`compound_type`, keyed by
//! `MiSize` — a straight `BLOCK_SIZES` index, no
//! neighbour-context arithmetic).
//!
//! * **Inter-frame intra mode** (round 134):
//! * `Default_Y_Mode_Cdf` (`y_mode`, keyed by
//! `ctx = Size_Group[ MiSize ] ∈ 0..BLOCK_SIZE_GROUPS`).
//! * `Default_Uv_Mode_Cfl_Not_Allowed_Cdf` (`uv_mode` when
//! chroma-from-luma is not allowed, keyed by `YMode`).
//! * `Default_Uv_Mode_Cfl_Allowed_Cdf` (`uv_mode` when
//! chroma-from-luma is allowed, keyed by `YMode`).
//!
//! * **Angle-delta** (round 135):
//! * `Default_Angle_Delta_Cdf` (`angle_delta_y` / `angle_delta_uv`,
//! keyed by the directional intra mode rebased onto
//! `0..DIRECTIONAL_MODES` via `YMode - V_PRED` /
//! `UVMode - V_PRED`).
//!
//! * **Coefficient-token entry sub-group** (round 136) — the
//! `init_coeff_cdfs` gateway to tile-content decode:
//! * `Default_Txb_Skip_Cdf` (`all_zero`, the transform-block skip
//! flag).
//! * `Default_Eob_Pt_{16,32,64,128,256,512,1024}_Cdf` (the
//! end-of-block position class `eob_pt_*`).
//! * `Default_Eob_Extra_Cdf` (the binary `eob_extra` flag).
//! * `Default_Dc_Sign_Cdf` (the binary `dc_sign`).
//!
//! Unlike the non-coeff CDFs the coefficient-entry arrays are reset by
//! the separate [`TileCdfContext::init_coeff_cdfs`] (`base_q_idx` →
//! `idx` via [`coeff_cdf_q_ctx`]); the working copy drops the
//! `COEFF_CDF_Q_CTXS` axis, selecting `Default_*_Cdf[ idx ]`.
//!
//! * **Intra-frame transform-type** (round 137):
//! * `Default_Intra_Tx_Type_Set1_Cdf` (`intra_tx_type`,
//! `TX_SET_INTRA_1`, 4x4 / 8x8 intra blocks; 7 symbols).
//! * `Default_Intra_Tx_Type_Set2_Cdf` (`intra_tx_type`,
//! `TX_SET_INTRA_2`, 4x4 / 8x8 / 16x16 intra blocks; 5 symbols).
//!
//! * **Coefficient `coeff_base_eob` sub-group** (round 138) — the
//! first of the `init_coeff_cdfs` `coeff_base` / `coeff_base_eob` /
//! `coeff_br` braid:
//! * `Default_Coeff_Base_Eob_Cdf` (`coeff_base_eob`, the base
//! level of the last non-zero coefficient).
//!
//! * **Coefficient `coeff_base` sub-group** (round 139) — the second
//! of the `init_coeff_cdfs` `coeff_base` / `coeff_base_eob` /
//! `coeff_br` braid:
//! * `Default_Coeff_Base_Cdf` (`coeff_base`, the base level of
//! each non-EOB coefficient; codes the 4-symbol alphabet
//! `0..3`).
//!
//! * **Coefficient `coeff_br` sub-group** (round 140) — the LAST of
//! the `init_coeff_cdfs` `coeff_base` / `coeff_base_eob` /
//! `coeff_br` braid:
//! * `Default_Coeff_Br_Cdf` (`coeff_br`, the per-coefficient
//! base-range increment used to push a level above
//! `NUM_BASE_LEVELS`; codes the `BR_CDF_SIZE`-symbol alphabet
//! `0..BR_CDF_SIZE`, stacked up to
//! `COEFF_BASE_RANGE / (BR_CDF_SIZE - 1)` times per
//! coefficient per §5.11.39).
//!
//! With this table all three coefficient-CDF braid members are landed;
//! the next gate is the §8.3.2 `get_coeff_base_ctx()` / `get_br_ctx()`
//! neighbour-derivation helpers (deferred — they need tile-content
//! walker state). The remaining `Default_*_Cdf` arrays of §9.4 (the
//! inter-intra group), and the §8.3.2 `split_or_horz` /
//! `split_or_vert` / … selections are a clear followup: each is a
//! mechanical transcription of one §9.4 table plus its §8.3.2
//! paragraph, slotted into the same [`TileCdfContext`] shape used here.
//!
//! All values are transcribed directly from `docs/video/av1/av1-spec`
//! §8.3 and §9.4 — no external source consulted.
// ---------------------------------------------------------------------
// §3 / §9.3 symbol constants used to dimension the tables below.
// ---------------------------------------------------------------------
/// `INTRA_MODES` (§9.3) — number of values for `y_mode` (and the first /
/// second dimension index range of intra-mode CDFs).
pub const INTRA_MODES: usize = 13;
/// `INTRA_MODE_CONTEXTS` (§9.3) — number of each of the left and above
/// contexts for `intra_frame_y_mode`.
pub const INTRA_MODE_CONTEXTS: usize = 5;
/// `PARTITION_CONTEXTS` (§9.3) — number of contexts when decoding
/// `partition`.
pub const PARTITION_CONTEXTS: usize = 4;
/// `EXT_PARTITION_TYPES` (§3 / §6.10.4) — number of partition kinds the
/// `partition` syntax element can code at `bsl >= 2`
/// (`BLOCK_8X8`..`BLOCK_64X64`). The W8 context uses only the first 4
/// values; the W128 context omits the trailing `PARTITION_*_4` pair.
pub const EXT_PARTITION_TYPES: usize = 10;
/// `PARTITION_NONE` (§6.10.4) — partition ordinal `0`; the block is left
/// unsplit.
pub const PARTITION_NONE: usize = 0;
/// `PARTITION_HORZ` (§6.10.4) — partition ordinal `1`; the block splits
/// into a top/bottom pair.
pub const PARTITION_HORZ: usize = 1;
/// `PARTITION_VERT` (§6.10.4) — partition ordinal `2`; the block splits
/// into a left/right pair.
pub const PARTITION_VERT: usize = 2;
/// `PARTITION_SPLIT` (§6.10.4) — partition ordinal `3`; the block
/// recursively splits into four quadrants.
pub const PARTITION_SPLIT: usize = 3;
/// `PARTITION_HORZ_A` (§6.10.4) — partition ordinal `4`; the top half of
/// a `PARTITION_HORZ` split is itself further split vertically (top-left
/// + top-right + bottom).
pub const PARTITION_HORZ_A: usize = 4;
/// `PARTITION_HORZ_B` (§6.10.4) — partition ordinal `5`; the bottom half
/// of a `PARTITION_HORZ` split is itself further split vertically (top +
/// bottom-left + bottom-right).
pub const PARTITION_HORZ_B: usize = 5;
/// `PARTITION_VERT_A` (§6.10.4) — partition ordinal `6`; the left half
/// of a `PARTITION_VERT` split is itself further split horizontally
/// (top-left + bottom-left + right).
pub const PARTITION_VERT_A: usize = 6;
/// `PARTITION_VERT_B` (§6.10.4) — partition ordinal `7`; the right half
/// of a `PARTITION_VERT` split is itself further split horizontally
/// (left + top-right + bottom-right).
pub const PARTITION_VERT_B: usize = 7;
/// `PARTITION_HORZ_4` (§6.10.4) — partition ordinal `8`; the block
/// splits into four horizontal stripes. Disallowed for `BLOCK_128X128`.
pub const PARTITION_HORZ_4: usize = 8;
/// `PARTITION_VERT_4` (§6.10.4) — partition ordinal `9`; the block
/// splits into four vertical stripes. Disallowed for `BLOCK_128X128`.
pub const PARTITION_VERT_4: usize = 9;
/// `BLOCK_128X128` (§6.10.4) — block-size ordinal `15`; the largest
/// superblock. The §8.3.2 `split_or_horz` / `split_or_vert` cdf
/// derivations drop the `PARTITION_*_4` term when `bSize` equals this
/// value (the W128 `partition` cdf has no entry at indices 8/9).
pub const BLOCK_128X128: usize = 15;
/// `SKIP_CONTEXTS` (§9.3) — number of contexts for decoding `skip`.
pub const SKIP_CONTEXTS: usize = 3;
/// `SEGMENT_ID_CONTEXTS` (§9.3) — number of contexts for `segment_id`.
pub const SEGMENT_ID_CONTEXTS: usize = 3;
/// `SEGMENT_ID_PREDICTED_CONTEXTS` (§9.3 — av1-spec p.220) — number of
/// contexts for `seg_id_predicted`. The §8.3.2 selection is
/// `ctx = LeftSegPredContext[ MiRow ] + AboveSegPredContext[ MiCol ]`,
/// each neighbour contributing `0` or `1`, so the sum is in `0..=2 <
/// SEGMENT_ID_PREDICTED_CONTEXTS`.
pub const SEGMENT_ID_PREDICTED_CONTEXTS: usize = 3;
/// `MAX_SEGMENTS` (§9.3) — number of segments allowed in the
/// segmentation map (number of `segment_id` symbol values).
pub const MAX_SEGMENTS: usize = 8;
/// `Intra_Mode_Context[ INTRA_MODES ]` (§8.3.2) — maps a neighbouring
/// block's `YMode` to the above/left context index used to select the
/// `intra_frame_y_mode` CDF.
pub const INTRA_MODE_CONTEXT: [usize; INTRA_MODES] = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0];
// ---------------------------------------------------------------------
// §3 motion-vector constants (round 17).
// ---------------------------------------------------------------------
/// `MV_CONTEXTS` (§3) — number of contexts for decoding motion vectors.
/// The §5.11.31 `read_mv()` derivation sets `MvCtx = MV_INTRABC_CONTEXT`
/// for intra-block-copy use and `MvCtx = 0` otherwise, so an MvCtx value
/// addresses one of `0..MV_CONTEXTS` (with `MV_INTRABC_CONTEXT = 1`
/// hitting the second slot).
pub const MV_CONTEXTS: usize = 2;
/// `MV_INTRABC_CONTEXT` (§3) — motion-vector context used by §5.11.31
/// `read_mv()` when `use_intrabc == 1`.
pub const MV_INTRABC_CONTEXT: usize = 1;
/// `MV_JOINTS` (§3) — number of values for `mv_joint`
/// (`MV_JOINT_ZERO`, `MV_JOINT_HNZVZ`, `MV_JOINT_HZVNZ`, `MV_JOINT_HNZVNZ`).
pub const MV_JOINTS: usize = 4;
/// `MV_CLASSES` (§3) — number of values for `mv_class`.
pub const MV_CLASSES: usize = 11;
/// `CLASS0_SIZE` (§3) — number of values for `mv_class0_bit`. Also the
/// inner dimension of `Default_Mv_Class0_Fr_Cdf`.
pub const CLASS0_SIZE: usize = 2;
/// `MV_OFFSET_BITS` (§3) — maximum number of `mv_bit` slots read by
/// `read_mv_component()` (one per `i = 0..mv_class-1`).
pub const MV_OFFSET_BITS: usize = 10;
/// Number of distinct mv components per call: the §5.11.31 motion vector
/// has a horizontal and vertical component (`comp = 0..1`).
pub const MV_COMPS: usize = 2;
/// `MV_JOINT_ZERO` (§6.10.27, av1-spec p.186 — "MV Joint" table) — the
/// §5.11.31 `mv_joint` ordinal for "both components are zero" (no
/// `read_mv_component()` calls fire).
pub const MV_JOINT_ZERO: u8 = 0;
/// `MV_JOINT_HNZVZ` (§6.10.27) — `mv_joint` ordinal for "horizontal
/// (row, `comp = 0`) is non-zero, vertical (col, `comp = 1`) is zero".
/// The §5.11.31 body invokes `read_mv_component(0)` only.
///
/// Note: §6.10.27's "horizontal / vertical" labels match the spec's
/// row-major component ordering: `comp = 0` is the row (vertical-axis
/// pixel) component, `comp = 1` is the column (horizontal-axis pixel)
/// component. The label "H non-zero" therefore corresponds to
/// `diffMv[0] != 0` and "V non-zero" to `diffMv[1] != 0`.
pub const MV_JOINT_HNZVZ: u8 = 1;
/// `MV_JOINT_HZVNZ` (§6.10.27) — `mv_joint` ordinal for "horizontal
/// (`comp = 0`) is zero, vertical (`comp = 1`) is non-zero". The
/// §5.11.31 body invokes `read_mv_component(1)` only.
pub const MV_JOINT_HZVNZ: u8 = 2;
/// `MV_JOINT_HNZVNZ` (§6.10.27) — `mv_joint` ordinal for "both
/// components are non-zero". The §5.11.31 body invokes both
/// `read_mv_component(0)` and `read_mv_component(1)`.
pub const MV_JOINT_HNZVNZ: u8 = 3;
/// `MV_CLASS_0` (§6.10.28, av1-spec p.187 — "MV Class" table) — the
/// §5.11.32 `mv_class` ordinal for the small-magnitude class. Selects
/// the `mv_class0_bit` / `mv_class0_fr` / `mv_class0_hp` ladder
/// (instead of the `mv_bit` loop). Every other `MV_CLASS_K` for `K =
/// 1..=10` is just `K` itself; the spec only names class 0 explicitly
/// in the syntax body.
pub const MV_CLASS_0: u8 = 0;
// ---------------------------------------------------------------------
// §3 inter-mode / reference-frame constants (round 18).
// ---------------------------------------------------------------------
/// `NEW_MV_CONTEXTS` (§3) — number of contexts for `new_mv`.
pub const NEW_MV_CONTEXTS: usize = 6;
/// `ZERO_MV_CONTEXTS` (§3) — number of contexts for `zero_mv`.
pub const ZERO_MV_CONTEXTS: usize = 2;
/// `REF_MV_CONTEXTS` (§3) — number of contexts for `ref_mv`.
pub const REF_MV_CONTEXTS: usize = 6;
/// `DRL_MODE_CONTEXTS` (§3) — number of contexts for `drl_mode`.
pub const DRL_MODE_CONTEXTS: usize = 3;
/// `IS_INTER_CONTEXTS` (§3) — number of contexts for `is_inter`.
pub const IS_INTER_CONTEXTS: usize = 4;
/// `COMP_INTER_CONTEXTS` (§3) — number of contexts for `comp_mode`.
pub const COMP_INTER_CONTEXTS: usize = 5;
/// `SKIP_MODE_CONTEXTS` (§3) — number of contexts for `skip_mode`.
pub const SKIP_MODE_CONTEXTS: usize = 3;
/// `DELTA_Q_SMALL` (§3, av1-spec p.16) — the escape value for the
/// §5.11.12 `delta_q_abs` symbol. When the decoded `delta_q_abs`
/// equals this constant, the absolute delta is encoded via the
/// `delta_q_rem_bits` (`L(3)`) + `delta_q_abs_bits` (`L(n)`) ladder
/// instead of being the literal value. The §8.3.2 `TileDeltaQCdf`
/// row therefore covers `DELTA_Q_SMALL + 1` literal values
/// (`0..=DELTA_Q_SMALL`) plus the §8.2.6 counter slot — i.e. it has
/// length `DELTA_Q_SMALL + 2 = 5`.
pub const DELTA_Q_SMALL: usize = 3;
/// `DELTA_LF_SMALL` (§3, av1-spec p.16) — the escape value for the
/// §5.11.13 `delta_lf_abs` symbol. Mirrors the role of
/// [`DELTA_Q_SMALL`] for the loop-filter delta read: when the decoded
/// `delta_lf_abs` equals this constant the absolute delta is encoded
/// via the `delta_lf_rem_bits` (`L(3)`) + `delta_lf_abs_bits` (`L(n)`)
/// ladder. The §8.3.2 `TileDeltaLFCdf` / `TileDeltaLFMultiCdf[ i ]`
/// rows therefore cover `DELTA_LF_SMALL + 1` literal values
/// (`0..=DELTA_LF_SMALL`) plus the §8.2.6 counter slot — i.e. length
/// `DELTA_LF_SMALL + 2 = 5`.
pub const DELTA_LF_SMALL: usize = 3;
/// `FRAME_LF_COUNT` (§3, av1-spec p.16) — number of loop filter
/// strength values per frame. Used as the upper bound on the §5.11.13
/// `for ( i = 0; i < frameLfCount; i++ )` loop and the size of the
/// `DeltaLFMultiCdf[ FRAME_LF_COUNT ]` array and the §5.11.2
/// `DeltaLF[ FRAME_LF_COUNT ]` accumulator. When `delta_lf_multi == 1`
/// and `NumPlanes > 1`, `frameLfCount = FRAME_LF_COUNT = 4` (the four
/// edge categories: horizontal-Y, vertical-Y, U, V); when
/// `NumPlanes == 1` it is `FRAME_LF_COUNT - 2 = 2`; when
/// `delta_lf_multi == 0`, `frameLfCount = 1`.
pub const FRAME_LF_COUNT: usize = 4;
/// `MAX_LOOP_FILTER` (§3, av1-spec p.16) — the §5.11.13 `Clip3(
/// -MAX_LOOP_FILTER, MAX_LOOP_FILTER, ...)` bound on the running
/// `DeltaLF[ i ]` accumulator. The §6.10.2 `Clip3(0, MAX_LOOP_FILTER,
/// deltaLF + loop_filter_level[ i ])` derivation downstream uses the
/// same constant; the loop-filter-level-parser module
/// (`uncompressed_header_tail::MAX_LOOP_FILTER`) holds an `i16`-typed
/// twin for the §5.9.11 ref-/mode-delta clip bounds. Held as `i32`
/// here because the pre-clip §5.11.13 sum
/// `DeltaLF[ i ] + (reducedDeltaLfLevel << delta_lf_res)` is signed.
pub const MAX_LOOP_FILTER: i32 = 63;
/// `REF_CONTEXTS` (§3) — number of contexts for `single_ref`, `comp_ref`,
/// `comp_bwdref`, and `uni_comp_ref`.
pub const REF_CONTEXTS: usize = 3;
/// `FWD_REFS` (§3) — number of forward reference syntax elements (the
/// `Default_Comp_Ref_Cdf` second dimension is `FWD_REFS - 1`).
pub const FWD_REFS: usize = 4;
/// `BWD_REFS` (§3) — number of backward reference syntax elements (the
/// `Default_Comp_Bwd_Ref_Cdf` second dimension is `BWD_REFS - 1`).
pub const BWD_REFS: usize = 3;
/// `SINGLE_REFS` (§3) — number of single-reference syntax elements (the
/// `Default_Single_Ref_Cdf` second dimension is `SINGLE_REFS - 1`).
pub const SINGLE_REFS: usize = 7;
/// `UNIDIR_COMP_REFS` (§3) — number of unidirectional-compound reference
/// syntax elements (the `Default_Uni_Comp_Ref_Cdf` second dimension is
/// `UNIDIR_COMP_REFS - 1`).
pub const UNIDIR_COMP_REFS: usize = 4;
/// `COMP_REF_TYPE_CONTEXTS` (§3) — number of contexts for `comp_ref_type`.
pub const COMP_REF_TYPE_CONTEXTS: usize = 5;
/// `COMPOUND_MODES` (§3) — number of values for `compound_mode`.
pub const COMPOUND_MODES: usize = 8;
/// `COMPOUND_MODE_CONTEXTS` (§3) — number of contexts for `compound_mode`.
pub const COMPOUND_MODE_CONTEXTS: usize = 8;
/// `COMP_NEWMV_CTXS` (§3) — number of new-mv values used when
/// constructing the compound-mode context (the second axis of
/// `Compound_Mode_Ctx_Map`).
pub const COMP_NEWMV_CTXS: usize = 5;
/// `Compound_Mode_Ctx_Map[ 3 ][ COMP_NEWMV_CTXS ]` (§8.3.2) — maps the
/// `(RefMvContext >> 1, Min(NewMvContext, COMP_NEWMV_CTXS - 1))` pair to
/// the `compound_mode` context index used to select the
/// `TileCompoundModeCdf` row.
pub const COMPOUND_MODE_CTX_MAP: [[usize; COMP_NEWMV_CTXS]; 3] =
[[0, 1, 1, 1, 1], [1, 2, 3, 4, 4], [4, 4, 5, 6, 7]];
// ---------------------------------------------------------------------
// §3 motion-vector / MV-stack constants (av1-spec p.16 — round 172,
// §7.10 find_mv_stack process).
// ---------------------------------------------------------------------
/// `MAX_REF_MV_STACK_SIZE` (§3, av1-spec p.16) — maximum number of motion
/// vectors retained in the §7.10.2 `RefStackMv[idx][list][comp]` stack
/// across the search-stack / compound-search-stack / extra-search /
/// sorting passes. Once the stack reaches this size, further candidates
/// only increment the weight of an existing match — they cannot append
/// a new slot.
pub const MAX_REF_MV_STACK_SIZE: usize = 8;
/// `REF_CAT_LEVEL` (§3, av1-spec p.16) — bonus weight added to
/// `WeightStack[idx]` for `idx = 0..numNearest-1` in §7.10.2 step 15. The
/// large numeric value (`640` ≫ any `weight = len * 2 <= 32`) ensures
/// every "close-match" candidate (one of `foundAboveMatch /
/// foundLeftMatch`) sorts above every non-close-match candidate after
/// the §7.10.2 step 33-34 sorting passes — the `w0 >= REF_CAT_LEVEL` /
/// `w1 < REF_CAT_LEVEL` partition in §7.10.2.14 `DrlCtxStack[idx]` and
/// the §7.10.2.11 stable-sort ordering both depend on this bonus.
pub const REF_CAT_LEVEL: u32 = 640;
/// `MV_BORDER` (§3, av1-spec p.16) — clip border used by §5.11.53
/// `clamp_mv_row(mvec, border)` / §5.11.54 `clamp_mv_col(mvec, border)`
/// inside the §7.10.2.14 stack-clamp loop. The `border` argument is
/// `MV_BORDER + bh * 8` / `MV_BORDER + bw * 8` (block dimension in 1/8
/// luma samples) — the extra 128 1/8-sample padding allows motion
/// vectors to reach slightly outside the frame edge before the §7.10
/// clamp.
pub const MV_BORDER: i32 = 128;
/// `MFMV_STACK_SIZE` per §3 (av1-spec p.16) — the number of reference
/// frames whose motion fields are projected by §7.9
/// `motion_field_estimation`. Used as the §7.9.1 `refStamp` cap (the
/// stamp counter starts at `MFMV_STACK_SIZE - 2` and decreases per
/// successful projection until the cap is exhausted).
pub const MFMV_STACK_SIZE: i32 = 3;
/// `MAX_OFFSET_WIDTH` per §3 (av1-spec p.16) — the §7.9.4
/// `get_block_position` cap on a projected motion vector's horizontal
/// displacement from the source 8×8 location. Values beyond the cap
/// clear `posValid`, dropping the projection.
pub const MAX_OFFSET_WIDTH: i32 = 8;
/// `MAX_OFFSET_HEIGHT` per §3 (av1-spec p.16) — vertical mirror of
/// [`MAX_OFFSET_WIDTH`].
pub const MAX_OFFSET_HEIGHT: i32 = 0;
/// `MV_IN_USE_BITS = 14` per §3 — the half-range bound for projected
/// motion vectors. §7.9.3 clamps each component into
/// `-(1 << 14) + 1 ..= (1 << 14) - 1` after the §4.7.2
/// `Round2Signed(mv * num * Div_Mult[denom], 14)` scale.
pub const MV_IN_USE_BITS: u32 = 14;
/// `Div_Mult[ 1..=31 ]` per §7.9.3 (av1-spec p.215) — the fixed
/// multiplicative-inverse lookup that lets the projection process
/// scale a saved MV by `clippedNumerator / clippedDenominator` using
/// only a multiply + arithmetic right shift. Slot 0 is the
/// `denominator == 0` divide-by-zero guard and is set to 0; the
/// §7.9.2 caller never invokes the projection for that case.
///
/// The entries are quantised inverses: `Div_Mult[d] = round(2^14 / d)`
/// for `d = 1..=31`.
pub const DIV_MULT: [i32; 32] = [
0, 16384, 8192, 5461, 4096, 3276, 2730, 2340, 2048, 1820, 1638, 1489, 1365, 1260, 1170, 1092,
1024, 963, 910, 862, 819, 780, 744, 712, 682, 655, 630, 606, 585, 564, 546, 528,
];
/// Sentinel value placed in `MotionFieldMvs[ref][y8][x8][0]` to mark a
/// motion-field slot as "no projection landed here". Per §7.9.1
/// initialisation (av1-spec p.212): `MotionFieldMvs[ ref ][ y ][ x ][ j
/// ] = -1 << 15` for every cell before any §7.9.2 projection runs. The
/// §7.10.2.6 temporal-sample body short-circuits when
/// `candMv[ 0 ] == -1 << 15`, so the §7.10.2.5 scan is a no-op on a
/// freshly-initialised grid (which is what
/// [`MotionFieldMvs::new_invalid`] returns).
pub const MFMV_INVALID: i16 = -1 << 15;
// ---------------------------------------------------------------------
// §3 / §6.10.22 inter-mode ordinal constants (av1-spec p.180 — round 172,
// the YMode = NEAREST_NEARESTMV + compound_mode offset table).
// Reproduced from the spec text:
// NEARESTMV = 14, NEARMV = 15, GLOBALMV = 16, NEWMV = 17,
// NEAREST_NEARESTMV = 18, NEAR_NEARMV = 19, NEAREST_NEWMV = 20,
// NEW_NEARESTMV = 21, NEAR_NEWMV = 22, NEW_NEARMV = 23,
// GLOBAL_GLOBALMV = 24, NEW_NEWMV = 25.
// `has_newmv(mode)` per spec is `mode == NEWMV || NEW_NEWMV ||
// NEAR_NEWMV || NEW_NEARMV || NEAREST_NEWMV || NEW_NEARESTMV`.
// ---------------------------------------------------------------------
/// `NEARESTMV` (§6.10.22) — single-prediction "use the nearest MV
/// candidate" Y mode.
pub const MODE_NEARESTMV: u8 = 14;
/// `NEARMV` (§6.10.22) — single-prediction "use the second nearest MV
/// candidate" Y mode.
pub const MODE_NEARMV: u8 = 15;
/// `GLOBALMV` (§6.10.22) — single-prediction "use the per-ref global
/// motion vector" Y mode.
pub const MODE_GLOBALMV: u8 = 16;
/// `NEWMV` (§6.10.22) — single-prediction "read an mv_difference" Y
/// mode. `has_newmv(NEWMV)` is true.
pub const MODE_NEWMV: u8 = 17;
/// `NEAREST_NEARESTMV` (§6.10.22) — compound-prediction Y mode with
/// `compound_mode = 0` offset from `NEAREST_NEARESTMV`.
pub const MODE_NEAREST_NEARESTMV: u8 = 18;
/// `NEAR_NEARMV` (§6.10.22) — compound-prediction Y mode with
/// `compound_mode = 1` offset.
pub const MODE_NEAR_NEARMV: u8 = 19;
/// `NEAREST_NEWMV` (§6.10.22) — `has_newmv` true (slot 1 = NEW).
pub const MODE_NEAREST_NEWMV: u8 = 20;
/// `NEW_NEARESTMV` (§6.10.22) — `has_newmv` true (slot 0 = NEW).
pub const MODE_NEW_NEARESTMV: u8 = 21;
/// `NEAR_NEWMV` (§6.10.22) — `has_newmv` true (slot 1 = NEW).
pub const MODE_NEAR_NEWMV: u8 = 22;
/// `NEW_NEARMV` (§6.10.22) — `has_newmv` true (slot 0 = NEW).
pub const MODE_NEW_NEARMV: u8 = 23;
/// `GLOBAL_GLOBALMV` (§6.10.22) — compound-prediction "both refs use
/// the per-ref global motion vector".
pub const MODE_GLOBAL_GLOBALMV: u8 = 24;
/// `NEW_NEWMV` (§6.10.22) — compound-prediction "read both mv
/// differences". `has_newmv(NEW_NEWMV)` is true.
pub const MODE_NEW_NEWMV: u8 = 25;
/// `IDENTITY` (§3, av1-spec p.16) — warp-model type discriminant 0,
/// used by §7.10.2.1 `setup_global_mv` to short-circuit to a zero
/// motion vector.
pub const GM_TYPE_IDENTITY: i32 = 0;
/// `TRANSLATION` (§3) — warp-model type discriminant 1 (pure
/// translation; `gm_params[ref][0..2]` only).
pub const GM_TYPE_TRANSLATION: i32 = 1;
/// `ROTZOOM` (§3) — warp-model type discriminant 2 (rotation +
/// symmetric zoom + translation).
pub const GM_TYPE_ROTZOOM: i32 = 2;
/// `AFFINE` (§3) — warp-model type discriminant 3 (general affine
/// transform).
pub const GM_TYPE_AFFINE: i32 = 3;
/// Local twin of [`crate::uncompressed_header_tail::WARPEDMODEL_PREC_BITS`]
/// for the §7.10.2.1 `setup_global_mv` body (av1-spec p.219). The
/// affine-projection arms shift by `WARPEDMODEL_PREC_BITS - 3` (high
/// precision) or `WARPEDMODEL_PREC_BITS - 2` (low precision) on the
/// final round step.
pub const WARPEDMODEL_PREC_BITS: u32 = 16;
// ---------------------------------------------------------------------
// §3 palette / filter-intra / CFL constants (round 19).
// ---------------------------------------------------------------------
/// `BLOCK_SIZES` (§3) — number of different block sizes. Also the first
/// dimension of `Default_Filter_Intra_Cdf` (indexed by `MiSize`). Per the
/// §9.4 note, first-dimension indices 10..=15 and 20..=21 are never
/// reached by the `use_filter_intra` selection but the table is still
/// transcribed full-width.
pub const BLOCK_SIZES: usize = 22;
/// `INTRA_FILTER_MODES` (§3) — number of values for `filter_intra_mode`
/// (the length of `Default_Filter_Intra_Mode_Cdf` is this + 1).
pub const INTRA_FILTER_MODES: usize = 5;
/// `PALETTE_BLOCK_SIZE_CONTEXTS` (§3) — number of `bsizeCtx` values for
/// palette block size. `bsizeCtx = Mi_Width_Log2[ MiSize ] +
/// Mi_Height_Log2[ MiSize ] - 2` (§5.11.46), in `0..PALETTE_BLOCK_SIZE_CONTEXTS`.
pub const PALETTE_BLOCK_SIZE_CONTEXTS: usize = 7;
/// `PALETTE_Y_MODE_CONTEXTS` (§3) — number of `has_palette_y` contexts.
pub const PALETTE_Y_MODE_CONTEXTS: usize = 3;
/// `PALETTE_UV_MODE_CONTEXTS` (§3) — number of `has_palette_uv` contexts.
pub const PALETTE_UV_MODE_CONTEXTS: usize = 2;
/// `PALETTE_SIZES` (§3) — number of values for `palette_size`
/// (`palette_size_y_minus_2` / `palette_size_uv_minus_2` code this many
/// symbols; the cumulative-array length is this + 1).
pub const PALETTE_SIZES: usize = 7;
/// `PALETTE_COLORS` (§3) — maximum palette size (the largest
/// `palette_color_idx_*` symbol count). The per-size colour-index CDFs
/// run from size 2 to size `PALETTE_COLORS` inclusive.
pub const PALETTE_COLORS: usize = 8;
/// `PALETTE_COLOR_CONTEXTS` (§3) — number of `ctx` values for
/// `palette_color_idx_*` (the colour-index CDFs' first dimension).
pub const PALETTE_COLOR_CONTEXTS: usize = 5;
/// `PALETTE_NUM_NEIGHBORS` (§3) — number of neighbours considered when
/// computing the palette colour-context hash.
pub const PALETTE_NUM_NEIGHBORS: usize = 3;
/// `PALETTE_MAX_COLOR_CONTEXT_HASH` (§3) — number of distinct colour
/// context hash values (the `Palette_Color_Context` map is indexed by
/// `0..=PALETTE_MAX_COLOR_CONTEXT_HASH`).
pub const PALETTE_MAX_COLOR_CONTEXT_HASH: usize = 8;
/// `Palette_Color_Context[ PALETTE_MAX_COLOR_CONTEXT_HASH + 1 ]`
/// (§9.4 additional tables) — maps a `ColorContextHash` to the
/// `palette_color_idx_*` context. The `-1` entries are hashes that the
/// §5.11.50 derivation never produces, so they are never accessed; they
/// are stored as `i8` to carry the spec's sentinel verbatim.
pub const PALETTE_COLOR_CONTEXT: [i8; PALETTE_MAX_COLOR_CONTEXT_HASH + 1] =
[-1, -1, 0, -1, -1, 4, 3, 2, 1];
/// `Palette_Color_Hash_Multipliers[ PALETTE_NUM_NEIGHBORS ]`
/// (§9.4 additional tables) — the per-neighbour weights summed into the
/// `ColorContextHash`.
pub const PALETTE_COLOR_HASH_MULTIPLIERS: [u32; PALETTE_NUM_NEIGHBORS] = [1, 2, 2];
/// `CFL_JOINT_SIGNS` (§3) — number of values for `cfl_alpha_signs` (the
/// `Default_Cfl_Sign_Cdf` cumulative-array length is this + 1).
pub const CFL_JOINT_SIGNS: usize = 8;
/// `CFL_ALPHABET_SIZE` (§3) — number of values for `cfl_alpha_u` /
/// `cfl_alpha_v` (the `Default_Cfl_Alpha_Cdf` inner length is this + 1).
pub const CFL_ALPHABET_SIZE: usize = 16;
/// `CFL_ALPHA_CONTEXTS` (§3) — number of `ctx` values for `cfl_alpha_u` /
/// `cfl_alpha_v` (the `Default_Cfl_Alpha_Cdf` first dimension).
pub const CFL_ALPHA_CONTEXTS: usize = 6;
/// `TX_SIZE_CONTEXTS` (§3) — number of contexts when decoding
/// `tx_depth`. Drives the first dimension of every
/// `Default_Tx_{8,16,32,64}x{8,16,32,64}_Cdf` table (§9.4) and is the
/// number of values returned by the §8.3.2 `tx_depth` context formula.
pub const TX_SIZE_CONTEXTS: usize = 3;
/// `TX_SIZES` (§3) — number of square transform sizes (`TX_4X4`,
/// `TX_8X8`, `TX_16X16`, `TX_32X32`, `TX_64X64`).
pub const TX_SIZES: usize = 5;
/// `MAX_TX_DEPTH` (§3) — maximum number of times the transform can
/// be split. Drives the §9.4 row widths for the per-`maxRectTxSize`
/// `tx_depth` CDFs: `Default_Tx_8x8_Cdf` rows are length
/// `MAX_TX_DEPTH + 1` (one symbol of 2 values, since the spec
/// constraint `tx_depth in 0..=2` collapses to two choices for the
/// `maxTxDepth == 1` block-size group), while every other
/// `Default_Tx_*Cdf` row is `MAX_TX_DEPTH + 2` (three choices) per
/// the §9.4 source.
pub const MAX_TX_DEPTH: usize = 2;
/// `TXFM_PARTITION_CONTEXTS` (§3) — number of contexts when decoding
/// `txfm_split`. Drives the first dimension of
/// `Default_Txfm_Split_Cdf` (§9.4).
pub const TXFM_PARTITION_CONTEXTS: usize = 21;
/// `TX_TYPES` (§3) — total number of inverse transform types in the
/// `Tx_Type_Inter_Inv_Set1` enumeration (the §6.10.19 full set). Drives
/// the symbol-count of [`DEFAULT_INTER_TX_TYPE_SET1_CDF`] (16 cumulative
/// frequencies + one trailing adaptation counter).
pub const TX_TYPES: usize = 16;
/// `TX_TYPES_SET2` — symbol count for §6.10.19 `TX_SET_INTER_2`
/// (`Tx_Type_Inter_Inv_Set2` has 12 entries). Drives the row width of
/// [`DEFAULT_INTER_TX_TYPE_SET2_CDF`].
pub const TX_TYPES_SET2: usize = 12;
/// `TX_TYPES_SET3` — symbol count for §6.10.19 `TX_SET_INTER_3`
/// (`Tx_Type_Inter_Inv_Set3 = { IDTX, DCT_DCT }`, two entries). Drives
/// the row width of [`DEFAULT_INTER_TX_TYPE_SET3_CDF`].
pub const TX_TYPES_SET3: usize = 2;
/// `TX_SET_DCTONLY` (§6.10.19) — the trivial transform-set returned by
/// §5.11.48 `get_tx_set()` when the block is too large or
/// `reduced_tx_set` collapses everything to DCT_DCT. Listed for
/// completeness alongside the inter-tx-type sets; no CDF row is read in
/// this case (§5.11.47 forces `TxType = DCT_DCT`).
pub const TX_SET_DCTONLY: u32 = 0;
/// `TX_SET_INTER_1` (§6.10.19) — the full-inter transform set
/// (`Tx_Type_Inter_Inv_Set1`, 16 symbols), selected when neither
/// `reduced_tx_set` nor `txSzSqrUp == TX_32X32` apply and
/// `txSzSqr != TX_16X16`. Returned by [`inter_tx_type_set`].
pub const TX_SET_INTER_1: u32 = 1;
/// `TX_SET_INTER_2` (§6.10.19) — the 16x16 inter transform set
/// (`Tx_Type_Inter_Inv_Set2`, 12 symbols), selected when
/// `txSzSqr == TX_16X16` and `reduced_tx_set == 0`. Returned by
/// [`inter_tx_type_set`].
pub const TX_SET_INTER_2: u32 = 2;
/// `TX_SET_INTER_3` (§6.10.19) — the reduced inter transform set
/// (`Tx_Type_Inter_Inv_Set3 = { IDTX, DCT_DCT }`), selected when
/// `reduced_tx_set == 1` or `txSzSqrUp == TX_32X32`. Returned by
/// [`inter_tx_type_set`].
pub const TX_SET_INTER_3: u32 = 3;
/// First-axis length of [`DEFAULT_INTER_TX_TYPE_SET1_CDF`] — two entries
/// for `Tx_Size_Sqr[ txSz ] ∈ { TX_4X4, TX_8X8 }`, the only sizes that
/// reach `TX_SET_INTER_1` (per §5.11.48 `get_tx_set()`: the function
/// already returns `TX_SET_INTER_2` for `txSzSqr == TX_16X16` and
/// `TX_SET_INTER_3` for `txSzSqrUp == TX_32X32`).
pub const INTER_TX_TYPE_SET1_SIZES: usize = 2;
/// First-axis length of [`DEFAULT_INTER_TX_TYPE_SET3_CDF`] — four
/// entries for `Tx_Size_Sqr[ txSz ] ∈ { TX_4X4, TX_8X8, TX_16X16,
/// TX_32X32 }`, the reachable sizes for the reduced inter-tx-type set
/// (per §5.11.48: `txSzSqrUp > TX_32X32` already returns
/// `TX_SET_DCTONLY`).
pub const INTER_TX_TYPE_SET3_SIZES: usize = 4;
/// `TX_SET_INTRA_1` (§6.10.19) — the full-intra transform set
/// (`Tx_Type_Intra_Inv_Set1`, 7 symbols including `IDTX`, `DCT_DCT`,
/// `V_DCT`, `H_DCT`, `ADST_ADST`, `ADST_DCT`, `DCT_ADST`). Returned by
/// §5.11.48 `get_tx_set()` on the `is_inter == 0` branch when neither
/// `reduced_tx_set` nor `txSzSqr == TX_16X16` apply.
pub const TX_SET_INTRA_1: u32 = 1;
/// `TX_SET_INTRA_2` (§6.10.19) — the reduced-intra transform set
/// (`Tx_Type_Intra_Inv_Set2`, 5 symbols: `IDTX`, `DCT_DCT`, `ADST_ADST`,
/// `ADST_DCT`, `DCT_ADST`). Returned by §5.11.48 `get_tx_set()` on the
/// `is_inter == 0` branch when `reduced_tx_set == 1` or
/// `txSzSqr == TX_16X16`.
pub const TX_SET_INTRA_2: u32 = 2;
/// Symbol count for §6.10.19 `TX_SET_INTRA_1` (`Tx_Type_Intra_Inv_Set1`
/// has 7 entries). Drives the row width of
/// [`DEFAULT_INTRA_TX_TYPE_SET1_CDF`] (`TX_TYPES_INTRA_SET1 + 1`
/// cumulative frequencies + the §8.3 adaptation counter, matching the
/// `[8]` row width in the spec's §9.4 listing).
pub const TX_TYPES_INTRA_SET1: usize = 7;
/// Symbol count for §6.10.19 `TX_SET_INTRA_2` (`Tx_Type_Intra_Inv_Set2`
/// has 5 entries). Drives the row width of
/// [`DEFAULT_INTRA_TX_TYPE_SET2_CDF`] (`TX_TYPES_INTRA_SET2 + 1`
/// cumulative frequencies + the §8.3 adaptation counter, matching the
/// `[6]` row width in the spec's §9.4 listing).
pub const TX_TYPES_INTRA_SET2: usize = 5;
/// First-axis length of [`DEFAULT_INTRA_TX_TYPE_SET1_CDF`] — two entries
/// for `Tx_Size_Sqr[ txSz ] ∈ { TX_4X4, TX_8X8 }`, the only sizes that
/// reach `TX_SET_INTRA_1` per §5.11.48 (`txSzSqr == TX_16X16` is routed
/// to `TX_SET_INTRA_2`, `txSzSqrUp == TX_32X32` to `TX_SET_DCTONLY`).
pub const INTRA_TX_TYPE_SET1_SIZES: usize = 2;
/// First-axis length of [`DEFAULT_INTRA_TX_TYPE_SET2_CDF`] — three
/// entries for `Tx_Size_Sqr[ txSz ] ∈ { TX_4X4, TX_8X8, TX_16X16 }`, the
/// reachable sizes for the reduced intra-tx-type set (per §5.11.48:
/// `txSzSqrUp > TX_32X32` already returns `TX_SET_DCTONLY`, and only
/// `txSzSqr == TX_16X16` or `reduced_tx_set == 1` routes to
/// `TX_SET_INTRA_2`).
pub const INTRA_TX_TYPE_SET2_SIZES: usize = 3;
/// `Filter_Intra_Mode_To_Intra_Dir[ INTRA_FILTER_MODES ]` (§8.3.2
/// `intra_tx_type`) — when `use_filter_intra == 1`, the §8.3.2 `intraDir`
/// axis is `Filter_Intra_Mode_To_Intra_Dir[ filter_intra_mode ]` rather
/// than `YMode`. The spec lists the entries as
/// `{ DC_PRED, V_PRED, H_PRED, D157_PRED, DC_PRED }`; their numeric
/// values follow the §6.10.x intra-mode enumeration (`DC_PRED = 0`,
/// `V_PRED = 1`, `H_PRED = 2`, `D157_PRED = 6`).
pub const FILTER_INTRA_MODE_TO_INTRA_DIR: [usize; INTRA_FILTER_MODES] = [0, 1, 2, 6, 0];
/// `INTRA_FILTER_SCALE_BITS` (§3 constant table, av1-spec p.20) — the
/// scaling shift `Round2Signed( pr, INTRA_FILTER_SCALE_BITS )` applies
/// to each §7.11.2.3 recursive-intra filter accumulation. The spec
/// value is `4`.
pub const INTRA_FILTER_SCALE_BITS: u32 = 4;
/// `Intra_Filter_Taps[ INTRA_FILTER_MODES ][ 8 ][ 7 ]` (additional
/// tables, av1-spec p.406-407) — the §7.11.2.3 recursive-intra filter
/// kernel. Indexed `[ filter_intra_mode ][ ( i1 << 2 ) + j1 ][ i ]`:
/// the per-mode 8-position (2 rows × 4 cols of the 4×2 output block)
/// 7-tap filters applied to the `p[ 0..6 ]` neighbour array. The taps
/// are signed.
pub const INTRA_FILTER_TAPS: [[[i32; 7]; 8]; INTRA_FILTER_MODES] = [
[
[-6, 10, 0, 0, 0, 12, 0],
[-5, 2, 10, 0, 0, 9, 0],
[-3, 1, 1, 10, 0, 7, 0],
[-3, 1, 1, 2, 10, 5, 0],
[-4, 6, 0, 0, 0, 2, 12],
[-3, 2, 6, 0, 0, 2, 9],
[-3, 2, 2, 6, 0, 2, 7],
[-3, 1, 2, 2, 6, 3, 5],
],
[
[-10, 16, 0, 0, 0, 10, 0],
[-6, 0, 16, 0, 0, 6, 0],
[-4, 0, 0, 16, 0, 4, 0],
[-2, 0, 0, 0, 16, 2, 0],
[-10, 16, 0, 0, 0, 0, 10],
[-6, 0, 16, 0, 0, 0, 6],
[-4, 0, 0, 16, 0, 0, 4],
[-2, 0, 0, 0, 16, 0, 2],
],
[
[-8, 8, 0, 0, 0, 16, 0],
[-8, 0, 8, 0, 0, 16, 0],
[-8, 0, 0, 8, 0, 16, 0],
[-8, 0, 0, 0, 8, 16, 0],
[-4, 4, 0, 0, 0, 0, 16],
[-4, 0, 4, 0, 0, 0, 16],
[-4, 0, 0, 4, 0, 0, 16],
[-4, 0, 0, 0, 4, 0, 16],
],
[
[-2, 8, 0, 0, 0, 10, 0],
[-1, 3, 8, 0, 0, 6, 0],
[-1, 2, 3, 8, 0, 4, 0],
[0, 1, 2, 3, 8, 2, 0],
[-1, 4, 0, 0, 0, 3, 10],
[-1, 3, 4, 0, 0, 4, 6],
[-1, 2, 3, 4, 0, 4, 4],
[-1, 2, 2, 3, 4, 3, 3],
],
[
[-12, 14, 0, 0, 0, 14, 0],
[-10, 0, 14, 0, 0, 12, 0],
[-9, 0, 0, 14, 0, 11, 0],
[-8, 0, 0, 0, 14, 10, 0],
[-10, 12, 0, 0, 0, 0, 14],
[-9, 1, 12, 0, 0, 0, 12],
[-8, 0, 0, 12, 0, 1, 11],
[-7, 0, 0, 1, 12, 1, 9],
],
];
/// `INTERP_FILTERS` per §3. Number of values for `interp_filter` (the
/// three switchable filters: `EIGHTTAP`, `EIGHTTAP_SMOOTH`,
/// `EIGHTTAP_SHARP` — `BILINEAR` is reachable only when the frame
/// header's `interpolation_filter` is `BILINEAR` and the §5.11.x
/// switchable-filter path is not entered).
pub const INTERP_FILTERS: usize = 3;
/// `INTERP_FILTER_CONTEXTS` per §3. Number of contexts for
/// `interp_filter`. The §8.3.2 ctx formula (`((dir & 1) * 2 +
/// (RefFrame[1] > INTRA_FRAME)) * 4 + ...`) ranges across
/// `0..INTERP_FILTER_CONTEXTS`.
pub const INTERP_FILTER_CONTEXTS: usize = 16;
/// `EIGHTTAP` — §6.8.9 `interpolation_filter` ordinal `0`. Default
/// 8-tap separable filter used for inter prediction. Reached by the
/// §5.11.x block-level `interp_filter[ dir ]` S() (the first symbol
/// of `TileInterpFilterCdf[ ctx ]`) or by the §5.9.10 frame-level
/// `interpolation_filter` f(2) = 0 (when `is_filter_switchable == 0`
/// and the f(2) reads `0`).
pub const EIGHTTAP: u8 = 0;
/// `EIGHTTAP_SMOOTH` — §6.8.9 `interpolation_filter` ordinal `1`.
/// Smoother 8-tap filter used for inter prediction. Reached by the
/// §5.11.x block-level S() returning `1` or the §5.9.10 frame-level
/// f(2) returning `1`.
pub const EIGHTTAP_SMOOTH: u8 = 1;
/// `EIGHTTAP_SHARP` — §6.8.9 `interpolation_filter` ordinal `2`.
/// Sharper 8-tap filter used for inter prediction. Reached by the
/// §5.11.x block-level S() returning `2` or the §5.9.10 frame-level
/// f(2) returning `2`.
pub const EIGHTTAP_SHARP: u8 = 2;
/// `BILINEAR` — §6.8.9 `interpolation_filter` ordinal `3`. 2-tap
/// bilinear filter. Reachable only via the §5.9.10 frame-level f(2)
/// = 3 path (the §5.11.x block-level S() codes only three switchable
/// symbols, `EIGHTTAP..=EIGHTTAP_SHARP = 0..=2`, against the §9.4
/// `Default_Interp_Filter_Cdf`). The §5.11.7 intra-bc arm and the
/// §7.11.3 prediction process also force `interp_filter[ 0..2 ] =
/// BILINEAR` per the spec body at av1-spec p.65.
pub const BILINEAR: u8 = 3;
/// `SWITCHABLE` — §6.8.9 `interpolation_filter` ordinal `4`.
/// Frame-header value that indicates per-block selection at
/// §5.11.x. When `interpolation_filter == SWITCHABLE` the §5.11.x
/// body runs the per-direction `if ( needs_interp_filter ) interp_filter
/// S() else interp_filter = EIGHTTAP` loop; otherwise every block on
/// the frame inherits the frame-header value for both dirs.
pub const SWITCHABLE: u8 = 4;
/// `MOTION_MODES` per §3 — number of values for `motion_mode`
/// (`SIMPLE = 0`, `OBMC = 1`, `LOCALWARP = 2`; see §6.10.26 semantics).
/// Drives the row width of [`DEFAULT_MOTION_MODE_CDF`]
/// (`MOTION_MODES + 1` = three cumulative frequencies + the §8.3
/// adaptation counter).
pub const MOTION_MODES: usize = 3;
/// `SIMPLE` (§6.10.26 `motion_mode` semantics) — the §5.11.27
/// `motion_mode` SIMPLE ordinal. The block uses plain translational
/// motion compensation; no §7.11 OBMC blend and no §7.11 warp-affine
/// projection. Reached either through the §5.11.27 short-circuit arms
/// (skip-mode / `!is_motion_mode_switchable` / small block / GLOBALMV
/// with a TRANSLATION gm_type / compound / inter-intra / no
/// overlappable candidates) or through the post-`find_warp_samples`
/// `motion_mode` S() returning `0`.
pub const MOTION_MODE_SIMPLE: u8 = 0;
/// `OBMC` (§6.10.26 `motion_mode` semantics) — the §5.11.27
/// `motion_mode` OBMC ordinal. The block uses §7.11 overlapped block
/// motion compensation: predictions from the spatially-neighbouring
/// inter-block reference frames are blended with the block's own
/// translational prediction. Reached either through the §5.11.27
/// `use_obmc` S() returning `1` (the `force_integer_mv ||
/// !allow_warped_motion || is_scaled` arm) or through the
/// `motion_mode` S() returning `1`.
pub const MOTION_MODE_OBMC: u8 = 1;
/// `WARPED_CAUSAL` (§6.10.26 `motion_mode` semantics) — the §5.11.27
/// `motion_mode` LOCALWARP ordinal. The block uses a per-block warp-
/// affine model derived from the §7.10.4 `find_warp_samples` candidate
/// list. Reached only through the post-`find_warp_samples` `motion_mode`
/// S() returning `2`; the `use_obmc` arm of §5.11.27 cannot produce
/// this value (its binary outcome is SIMPLE or OBMC).
pub const MOTION_MODE_WARPED_CAUSAL: u8 = 2;
/// `LEAST_SQUARES_SAMPLES_MAX` (§3 constant table) — upper bound on the
/// number of candidate samples retained by the §7.10.4
/// `find_warp_samples` process. The §7.10.4.2 `add_sample` body
/// immediately exits once `NumSamplesScanned >=
/// LEAST_SQUARES_SAMPLES_MAX`, so `CandList` is bounded at this size.
pub const LEAST_SQUARES_SAMPLES_MAX: usize = 8;
/// `REF_SCALE_SHIFT` (§3 constant table) — fixed-point shift used by
/// the §5.11.27 `is_scaled` reference-frame scaling check.
/// `xScale = ((RefUpscaledWidth[refIdx] << REF_SCALE_SHIFT) + (FrameWidth
/// / 2)) / FrameWidth`; `noScale = 1 << REF_SCALE_SHIFT`. A reference
/// frame is "scaled" iff `xScale != noScale || yScale != noScale`.
pub const REF_SCALE_SHIFT: u32 = 14;
/// `COMPOUND_TYPES` per §3 — number of values for `compound_type`
/// (the §8.3.2 binary symbol selecting `COMPOUND_WEDGE` vs
/// `COMPOUND_DIFFWTD`; see §6.10.24 semantics). Drives the row width
/// of [`DEFAULT_COMPOUND_TYPE_CDF`] (`COMPOUND_TYPES + 1` = two
/// cumulative frequencies + the §8.3 adaptation counter).
pub const COMPOUND_TYPES: usize = 2;
/// `COMP_GROUP_IDX_CONTEXTS` per §3 — number of contexts for
/// `comp_group_idx`. Drives the outer dimension of
/// [`DEFAULT_COMP_GROUP_IDX_CDF`]. The §8.3.2 selection clamps the
/// computed context with `ctx = Min(5, ctx)`, so the valid range is
/// `0..COMP_GROUP_IDX_CONTEXTS`.
pub const COMP_GROUP_IDX_CONTEXTS: usize = 6;
/// `COMPOUND_IDX_CONTEXTS` per §3 — number of contexts for
/// `compound_idx`. Drives the outer dimension of
/// [`DEFAULT_COMPOUND_IDX_CDF`]; valid range is
/// `0..COMPOUND_IDX_CONTEXTS`.
pub const COMPOUND_IDX_CONTEXTS: usize = 6;
/// `BLOCK_SIZE_GROUPS` (§3) — number of contexts when decoding `y_mode`
/// (the inter-frame, non-keyframe luma intra-mode element). Drives the
/// outer dimension of [`DEFAULT_Y_MODE_CDF`]. The §8.3.2 selection
/// computes `ctx = Size_Group[ MiSize ]`, in `0..BLOCK_SIZE_GROUPS`.
pub const BLOCK_SIZE_GROUPS: usize = 4;
/// `UV_INTRA_MODES_CFL_NOT_ALLOWED` (§3) — number of values for `uv_mode`
/// when chroma-from-luma is not allowed (i.e. `UV_CFL_PRED` is excluded).
/// Drives the row width of [`DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF`]
/// (`UV_INTRA_MODES_CFL_NOT_ALLOWED + 1` cumulative frequencies + the
/// §8.3 adaptation counter).
pub const UV_INTRA_MODES_CFL_NOT_ALLOWED: usize = 13;
/// `UV_INTRA_MODES_CFL_ALLOWED` (§3) — number of values for `uv_mode`
/// when chroma-from-luma is allowed (i.e. `UV_CFL_PRED` is included).
/// Drives the row width of [`DEFAULT_UV_MODE_CFL_ALLOWED_CDF`]
/// (`UV_INTRA_MODES_CFL_ALLOWED + 1` cumulative frequencies + the §8.3
/// adaptation counter).
pub const UV_INTRA_MODES_CFL_ALLOWED: usize = 14;
/// `Size_Group[ BLOCK_SIZES ]` (§8.3.2) — maps a luma block size
/// (`MiSize`) into the `y_mode` context (and, after `- 1`, several other
/// intra-syntax contexts). Used by the §8.3.2 `y_mode` selection as
/// `ctx = Size_Group[ MiSize ]`, yielding a value in
/// `0..BLOCK_SIZE_GROUPS`.
pub const SIZE_GROUP: [usize; BLOCK_SIZES] = [
0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2,
];
// ---------------------------------------------------------------------
// Round 148 — §9.3 block-size conversion tables (av1-spec p.400-401).
// Stages the four `BLOCK_SIZES`-indexed lookup tables that convert a
// `MiSize` into block dimensions, plus the related §3 `MI_SIZE` /
// `MI_SIZE_LOG2` constants (p.20 enumeration). These feed the §5.11.49
// `palette_tokens` caller (block_w / block_h derivation) landed in r147
// and the wider §5.x reconstruction code that the parser eventually
// surfaces (§5.11.15 `read_block` and onward).
//
// All four §9.3 tables are listed in the spec as a 22-entry `BLOCK_SIZES`
// array; this round transcribes the published values verbatim and adds
// the matching `MiSize`-keyed accessor helpers. The
// `Block_Width[ x ] = 4 * Num_4x4_Blocks_Wide[ x ]` /
// `Block_Height[ x ] = 4 * Num_4x4_Blocks_High[ x ]` derivations
// (av1-spec p.401) are encoded as `block_width` / `block_height` helpers
// that reuse the `NUM_4X4_BLOCKS_*` tables, matching the spec's
// definition without duplicating numeric values.
// ---------------------------------------------------------------------
/// `MI_SIZE` (§3 constant table, av1-spec p.20) — smallest size of a
/// mode-info block in luma samples. Equal to `4`. Used in the §5.x
/// `MiCols = (FrameWidth + MI_SIZE - 1) >> MI_SIZE_LOG2` derivation
/// (the per-frame mode-info grid) and as the unit-conversion factor
/// from "4-sample units" (`Num_4x4_Blocks_Wide`, `Mi_Width_Log2`) to
/// luma samples (`Block_Width`).
pub const MI_SIZE: usize = 4;
/// `MI_SIZE_LOG2` (§3 constant table, av1-spec p.20) — base-2 logarithm
/// of [`MI_SIZE`], i.e. `2`. Used by every `>> MI_SIZE_LOG2` /
/// `<< MI_SIZE_LOG2` conversion between luma samples and mode-info
/// grid units (e.g. §7.11.3.3 `row = startY >> MI_SIZE_LOG2`).
pub const MI_SIZE_LOG2: usize = 2;
/// `INTRABC_DELAY_PIXELS` (§3 constant table, av1-spec p.7) — number of
/// horizontal luma samples before intra block copy can be used. Equal
/// to `256`. Consumed by the §5.11.26 `assign_mv` intra-block-copy
/// fallback predictor (`PredMv[ 0 ][ 1 ] = -(sbSize4 * MI_SIZE +
/// INTRABC_DELAY_PIXELS) * 8` on the top superblock row) and by the
/// §7.10.x intra-block-copy wave-front validity checks.
pub const INTRABC_DELAY_PIXELS: usize = 256;
/// `Mi_Width_Log2[ BLOCK_SIZES ]` (§9.3, av1-spec p.400) — base-2
/// logarithm of the block width in units of `4` luma samples. Indexed
/// by `MiSize`, returning a value in `0..=5`. Used by §5.11.46
/// `bsizeCtx = Mi_Width_Log2[ MiSize ] + Mi_Height_Log2[ MiSize ] - 2`
/// (the palette block-size context).
pub const MI_WIDTH_LOG2: [usize; BLOCK_SIZES] = [
0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 0, 2, 1, 3, 2, 4,
];
/// `Mi_Height_Log2[ BLOCK_SIZES ]` (§9.3, av1-spec p.400) — base-2
/// logarithm of the block height in units of `4` luma samples. Indexed
/// by `MiSize`, returning a value in `0..=5`. Companion to
/// [`MI_WIDTH_LOG2`].
pub const MI_HEIGHT_LOG2: [usize; BLOCK_SIZES] = [
0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 2, 0, 3, 1, 4, 2,
];
/// `Num_4x4_Blocks_Wide[ BLOCK_SIZES ]` (§9.3, av1-spec p.400) — width
/// of the block in units of `4` luma samples. Equal to
/// `1 << MI_WIDTH_LOG2[ x ]` for every entry. Indexed by `MiSize`,
/// returning a value in `1..=32`. Used by §7.5 / §7.11 reconstruction
/// loops (`bw4 = Num_4x4_Blocks_Wide[ MiSize ]`).
pub const NUM_4X4_BLOCKS_WIDE: [usize; BLOCK_SIZES] = [
1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16,
];
/// `Num_4x4_Blocks_High[ BLOCK_SIZES ]` (§9.3, av1-spec p.400) — height
/// of the block in units of `4` luma samples. Equal to
/// `1 << MI_HEIGHT_LOG2[ x ]` for every entry. Indexed by `MiSize`,
/// returning a value in `1..=32`. Companion to [`NUM_4X4_BLOCKS_WIDE`].
pub const NUM_4X4_BLOCKS_HIGH: [usize; BLOCK_SIZES] = [
1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4,
];
/// `Block_Width[ MiSize ]` (§9.3, av1-spec p.401) — width of a block in
/// luma samples. Defined by the spec as
/// `Block_Width[ x ] = 4 * Num_4x4_Blocks_Wide[ x ]`, i.e.
/// `NUM_4X4_BLOCKS_WIDE[ mi_size ] << MI_SIZE_LOG2`. Returns a value
/// in `4..=128`. Panics in debug builds if `mi_size >= BLOCK_SIZES`.
///
/// This is the per-`MiSize` driver used to seed
/// [`palette_tokens_plane`]'s `block_w` / `onscreen_w` once the parser
/// is wired into the §5.11.x reconstruction pass.
#[inline]
#[must_use]
pub const fn block_width(mi_size: usize) -> usize {
debug_assert!(mi_size < BLOCK_SIZES);
NUM_4X4_BLOCKS_WIDE[mi_size] << MI_SIZE_LOG2
}
/// `Block_Height[ MiSize ]` (§9.3, av1-spec p.401) — height of a block
/// in luma samples. Defined by the spec as
/// `Block_Height[ x ] = 4 * Num_4x4_Blocks_High[ x ]`, i.e.
/// `NUM_4X4_BLOCKS_HIGH[ mi_size ] << MI_SIZE_LOG2`. Returns a value
/// in `4..=128`. Panics in debug builds if `mi_size >= BLOCK_SIZES`.
///
/// Companion to [`block_width`].
#[inline]
#[must_use]
pub const fn block_height(mi_size: usize) -> usize {
debug_assert!(mi_size < BLOCK_SIZES);
NUM_4X4_BLOCKS_HIGH[mi_size] << MI_SIZE_LOG2
}
/// `Num_4x4_Blocks_Wide[ MiSize ]` (§9.3) — convenience accessor for
/// [`NUM_4X4_BLOCKS_WIDE`]. Returns the block width in units of `4`
/// luma samples. Panics in debug builds if `mi_size >= BLOCK_SIZES`.
#[inline]
#[must_use]
pub const fn num_4x4_blocks_wide(mi_size: usize) -> usize {
debug_assert!(mi_size < BLOCK_SIZES);
NUM_4X4_BLOCKS_WIDE[mi_size]
}
/// `Num_4x4_Blocks_High[ MiSize ]` (§9.3) — convenience accessor for
/// [`NUM_4X4_BLOCKS_HIGH`]. Returns the block height in units of `4`
/// luma samples. Panics in debug builds if `mi_size >= BLOCK_SIZES`.
#[inline]
#[must_use]
pub const fn num_4x4_blocks_high(mi_size: usize) -> usize {
debug_assert!(mi_size < BLOCK_SIZES);
NUM_4X4_BLOCKS_HIGH[mi_size]
}
/// `Mi_Width_Log2[ MiSize ]` (§9.3) — convenience accessor for
/// [`MI_WIDTH_LOG2`]. Panics in debug builds if
/// `mi_size >= BLOCK_SIZES`.
#[inline]
#[must_use]
pub const fn mi_width_log2(mi_size: usize) -> usize {
debug_assert!(mi_size < BLOCK_SIZES);
MI_WIDTH_LOG2[mi_size]
}
/// `Mi_Height_Log2[ MiSize ]` (§9.3) — convenience accessor for
/// [`MI_HEIGHT_LOG2`]. Panics in debug builds if
/// `mi_size >= BLOCK_SIZES`.
#[inline]
#[must_use]
pub const fn mi_height_log2(mi_size: usize) -> usize {
debug_assert!(mi_size < BLOCK_SIZES);
MI_HEIGHT_LOG2[mi_size]
}
// ---------------------------------------------------------------------
// Round 180 — §5.11.38 `get_plane_residual_size` + §5.11.33
// `compute_prediction` per-plane block-size derivation.
//
// `Subsampled_Size[ subsize ][ subX ][ subY ]` (av1-spec p.88) is the
// §5.11.38 lookup the §5.11.30 / §5.11.33 / §5.11.34 callers consume
// to translate a luma `MiSize` into a per-plane residual block size
// (chroma residual blocks are width/height-halved by `subsampling_x` /
// `subsampling_y`, with the §3 enumeration's `BLOCK_INVALID` slots
// marking the configurations forbidden by the §6.4.1
// `monochrome` / `subsampling_{x,y}` gates).
// ---------------------------------------------------------------------
/// `Subsampled_Size[ BLOCK_SIZES ][ 2 ][ 2 ]` (av1-spec p.88,
/// §5.11.38 `get_plane_residual_size`) — for a luma `subsize` plus a
/// per-plane `(subsampling_x, subsampling_y)` pair, returns the
/// `BLOCK_*` ordinal of the per-plane residual block. The
/// `(0, 0)` cell is always equal to `subsize` (luma needs no
/// down-shift); the other three cells fold the `subsampling_x` /
/// `subsampling_y` chroma halving into a §3-enumeration ordinal,
/// promoting any post-shift `2` dimension back to `4` (the §3
/// enumeration's smallest tile dimension). The §3 enumeration's
/// `BLOCK_INVALID` sentinel marks configurations the §6.4.1
/// `subsampling_{x,y}` / `monochrome` gates forbid (e.g.
/// `BLOCK_4X8` with `subsampling_x == 1`, `subsampling_y == 0` —
/// chroma would need a `2`-wide x `8`-tall residual the §3 ordinal
/// space cannot express).
///
/// Indexed as `SUBSAMPLED_SIZE[subsize][subx][suby]`. Caller-bug if
/// `subsize >= BLOCK_SIZES`.
pub const SUBSAMPLED_SIZE: [[[usize; 2]; 2]; BLOCK_SIZES] = [
// BLOCK_4X4 — already 4x4 chroma cell.
[[BLOCK_4X4, BLOCK_4X4], [BLOCK_4X4, BLOCK_4X4]],
// BLOCK_4X8
[[BLOCK_4X8, BLOCK_4X4], [BLOCK_INVALID, BLOCK_4X4]],
// BLOCK_8X4
[[BLOCK_8X4, BLOCK_INVALID], [BLOCK_4X4, BLOCK_4X4]],
// BLOCK_8X8
[[BLOCK_8X8, BLOCK_8X4], [BLOCK_4X8, BLOCK_4X4]],
// BLOCK_8X16
[[BLOCK_8X16, BLOCK_8X8], [BLOCK_INVALID, BLOCK_4X8]],
// BLOCK_16X8
[[BLOCK_16X8, BLOCK_INVALID], [BLOCK_8X8, BLOCK_8X4]],
// BLOCK_16X16
[[BLOCK_16X16, BLOCK_16X8], [BLOCK_8X16, BLOCK_8X8]],
// BLOCK_16X32
[[BLOCK_16X32, BLOCK_16X16], [BLOCK_INVALID, BLOCK_8X16]],
// BLOCK_32X16
[[BLOCK_32X16, BLOCK_INVALID], [BLOCK_16X16, BLOCK_16X8]],
// BLOCK_32X32
[[BLOCK_32X32, BLOCK_32X16], [BLOCK_16X32, BLOCK_16X16]],
// BLOCK_32X64
[[BLOCK_32X64, BLOCK_32X32], [BLOCK_INVALID, BLOCK_16X32]],
// BLOCK_64X32
[[BLOCK_64X32, BLOCK_INVALID], [BLOCK_32X32, BLOCK_32X16]],
// BLOCK_64X64
[[BLOCK_64X64, BLOCK_64X32], [BLOCK_32X64, BLOCK_32X32]],
// BLOCK_64X128
[[BLOCK_64X128, BLOCK_64X64], [BLOCK_INVALID, BLOCK_32X64]],
// BLOCK_128X64
[[BLOCK_128X64, BLOCK_INVALID], [BLOCK_64X64, BLOCK_64X32]],
// BLOCK_128X128
[[BLOCK_128X128, BLOCK_128X64], [BLOCK_64X128, BLOCK_64X64]],
// BLOCK_4X16
[[BLOCK_4X16, BLOCK_4X8], [BLOCK_INVALID, BLOCK_4X8]],
// BLOCK_16X4
[[BLOCK_16X4, BLOCK_INVALID], [BLOCK_8X4, BLOCK_8X4]],
// BLOCK_8X32
[[BLOCK_8X32, BLOCK_8X16], [BLOCK_INVALID, BLOCK_4X16]],
// BLOCK_32X8
[[BLOCK_32X8, BLOCK_INVALID], [BLOCK_16X8, BLOCK_16X4]],
// BLOCK_16X64
[[BLOCK_16X64, BLOCK_16X32], [BLOCK_INVALID, BLOCK_8X32]],
// BLOCK_64X16
[[BLOCK_64X16, BLOCK_INVALID], [BLOCK_32X16, BLOCK_32X8]],
];
/// `get_plane_residual_size( subsize, plane )` per §5.11.38 (av1-spec
/// p.88) — returns the `BLOCK_*` ordinal of the per-plane residual
/// block.
///
/// The spec body reads:
///
/// ```text
/// get_plane_residual_size( subsize, plane ) {
/// subx = plane > 0 ? subsampling_x : 0
/// suby = plane > 0 ? subsampling_y : 0
/// return Subsampled_Size[ subsize ][ subx ][ suby ]
/// }
/// ```
///
/// `plane == 0` (luma) returns `subsize` unchanged (the `(0, 0)` cell
/// of [`SUBSAMPLED_SIZE`]). `plane > 0` (chroma) applies the
/// per-plane subsampling pair from the §5.5.2 sequence-header
/// `color_config()`. The §6.4.1 conformance requirement that
/// `get_plane_residual_size( subsize, 1 ) != BLOCK_INVALID` holds on
/// every reachable code path; this helper returns `Some(BLOCK_*)`
/// for that case and `None` for the §3-enumeration sentinel (i.e.
/// the §6.4.1-forbidden combinations).
///
/// Returns `None` if `subsize >= BLOCK_SIZES` (a caller bug — every
/// dispatcher upstream gates on the `BLOCK_SIZES` bound first).
#[inline]
#[must_use]
pub fn get_plane_residual_size(
subsize: usize,
plane: u8,
subsampling_x: u8,
subsampling_y: u8,
) -> Option<usize> {
if subsize >= BLOCK_SIZES {
return None;
}
let subx = if plane > 0 {
(subsampling_x != 0) as usize
} else {
0
};
let suby = if plane > 0 {
(subsampling_y != 0) as usize
} else {
0
};
let raw = SUBSAMPLED_SIZE[subsize][subx][suby];
if raw == BLOCK_INVALID {
None
} else {
Some(raw)
}
}
// ---------------------------------------------------------------------
// Round 180 — §7.11.2.5 DC intra prediction process.
//
// `predict_intra_dc_pred` is the §7.11.2.5 sample-generation routine
// the §7.11.2.1 dispatcher calls when `mode == DC_PRED` (av1-spec
// p.248-249). Operates on caller-supplied `AboveRow[ 0..w-1 ]` /
// `LeftCol[ 0..h-1 ]` slices (the §7.11.2.1 prologue's derived
// neighbour arrays) plus a flat `pred` output buffer of size
// `h * w` (row-major), writing the per-cell prediction sample.
//
// `BitDepth` enters only through the no-neighbour fallback
// (`pred = 1 << (BitDepth - 1)`); a caller with `BitDepth == 8`
// passes `bit_depth = 8` and the helper writes `128` into every
// `pred[i][j]` cell.
//
// This is the §7.11.2 sample-generation leaf — separate from the
// §5.11.30 / §5.11.33 dispatcher (which only walks the per-plane
// block enumeration). The dispatcher's `predict_intra` call site
// passes `AboveRow` / `LeftCol` derived from `CurrFrame`, which
// the §5.11.5 syntax walker does not yet track; the leaf is
// therefore exposed for direct caller use (e.g. a future
// `transform_block`-driving reconstruction pass).
// ---------------------------------------------------------------------
/// §7.11.2.5 DC intra prediction process (av1-spec p.248-249) — fills
/// `pred[ 0..h*w ]` with the DC prediction for the given neighbour
/// configuration.
///
/// The spec body reads as a four-arm dispatch on `(haveLeft,
/// haveAbove)`:
///
/// * `(1, 1)` ⇒ `pred[i][j] = avg`, where `avg = (sum + (w+h)/2) /
/// (w+h)` with `sum = Σ LeftCol[k] + Σ AboveRow[k]`.
/// * `(1, 0)` ⇒ `pred[i][j] = Clip1( ( sum + (h>>1) ) >> log2H )`,
/// `sum = Σ LeftCol[k]`.
/// * `(0, 1)` ⇒ `pred[i][j] = Clip1( ( sum + (w>>1) ) >> log2W )`,
/// `sum = Σ AboveRow[k]`.
/// * `(0, 0)` ⇒ `pred[i][j] = 1 << (BitDepth - 1)` (e.g. `128` for
/// 8-bit content).
///
/// `Clip1(x) = Clip3(0, (1 << BitDepth) - 1, x)` per §3 `Clip1`.
///
/// ## Arguments
///
/// * `have_left` — `1` if `LeftCol` carries valid samples (`haveLeft`
/// from §7.11.2.1).
/// * `have_above` — `1` if `AboveRow` carries valid samples.
/// * `log2_w` / `log2_h` — base-2 logarithms of the prediction
/// region's width / height (passed through from §7.11.2.1's
/// `log2W` / `log2H` inputs). Must satisfy `(1 << log2_w) == w` /
/// `(1 << log2_h) == h`.
/// * `w` / `h` — the prediction region's width / height in luma
/// samples. Both in `4..=64` per the §3 transform-size table.
/// * `bit_depth` — the §5.5.2 frame bit depth (`8`, `10`, or `12`).
/// * `above_row` — `AboveRow[ 0..w-1 ]`. Length must be `>= w`. Ignored
/// when `have_above == 0`.
/// * `left_col` — `LeftCol[ 0..h-1 ]`. Length must be `>= h`. Ignored
/// when `have_left == 0`.
/// * `pred` — output buffer, row-major; length must be `>= h * w`. The
/// helper writes `pred[i * w + j]` for `i in 0..h`, `j in 0..w`.
///
/// ## Returns
///
/// `Ok(())` on success.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments: `bit_depth` not in `{8, 10, 12}`; `w != (1 << log2_w)`
/// or `h != (1 << log2_h)`; `log2_w > 6` or `log2_h > 6`;
/// `above_row.len() < w` (when `have_above != 0`);
/// `left_col.len() < h` (when `have_left != 0`); `pred.len() < h * w`.
#[allow(clippy::too_many_arguments)]
pub fn predict_intra_dc_pred(
have_left: u8,
have_above: u8,
log2_w: u32,
log2_h: u32,
w: usize,
h: usize,
bit_depth: u8,
above_row: &[u16],
left_col: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
// ---------- caller-bug guards ----------
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if log2_w > 6 || log2_h > 6 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if w != (1usize << log2_w) || h != (1usize << log2_h) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h * w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if have_above != 0 && above_row.len() < w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if have_left != 0 && left_col.len() < h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §7.11.2.5 four-arm dispatch on (haveLeft, haveAbove).
let avg: u32 = if have_left != 0 && have_above != 0 {
// §7.11.2.5 arm 1: union average.
//
// sum = Σ LeftCol[k] for k in 0..h
// + Σ AboveRow[k] for k in 0..w
// sum += (w + h) >> 1
// avg = sum / (w + h)
//
// The spec's reference-note observes the division by `w + h`
// can be implemented with a multiply-shift; we use the spec's
// direct division — the result is identical for the `w + h ∈
// {8, 12, 16, 20, 24, 32, 40, 48, 64, 96, 128}` set the §3
// transform-size table admits, and it keeps the
// implementation provably equivalent to the spec text.
let mut sum: u32 = 0;
for v in left_col.iter().take(h) {
sum += *v as u32;
}
for v in above_row.iter().take(w) {
sum += *v as u32;
}
sum += ((w + h) as u32) >> 1;
sum / ((w + h) as u32)
} else if have_left != 0 {
// §7.11.2.5 arm 2: left-only average with `Clip1`.
let mut sum: u32 = 0;
for v in left_col.iter().take(h) {
sum += *v as u32;
}
let v = (sum + ((h as u32) >> 1)) >> log2_h;
clip1_bit_depth(v, bit_depth)
} else if have_above != 0 {
// §7.11.2.5 arm 3: above-only average with `Clip1`.
let mut sum: u32 = 0;
for v in above_row.iter().take(w) {
sum += *v as u32;
}
let v = (sum + ((w as u32) >> 1)) >> log2_w;
clip1_bit_depth(v, bit_depth)
} else {
// §7.11.2.5 arm 4: no-neighbour fallback (the
// `1 << (BitDepth - 1)` mid-grey value).
1u32 << (bit_depth - 1)
};
let avg = avg as u16;
for cell in pred.iter_mut().take(h * w) {
*cell = avg;
}
Ok(())
}
/// §3 `Clip1(x) = Clip3(0, (1 << BitDepth) - 1, x)`. The clip
/// upper bound depends on `bit_depth` (`255`, `1023`, or `4095`).
#[inline]
fn clip1_bit_depth(x: u32, bit_depth: u8) -> u32 {
let max = (1u32 << bit_depth) - 1;
if x > max {
max
} else {
x
}
}
// ---------------------------------------------------------------------
// Round 186 — §7.11.2.1 `AboveRow[]` / `LeftCol[]` neighbour derivation.
//
// The §7.11.2.1 prologue (av1-spec p.241-242) walks `CurrFrame[plane]`
// at the per-TU `(x, y)` top-left to populate the two neighbour
// arrays the §7.11.2.{2..6} leaves consume:
//
// * `AboveRow[ 0..w+h-1 ]` — top edge samples, with the right-tail
// clamped to `maxX` when the TU is at the frame's right boundary
// and constant-propagated from the single available LEFT cell
// when `haveAbove == 0 && haveLeft == 1`.
// * `LeftCol[ 0..w+h-1 ]` — left edge samples, mirrored derivation.
//
// On the all-unavailable arm (`haveAbove == 0 && haveLeft == 0`)
// `AboveRow[i]` is filled with `(1 << (BitDepth - 1)) - 1` and
// `LeftCol[i]` with `(1 << (BitDepth - 1)) + 1` — the spec's
// asymmetric mid-grey offsets that the §7.11.2.2 PAETH base-sample
// derivation depends on to break ties cleanly.
//
// These helpers stage the prologue as standalone routines so the
// V_PRED / H_PRED leaves (and the next-arc D-mode / SMOOTH /
// PAETH bodies) can be driven end-to-end from a `CurrFrame[plane]`
// buffer the walker's §7.12.3 step-3 merge has populated. The
// `AboveRow[-1]` / `LeftCol[-1]` corner sample (§7.11.2.1 last
// bullet) is not derived here — V_PRED / H_PRED do not consult it;
// it lands alongside the next-arc PAETH leaf which is its only
// reader on the degenerate-angle subset.
// ---------------------------------------------------------------------
/// §7.11.2.1 `AboveRow[ 0..w+h-1 ]` derivation (av1-spec p.241) —
/// fills `above_row[ 0..w+h-1 ]` from the per-plane `CurrFrame`
/// buffer at the `(x, y)` top-left of the current transform block.
///
/// ## Arguments
///
/// * `have_above` — `1` if the TU has valid samples above (`haveAbove`
/// from §7.11.2.1). Routed from the §5.11.5 `AvailU` derivation.
/// * `have_left` — `1` if the TU has valid samples to the left
/// (`haveLeft` from §7.11.2.1).
/// * `have_above_right` — `1` if the TU has valid samples above-right
/// (drives the `aboveLimit` `2*w` extension).
/// * `x` / `y` — the per-plane top-left in `CurrFrame[plane]` sample
/// space (already post-subsampling for `plane > 0`).
/// * `w` / `h` — prediction region width / height in plane samples.
/// * `max_x` — `(MiCols * MI_SIZE) - 1` for plane 0; for `plane > 0`
/// it is `((MiCols * MI_SIZE) >> subsampling_x) - 1`. The spec's
/// per-plane right-edge sample index clamp.
/// * `curr_frame` — flat row-major `CurrFrame[plane]` buffer.
/// * `curr_frame_cols` — column stride of `curr_frame`. The flat
/// index for `(row, col)` is `row * curr_frame_cols + col`.
/// * `bit_depth` — §5.5.2 bit depth (`8`, `10`, or `12`); used only
/// for the all-unavailable mid-grey fallback.
/// * `above_row` — output buffer; length must be `>= w + h`.
///
/// ## Returns
///
/// `Ok(())` on success.
///
/// [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments: `w > 64`, `h > 64`, `above_row.len() < w + h`,
/// `bit_depth ∉ {8, 10, 12}`, `curr_frame_cols == 0`, or the
/// `have_above != 0` arm's `(y - 1, x + i)` reads landing outside
/// the buffer (a real walker post-§7.12.3-merge invariant keeps
/// the per-TU coordinates in-buffer; this guard catches the
/// caller-bug case where they don't).
#[allow(clippy::too_many_arguments)]
pub fn derive_above_row(
have_above: u8,
have_left: u8,
have_above_right: u8,
x: u32,
y: u32,
w: usize,
h: usize,
max_x: u32,
curr_frame: &[i32],
curr_frame_cols: u32,
bit_depth: u8,
above_row: &mut [u16],
) -> Result<(), crate::Error> {
if w > 64 || h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let needed = w
.checked_add(h)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
if above_row.len() < needed {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if curr_frame_cols == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if have_above == 0 && have_left == 1 {
// §7.11.2.1 arm 1: AboveRow[i] = CurrFrame[plane][y][x-1].
// The `x-1` index is well-defined when `haveLeft == 1` (the
// §5.11.5 walker only sets `AvailL == true` for `x > 0`);
// a `have_left = 1 && x == 0` argument is a caller bug.
if x == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let cols = curr_frame_cols as usize;
let col = (x as usize) - 1;
let row = y as usize;
let idx = row
.checked_mul(cols)
.and_then(|p| p.checked_add(col))
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
if idx >= curr_frame.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let sample = curr_frame[idx].clamp(0, (1i32 << bit_depth) - 1) as u16;
for slot in above_row.iter_mut().take(needed) {
*slot = sample;
}
} else if have_above == 0 && have_left == 0 {
// §7.11.2.1 arm 2: AboveRow[i] = (1 << (BitDepth - 1)) - 1.
let mid_minus = ((1u32 << (bit_depth - 1)) - 1) as u16;
for slot in above_row.iter_mut().take(needed) {
*slot = mid_minus;
}
} else {
// §7.11.2.1 arm 3 (haveAbove == 1):
// aboveLimit = Min(maxX, x + (haveAboveRight ? 2*w : w) - 1).
// AboveRow[i] = CurrFrame[plane][y - 1][Min(aboveLimit, x + i)].
if y == 0 {
// haveAbove == 1 with y == 0 is a caller bug.
return Err(crate::Error::PartitionWalkOutOfRange);
}
let extend = if have_above_right != 0 { 2 * w } else { w };
// `extend == 0` would underflow; the §3 transform-size table
// forbids w == 0 (smallest is 4).
let above_limit = (max_x).min((x as u64 + extend as u64 - 1) as u32);
let cols = curr_frame_cols as usize;
let row = (y as usize) - 1;
let base = row
.checked_mul(cols)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let max_in_buffer = curr_frame.len();
let max_val = (1i32 << bit_depth) - 1;
for (i, slot) in above_row.iter_mut().take(needed).enumerate() {
let col = (x as u64 + i as u64).min(above_limit as u64) as usize;
let idx = base
.checked_add(col)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
if idx >= max_in_buffer {
return Err(crate::Error::PartitionWalkOutOfRange);
}
*slot = curr_frame[idx].clamp(0, max_val) as u16;
}
}
Ok(())
}
/// §7.11.2.1 `LeftCol[ 0..w+h-1 ]` derivation (av1-spec p.242) —
/// mirror of [`derive_above_row`] for the left-edge neighbour
/// array. `max_y` is the per-plane bottom-edge clamp (`(MiRows *
/// MI_SIZE) - 1` for plane 0).
#[allow(clippy::too_many_arguments)]
pub fn derive_left_col(
have_left: u8,
have_above: u8,
have_below_left: u8,
x: u32,
y: u32,
w: usize,
h: usize,
max_y: u32,
curr_frame: &[i32],
curr_frame_cols: u32,
bit_depth: u8,
left_col: &mut [u16],
) -> Result<(), crate::Error> {
if w > 64 || h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let needed = w
.checked_add(h)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
if left_col.len() < needed {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if curr_frame_cols == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if have_left == 0 && have_above == 1 {
// §7.11.2.1 arm 1 (LeftCol): LeftCol[i] = CurrFrame[plane][y - 1][x].
if y == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let cols = curr_frame_cols as usize;
let row = (y as usize) - 1;
let col = x as usize;
let idx = row
.checked_mul(cols)
.and_then(|p| p.checked_add(col))
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
if idx >= curr_frame.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let sample = curr_frame[idx].clamp(0, (1i32 << bit_depth) - 1) as u16;
for slot in left_col.iter_mut().take(needed) {
*slot = sample;
}
} else if have_left == 0 && have_above == 0 {
// §7.11.2.1 arm 2 (LeftCol): LeftCol[i] = (1 << (BitDepth - 1)) + 1.
let mid_plus = ((1u32 << (bit_depth - 1)) + 1) as u16;
for slot in left_col.iter_mut().take(needed) {
*slot = mid_plus;
}
} else {
// §7.11.2.1 arm 3 (haveLeft == 1):
// leftLimit = Min(maxY, y + (haveBelowLeft ? 2*h : h) - 1).
// LeftCol[i] = CurrFrame[plane][Min(leftLimit, y + i)][x - 1].
if x == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let extend = if have_below_left != 0 { 2 * h } else { h };
let left_limit = (max_y).min((y as u64 + extend as u64 - 1) as u32);
let cols = curr_frame_cols as usize;
let col = (x as usize) - 1;
let max_in_buffer = curr_frame.len();
let max_val = (1i32 << bit_depth) - 1;
for (i, slot) in left_col.iter_mut().take(needed).enumerate() {
let row = (y as u64 + i as u64).min(left_limit as u64) as usize;
let idx = row
.checked_mul(cols)
.and_then(|p| p.checked_add(col))
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
if idx >= max_in_buffer {
return Err(crate::Error::PartitionWalkOutOfRange);
}
*slot = curr_frame[idx].clamp(0, max_val) as u16;
}
}
Ok(())
}
// ---------------------------------------------------------------------
// Round 186 — §7.11.2.4 V_PRED and H_PRED sample-generation leaves.
//
// V_PRED and H_PRED are the two degenerate directional intra modes
// (`Mode_To_Angle[V_PRED] = 90`, `Mode_To_Angle[H_PRED] = 180`; av1-
// spec p.485). The §7.11.2.4 directional process collapses on those
// angles to steps 10 and 11 of the §7.11.2.4 body (av1-spec p.247):
//
// * step 10 (pAngle == 90, V_PRED): `pred[i][j] = AboveRow[j]` for
// i = 0..h-1, j = 0..w-1 — each row is a copy of `AboveRow`.
// * step 11 (pAngle == 180, H_PRED): `pred[i][j] = LeftCol[i]` for
// i = 0..h-1, j = 0..w-1 — each column is a copy of `LeftCol`.
//
// On the `AngleDeltaY == 0` / `AngleDeltaUV == 0` path the §5.11.5
// walker's `decode_intra_block_mode_info` defaults to today, the
// step-2 `pAngle = Mode_To_Angle[mode] + 0 * ANGLE_STEP` is exactly
// `90` for V_PRED and `180` for H_PRED. (Non-zero angle deltas
// shift `pAngle` into the strict `< 90` / `90 < .. < 180` /
// `> 180` arms of §7.11.2.4, which are the D-mode bodies covered by
// a subsequent arc.)
//
// The §7.11.2.1 prologue (av1-spec p.241-242) derives `AboveRow[]` /
// `LeftCol[]` from `CurrFrame[plane][..][..]` and falls back to
// `(1 << (BitDepth - 1)) ± 1` mid-grey when both neighbours are
// unavailable. These leaves take pre-derived neighbour arrays and
// write `pred[ 0..h*w ]`; the caller (the [`PartitionWalker::
// compute_prediction`] dispatcher's intra arm) drives the
// neighbour-sample derivation through a separate §7.11.2.1
// `derive_above_row` / `derive_left_col` helper, mirroring the r180
// DC_PRED leaf's caller-supplies-edges convention.
// ---------------------------------------------------------------------
/// §7.11.2.4 step 10 — vertical (V_PRED) intra prediction (av1-spec
/// p.247). Fills `pred[ 0..h*w ]` with `pred[i][j] = AboveRow[j]`
/// for `i = 0..h-1`, `j = 0..w-1` (each row is a copy of the top-
/// edge sample at column `j`).
///
/// The spec body collapses to step 10 when `pAngle == 90`, i.e.
/// `mode == V_PRED` (the §6.10.x intra-mode ordinal `1`) and
/// `angleDelta == 0` (`AngleDeltaY` / `AngleDeltaUV` from §5.11.42 /
/// §5.11.43, both default-zero on the §5.11.5 walker's reachable
/// path today).
///
/// ## Arguments
///
/// * `w` / `h` — the prediction region's width / height in plane
/// samples. Both in `4..=64` per the §3 transform-size table.
/// * `above_row` — `AboveRow[ 0..w-1 ]`. Length must be `>= w`.
/// * `pred` — output buffer, row-major; length must be `>= h * w`.
///
/// ## Returns
///
/// `Ok(())` on success.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (`w > 64`, `h > 64`, `above_row.len() < w`,
/// `pred.len() < h * w`).
pub fn predict_intra_v_pred(
w: usize,
h: usize,
above_row: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
if w > 64 || h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if above_row.len() < w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §7.11.2.4 step 10: each row of the block is filled with a
// copy of `AboveRow[ 0..w-1 ]`.
for i in 0..h {
let row = &mut pred[i * w..i * w + w];
row.copy_from_slice(&above_row[..w]);
}
Ok(())
}
/// §7.11.2.4 step 11 — horizontal (H_PRED) intra prediction (av1-
/// spec p.247). Fills `pred[ 0..h*w ]` with `pred[i][j] =
/// LeftCol[i]` for `i = 0..h-1`, `j = 0..w-1` (each column is a
/// copy of the left-edge sample at row `i`).
///
/// The spec body collapses to step 11 when `pAngle == 180`, i.e.
/// `mode == H_PRED` (the §6.10.x intra-mode ordinal `2`) and
/// `angleDelta == 0`.
///
/// ## Arguments
///
/// * `w` / `h` — the prediction region's width / height in plane
/// samples. Both in `4..=64`.
/// * `left_col` — `LeftCol[ 0..h-1 ]`. Length must be `>= h`.
/// * `pred` — output buffer, row-major; length must be `>= h * w`.
///
/// ## Returns
///
/// `Ok(())` on success.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (`w > 64`, `h > 64`, `left_col.len() < h`,
/// `pred.len() < h * w`).
pub fn predict_intra_h_pred(
w: usize,
h: usize,
left_col: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
if w > 64 || h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if left_col.len() < h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §7.11.2.4 step 11: each column of the block is filled with a
// copy of `LeftCol[ 0..h-1 ]`. Implemented row-major: row `i`
// gets `LeftCol[i]` repeated `w` times.
for i in 0..h {
let v = left_col[i];
let row = &mut pred[i * w..i * w + w];
for cell in row.iter_mut() {
*cell = v;
}
}
Ok(())
}
// ---------------------------------------------------------------------
// Round 187 — §7.11.2.1 `AboveRow[-1]` / `LeftCol[-1]` corner-sample
// derivation, plus the §7.11.2.2 PAETH_PRED and §7.11.2.6 SMOOTH /
// SMOOTH_V / SMOOTH_H sample-generation leaves.
//
// The §7.11.2.1 prologue (av1-spec p.242) closes its neighbour-array
// derivation with a single corner sample shared between `AboveRow[-1]`
// and `LeftCol[-1]` (the spec text reads: "The array LeftCol[ i ] for
// i = -1 is set equal to AboveRow[ -1 ]"). The four-arm dispatch is:
//
// * `(haveAbove, haveLeft) == (1, 1)` ⇒ `CurrFrame[plane][y-1][x-1]`
// * `(haveAbove, haveLeft) == (1, 0)` ⇒ `CurrFrame[plane][y-1][x]`
// * `(haveAbove, haveLeft) == (0, 1)` ⇒ `CurrFrame[plane][y][x-1]`
// * `(haveAbove, haveLeft) == (0, 0)` ⇒ `1 << (BitDepth - 1)` (the
// symmetric mid-grey value, NOT the `±1`-offset values that arms
// 1 / 2 of the `AboveRow[]` / `LeftCol[]` derivation use).
//
// PAETH_PRED (§7.11.2.2 — "Basic intra prediction process", av1-spec
// p.243) is the only §7.11.2.{2..6} leaf that reads the corner cell.
// SMOOTH / SMOOTH_V / SMOOTH_H (§7.11.2.6) read `LeftCol[h-1]` and
// `AboveRow[w-1]` — the LAST values of the two neighbour arrays (the
// bottom-left and top-right of the extended `0..w+h-1` slots), not the
// corner. The SMOOTH leaves therefore do not consume the corner cell.
//
// PAETH per-cell (i = 0..h-1, j = 0..w-1):
//
// base = AboveRow[j] + LeftCol[i] - AboveRow[-1]
// pLeft = Abs(base - LeftCol[i])
// pTop = Abs(base - AboveRow[j])
// pTopLeft = Abs(base - AboveRow[-1])
// pred[i][j] =
// LeftCol[i] if pLeft <= pTop and pLeft <= pTopLeft
// AboveRow[j] else if pTop <= pTopLeft
// AboveRow[-1] otherwise
//
// SMOOTH_PRED per-cell (i = 0..h-1, j = 0..w-1):
//
// smoothPred = smWeightsY[i] * AboveRow[j]
// + (256 - smWeightsY[i]) * LeftCol[h-1]
// + smWeightsX[j] * LeftCol[i]
// + (256 - smWeightsX[j]) * AboveRow[w-1]
// pred[i][j] = Round2(smoothPred, 9)
//
// SMOOTH_V_PRED per-cell:
//
// smoothPred = smWeights[i] * AboveRow[j]
// + (256 - smWeights[i]) * LeftCol[h-1]
// pred[i][j] = Round2(smoothPred, 8)
//
// SMOOTH_H_PRED per-cell:
//
// smoothPred = smWeights[j] * LeftCol[i]
// + (256 - smWeights[j]) * AboveRow[w-1]
// pred[i][j] = Round2(smoothPred, 8)
//
// `Round2(x, n) = (x + (1 << (n - 1))) >> n` (§3 `Round2`); for
// non-negative `x` (the smooth-prediction sums always are) this is the
// nearest-integer round with ties-to-even-power-of-two break.
//
// The `Sm_Weights_Tx_*` tables are transcribed verbatim from av1-spec
// p.508. Indexed by `log2W - 2` / `log2H - 2`, sizes `2..=6` cover
// `w / h ∈ {4, 8, 16, 32, 64}` (the §3 transform-size set).
// ---------------------------------------------------------------------
/// `SMOOTH_PRED` (§6.10.x intra-mode enumeration) — the first of the
/// three smooth intra modes (`SMOOTH_PRED == 9`, `SMOOTH_V_PRED ==
/// 10`, `SMOOTH_H_PRED == 11`). The sample-generation leaf is
/// [`predict_intra_smooth_pred`]; §7.11.2.6 (av1-spec p.250).
pub const SMOOTH_PRED: usize = 9;
/// `SMOOTH_V_PRED` (§6.10.x intra-mode enumeration). Sample-generation
/// leaf: [`predict_intra_smooth_v_pred`]; §7.11.2.6 second arm
/// (av1-spec p.250-251).
pub const SMOOTH_V_PRED: usize = 10;
/// `SMOOTH_H_PRED` (§6.10.x intra-mode enumeration). Sample-generation
/// leaf: [`predict_intra_smooth_h_pred`]; §7.11.2.6 third arm
/// (av1-spec p.251).
pub const SMOOTH_H_PRED: usize = 11;
/// `PAETH_PRED` (§6.10.x intra-mode enumeration) — the basic intra
/// prediction mode (`PAETH_PRED == 12`, the last Y intra mode before
/// `UV_CFL_PRED == 13` is added for chroma). Sample-generation leaf:
/// [`predict_intra_paeth_pred`]; §7.11.2.2 (av1-spec p.243).
pub const PAETH_PRED: usize = 12;
/// `Sm_Weights_Tx_4x4` (av1-spec p.508) — smooth-prediction weights
/// for the 4-sample side. Used when `log2W == 2` (SMOOTH_PRED x-axis,
/// SMOOTH_H_PRED) or `log2H == 2` (SMOOTH_PRED y-axis, SMOOTH_V_PRED).
pub const SM_WEIGHTS_TX_4X4: [u16; 4] = [255, 149, 85, 64];
/// `Sm_Weights_Tx_8x8` (av1-spec p.508) — smooth-prediction weights
/// for the 8-sample side.
pub const SM_WEIGHTS_TX_8X8: [u16; 8] = [255, 197, 146, 105, 73, 50, 37, 32];
/// `Sm_Weights_Tx_16x16` (av1-spec p.508) — smooth-prediction weights
/// for the 16-sample side.
pub const SM_WEIGHTS_TX_16X16: [u16; 16] = [
255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16,
];
/// `Sm_Weights_Tx_32x32` (av1-spec p.508) — smooth-prediction weights
/// for the 32-sample side.
pub const SM_WEIGHTS_TX_32X32: [u16; 32] = [
255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74, 66, 59, 52, 45,
39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8,
];
/// `Sm_Weights_Tx_64x64` (av1-spec p.508) — smooth-prediction weights
/// for the 64-sample side.
pub const SM_WEIGHTS_TX_64X64: [u16; 64] = [
255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156, 150, 144, 138, 133,
127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69, 65, 61, 57, 54, 50, 47, 44, 41, 38,
35, 32, 29, 27, 25, 22, 20, 18, 16, 15, 13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4,
];
/// Internal dispatch on `log2_n` for the smooth-prediction weight
/// tables. Returns the slice the spec's `smWeightsX` / `smWeightsY` /
/// `smWeights` selector resolves to (`log2_n ∈ 2..=6`).
fn sm_weights_for_log2(log2_n: u32) -> Option<&'static [u16]> {
match log2_n {
2 => Some(&SM_WEIGHTS_TX_4X4),
3 => Some(&SM_WEIGHTS_TX_8X8),
4 => Some(&SM_WEIGHTS_TX_16X16),
5 => Some(&SM_WEIGHTS_TX_32X32),
6 => Some(&SM_WEIGHTS_TX_64X64),
_ => None,
}
}
/// §7.11.2.1 corner-sample derivation (av1-spec p.242) — returns the
/// `AboveRow[-1]` value, which the spec also assigns to `LeftCol[-1]`.
///
/// The four-arm dispatch on `(haveAbove, haveLeft)`:
///
/// * `(1, 1)` ⇒ `CurrFrame[plane][y-1][x-1]`
/// * `(1, 0)` ⇒ `CurrFrame[plane][y-1][x]`
/// * `(0, 1)` ⇒ `CurrFrame[plane][y][x-1]`
/// * `(0, 0)` ⇒ `1 << (BitDepth - 1)` (symmetric mid-grey, NOT the
/// `±1` offsets arms 1 / 2 of `derive_above_row` / `derive_left_col`
/// use).
///
/// ## Arguments
///
/// * `have_above` / `have_left` — §7.11.2.1 inputs.
/// * `x` / `y` — per-plane top-left in `CurrFrame[plane]` sample space.
/// * `curr_frame` — flat row-major `CurrFrame[plane]` buffer.
/// * `curr_frame_cols` — column stride.
/// * `bit_depth` — §5.5.2 bit depth (`8`, `10`, or `12`); used for
/// arm-4 mid-grey fallback.
///
/// ## Returns
///
/// `Ok(sample)` with the derived `AboveRow[-1]` value, clipped to
/// `[0, (1 << BitDepth) - 1]`.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments: `bit_depth ∉ {8, 10, 12}`, `curr_frame_cols == 0`,
/// `(y == 0)` with `have_above == 1`, `(x == 0)` with `have_left == 1
/// && have_above == 0` (arm 3 needs `x-1`), `(x == 0)` with
/// `have_above == 1 && have_left == 1` (arm 1 needs `x-1`), or an
/// `(row, col)` read landing outside `curr_frame`.
#[allow(clippy::too_many_arguments)]
pub fn derive_above_left(
have_above: u8,
have_left: u8,
x: u32,
y: u32,
curr_frame: &[i32],
curr_frame_cols: u32,
bit_depth: u8,
) -> Result<u16, crate::Error> {
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if curr_frame_cols == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if have_above == 0 && have_left == 0 {
// §7.11.2.1 corner arm 4: AboveRow[-1] = 1 << (BitDepth - 1).
return Ok(1u16 << (bit_depth - 1));
}
let (row, col) = if have_above != 0 && have_left != 0 {
// §7.11.2.1 corner arm 1: CurrFrame[plane][y-1][x-1].
if x == 0 || y == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
((y as usize) - 1, (x as usize) - 1)
} else if have_above != 0 {
// §7.11.2.1 corner arm 2: CurrFrame[plane][y-1][x].
if y == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
((y as usize) - 1, x as usize)
} else {
// §7.11.2.1 corner arm 3: CurrFrame[plane][y][x-1].
if x == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
(y as usize, (x as usize) - 1)
};
let cols = curr_frame_cols as usize;
let idx = row
.checked_mul(cols)
.and_then(|p| p.checked_add(col))
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
if idx >= curr_frame.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let max_val = (1i32 << bit_depth) - 1;
Ok(curr_frame[idx].clamp(0, max_val) as u16)
}
/// §7.11.2.2 basic intra prediction process (av1-spec p.243) — fills
/// `pred[ 0..h*w ]` with PAETH_PRED samples.
///
/// Per-cell (`i = 0..h-1`, `j = 0..w-1`):
///
/// ```text
/// base = AboveRow[j] + LeftCol[i] - AboveRow[-1]
/// pLeft = Abs(base - LeftCol[i]) // = |AboveRow[j] - AboveRow[-1]|
/// pTop = Abs(base - AboveRow[j]) // = |LeftCol[i] - AboveRow[-1]|
/// pTopLeft = Abs(base - AboveRow[-1]) // = |AboveRow[j]+LeftCol[i] - 2*AboveRow[-1]|
/// pred[i][j] =
/// LeftCol[i] if pLeft <= pTop && pLeft <= pTopLeft
/// AboveRow[j] else if pTop <= pTopLeft
/// AboveRow[-1] otherwise
/// ```
///
/// The intermediates are computed in `i32` so the `AboveRow[j] +
/// LeftCol[i] - AboveRow[-1]` step is exact (each sample is at most
/// `12` bits, so the sum fits in `i32` with massive headroom; the
/// subsequent `Abs(base - ...)` differences are also in `i32`).
///
/// ## Arguments
///
/// * `w` / `h` — region width / height in plane samples (`4..=64`).
/// * `above_row` — `AboveRow[ 0..w-1 ]`. Length must be `>= w`.
/// * `left_col` — `LeftCol[ 0..h-1 ]`. Length must be `>= h`.
/// * `above_left` — `AboveRow[-1]` (= `LeftCol[-1]`); from
/// [`derive_above_left`].
/// * `pred` — output buffer, row-major; length must be `>= h * w`.
///
/// ## Returns
///
/// `Ok(())` on success.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (`w > 64`, `h > 64`, undersized `above_row` / `left_col`
/// / `pred`).
pub fn predict_intra_paeth_pred(
w: usize,
h: usize,
above_row: &[u16],
left_col: &[u16],
above_left: u16,
pred: &mut [u16],
) -> Result<(), crate::Error> {
if w > 64 || h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if above_row.len() < w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if left_col.len() < h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let above_left_i = above_left as i32;
for i in 0..h {
let left = left_col[i] as i32;
for j in 0..w {
let top = above_row[j] as i32;
// §7.11.2.2 step 1: base = AboveRow[j] + LeftCol[i] -
// AboveRow[-1].
let base = top + left - above_left_i;
// Steps 2-4: absolute deltas to the three candidates.
let p_left = (base - left).abs();
let p_top = (base - top).abs();
let p_top_left = (base - above_left_i).abs();
// Steps 5-7: tie-break in the order the spec specifies
// (LeftCol > AboveRow > AboveRow[-1]).
let v = if p_left <= p_top && p_left <= p_top_left {
left_col[i]
} else if p_top <= p_top_left {
above_row[j]
} else {
above_left
};
pred[i * w + j] = v;
}
}
Ok(())
}
/// §7.11.2.3 recursive intra prediction process (av1-spec p.243-244) —
/// the `use_filter_intra == 1` luma arm of the §7.11.2.1 dispatch.
/// Fills `pred[ 0..h*w ]` by walking each `4×2` output sub-block, building
/// a 7-sample neighbour array `p`, and applying the per-mode
/// [`INTRA_FILTER_TAPS`] kernel + `Round2Signed( ., INTRA_FILTER_SCALE_BITS )`
/// + `Clip1`.
///
/// `above_ext` / `left_ext` are the head-extended edge buffers: offset
/// `1` is the `AboveRow[-1]` / `LeftCol[-1]` corner (the two share a
/// value per §7.11.2.1), and offset `2 + k` is `AboveRow[k]` /
/// `LeftCol[k]` for `k >= 0`. This matches the buffer layout
/// [`PartitionWalker::predict_intra_into_curr_frame`] already builds, so
/// the caller reuses it without re-deriving the neighbours.
///
/// The spec's `AboveRow[ ( j4 << 2 ) + i - 1 ]` index runs from `-1`
/// (the corner) upward; the `+ 2` head-extension offset maps `-1` to
/// offset `1`. `pred` reads of `pred[ ( i2 << 1 ) - 1 ][ ... ]` (the
/// row immediately above the current `4×2` block) read the
/// already-filled `pred` rows for `i2 > 0`, and `LeftCol` for `i2 == 0`,
/// exactly as the spec's branch structure specifies.
pub(crate) fn predict_intra_recursive(
w: usize,
h: usize,
filter_intra_mode: usize,
bit_depth: u8,
above_ext: &[u16],
left_ext: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
if w > 64 || h > 64 || w < 4 || h < 2 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if filter_intra_mode >= INTRA_FILTER_MODES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Head-extended accessors: `AboveRow[k]` / `LeftCol[k]` for `k >= -1`.
// Offset `1` = corner (`k == -1`); offset `2 + k` = sample `k`. The
// §7.11.2.3 walk reads `AboveRow[ ( (w4-1) << 2 ) + 4 - 1 ] =
// AboveRow[w-1]` (offset `w + 1`) and `LeftCol[h-1]` (offset
// `h + 1`) at the far edge.
let need_above = w + 2;
let need_left = h + 2;
if above_ext.len() < need_above || left_ext.len() < need_left {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let above = |k: i32| -> i32 {
let off = (k + 2) as usize;
above_ext.get(off).copied().unwrap_or(0) as i32
};
let left = |k: i32| -> i32 {
let off = (k + 2) as usize;
left_ext.get(off).copied().unwrap_or(0) as i32
};
let taps = &INTRA_FILTER_TAPS[filter_intra_mode];
let w4 = w >> 2;
let h2 = h >> 1;
for i2 in 0..h2 {
for j4 in 0..w4 {
// §7.11.2.3: prepare the 7-sample neighbour array `p`.
let mut p = [0i32; 7];
for (i, pi) in p.iter_mut().enumerate() {
if i < 5 {
if i2 == 0 {
// AboveRow[ ( j4 << 2 ) + i - 1 ].
*pi = above(((j4 << 2) + i) as i32 - 1);
} else if j4 == 0 && i == 0 {
// LeftCol[ ( i2 << 1 ) - 1 ].
*pi = left(((i2 << 1) as i32) - 1);
} else {
// pred[ ( i2 << 1 ) - 1 ][ ( j4 << 2 ) + i - 1 ].
let pr_row = (i2 << 1) - 1;
let pr_col = ((j4 << 2) as i32) + (i as i32) - 1;
*pi = pred[pr_row * w + pr_col as usize] as i32;
}
} else if j4 == 0 {
// LeftCol[ ( i2 << 1 ) + i - 5 ].
*pi = left(((i2 << 1) as i32) + (i as i32) - 5);
} else {
// pred[ ( i2 << 1 ) + i - 5 ][ ( j4 << 2 ) - 1 ].
let pr_row = (i2 << 1) + i - 5;
let pr_col = (j4 << 2) - 1;
*pi = pred[pr_row * w + pr_col] as i32;
}
}
// §7.11.2.3: filter the 4×2 output block.
for i1 in 0..2usize {
for j1 in 0..4usize {
let tap_row = &taps[(i1 << 2) + j1];
let mut acc: i64 = 0;
for i in 0..7usize {
acc += (tap_row[i] as i64) * (p[i] as i64);
}
let v = round2_signed(acc, INTRA_FILTER_SCALE_BITS);
let clipped = v.clamp(0, ((1u32 << bit_depth) - 1) as i64) as u16;
pred[((i2 << 1) + i1) * w + (j4 << 2) + j1] = clipped;
}
}
}
}
Ok(())
}
/// §7.11.2.6 smooth intra prediction process — `SMOOTH_PRED` arm
/// (av1-spec p.250). Fills `pred[ 0..h*w ]` with the 4-tap
/// bidirectional blend:
///
/// ```text
/// smoothPred = smWeightsY[i] * AboveRow[j]
/// + (256 - smWeightsY[i]) * LeftCol[h-1]
/// + smWeightsX[j] * LeftCol[i]
/// + (256 - smWeightsX[j]) * AboveRow[w-1]
/// pred[i][j] = Round2(smoothPred, 9)
/// ```
///
/// `smWeightsX = Sm_Weights_Tx_{1 << log2_w}x...`, indexed by
/// `log2_w ∈ 2..=6`. Likewise `smWeightsY`. The right / bottom edges
/// (`AboveRow[w-1]` / `LeftCol[h-1]`) are the **last entries** of the
/// per-edge neighbour arrays — NOT the corner cell `AboveRow[-1]`
/// (the corner cell is consumed only by `predict_intra_paeth_pred`).
///
/// ## Arguments
///
/// * `log2_w` / `log2_h` — base-2 logs of region width / height
/// (`2..=6`).
/// * `w` / `h` — region width / height in plane samples, with
/// `w == (1 << log2_w)` / `h == (1 << log2_h)`.
/// * `above_row` — `AboveRow[ 0..w-1 ]`. Length must be `>= w`.
/// * `left_col` — `LeftCol[ 0..h-1 ]`. Length must be `>= h`.
/// * `pred` — output buffer, row-major; length must be `>= h * w`.
///
/// ## Returns
///
/// `Ok(())` on success.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (`log2_w / log2_h` outside `2..=6`, `w != (1 << log2_w)`,
/// `h != (1 << log2_h)`, undersized buffers).
#[allow(clippy::too_many_arguments)]
pub fn predict_intra_smooth_pred(
log2_w: u32,
log2_h: u32,
w: usize,
h: usize,
above_row: &[u16],
left_col: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
let sm_w = sm_weights_for_log2(log2_w).ok_or(crate::Error::PartitionWalkOutOfRange)?;
let sm_h = sm_weights_for_log2(log2_h).ok_or(crate::Error::PartitionWalkOutOfRange)?;
if w != (1usize << log2_w) || h != (1usize << log2_h) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if above_row.len() < w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if left_col.len() < h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §7.11.2.6 SMOOTH_PRED constants: `LeftCol[h-1]` and
// `AboveRow[w-1]` — the last entries of the per-edge neighbour
// arrays (the spec's bottom-left / top-right reference samples).
let bottom_left = left_col[h - 1] as u32;
let top_right = above_row[w - 1] as u32;
for i in 0..h {
let wy = sm_h[i] as u32;
let left = left_col[i] as u32;
for j in 0..w {
let wx = sm_w[j] as u32;
let top = above_row[j] as u32;
// §7.11.2.6 SMOOTH_PRED step 3:
// smoothPred = smWeightsY[i] * AboveRow[j]
// + (256 - smWeightsY[i]) * LeftCol[h-1]
// + smWeightsX[j] * LeftCol[i]
// + (256 - smWeightsX[j]) * AboveRow[w-1]
//
// Maxima: each `smWeight*` is `<= 255`; each sample is
// `<= 4095` (12-bit). Each term is then `<= 255 * 4095 <
// 2^20`, and the four-term sum is `< 2^22` — well within
// `u32`.
let smooth_pred =
wy * top + (256 - wy) * bottom_left + wx * left + (256 - wx) * top_right;
// §3 Round2(x, 9) = (x + (1 << 8)) >> 9.
let v = (smooth_pred + (1u32 << 8)) >> 9;
pred[i * w + j] = v as u16;
}
}
Ok(())
}
/// §7.11.2.6 smooth intra prediction process — `SMOOTH_V_PRED` arm
/// (av1-spec p.250-251). Fills `pred[ 0..h*w ]` with the vertical
/// 2-tap blend:
///
/// ```text
/// smoothPred = smWeights[i] * AboveRow[j]
/// + (256 - smWeights[i]) * LeftCol[h-1]
/// pred[i][j] = Round2(smoothPred, 8)
/// ```
///
/// where `smWeights = Sm_Weights_Tx_{1 << log2_h}x...` (per
/// `log2_h ∈ 2..=6`).
pub fn predict_intra_smooth_v_pred(
log2_h: u32,
w: usize,
h: usize,
above_row: &[u16],
left_col: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
let sm = sm_weights_for_log2(log2_h).ok_or(crate::Error::PartitionWalkOutOfRange)?;
if h != (1usize << log2_h) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if w > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if above_row.len() < w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if left_col.len() < h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let bottom_left = left_col[h - 1] as u32;
for i in 0..h {
let wy = sm[i] as u32;
let comp = 256 - wy;
for j in 0..w {
let top = above_row[j] as u32;
let smooth_pred = wy * top + comp * bottom_left;
// §3 Round2(x, 8) = (x + (1 << 7)) >> 8.
let v = (smooth_pred + (1u32 << 7)) >> 8;
pred[i * w + j] = v as u16;
}
}
Ok(())
}
/// §7.11.2.6 smooth intra prediction process — `SMOOTH_H_PRED` arm
/// (av1-spec p.251). Fills `pred[ 0..h*w ]` with the horizontal 2-tap
/// blend:
///
/// ```text
/// smoothPred = smWeights[j] * LeftCol[i]
/// + (256 - smWeights[j]) * AboveRow[w-1]
/// pred[i][j] = Round2(smoothPred, 8)
/// ```
///
/// where `smWeights = Sm_Weights_Tx_{1 << log2_w}x...` (per
/// `log2_w ∈ 2..=6`).
pub fn predict_intra_smooth_h_pred(
log2_w: u32,
w: usize,
h: usize,
above_row: &[u16],
left_col: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
let sm = sm_weights_for_log2(log2_w).ok_or(crate::Error::PartitionWalkOutOfRange)?;
if w != (1usize << log2_w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if above_row.len() < w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if left_col.len() < h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let top_right = above_row[w - 1] as u32;
for i in 0..h {
let left = left_col[i] as u32;
for j in 0..w {
let wx = sm[j] as u32;
let smooth_pred = wx * left + (256 - wx) * top_right;
let v = (smooth_pred + (1u32 << 7)) >> 8;
pred[i * w + j] = v as u16;
}
}
Ok(())
}
// ---------------------------------------------------------------------
// Round 188 — §7.11.2.4 directional intra prediction (the six
// non-degenerate D-mode arms — D45 / D67 / D113 / D135 / D157 / D203)
// + §7.11.2.7 filter corner + §7.11.2.9 intra edge filter strength
// + §7.11.2.10 intra edge upsample selection + §7.11.2.11 intra edge
// upsample + §7.11.2.12 intra edge filter pre-passes. The six D-modes
// share a single sample-generation body keyed off `pAngle`
// (`Mode_To_Angle[ mode ] + angleDelta * ANGLE_STEP` per av1-spec
// p.485 + p.487). The body itself transcribes §7.11.2.4 steps 5..9
// (av1-spec p.246-248) verbatim.
//
// §7.11.2.4 directional projection (steps 5..9):
//
// * step 5 derives `dx` from `Dr_Intra_Derivative[]`:
// - `pAngle < 90` ⇒ dx = Dr_Intra_Derivative[ pAngle ];
// - `90 < pAngle < 180` ⇒ dx = Dr_Intra_Derivative[ 180 - pAngle ];
// - otherwise dx is undefined.
// * step 6 derives `dy`:
// - `90 < pAngle < 180` ⇒ dy = Dr_Intra_Derivative[ pAngle - 90 ];
// - `pAngle > 180` ⇒ dy = Dr_Intra_Derivative[ 270 - pAngle ];
// - otherwise dy is undefined.
// * step 7 (`pAngle < 90`) — D45-like, projects from AboveRow only:
// `idx = (i + 1) * dx`,
// `base = (idx >> (6 - upsampleAbove)) + (j << upsampleAbove)`,
// `shift = ((idx << upsampleAbove) >> 1) & 0x1F`,
// `maxBaseX = (w + h - 1) << upsampleAbove`,
// `pred = base < maxBaseX
// ? Round2(AboveRow[base] * (32 - shift) +
// AboveRow[base + 1] * shift, 5)
// : AboveRow[maxBaseX]`.
// * step 8 (`90 < pAngle < 180`) — D113 / D135 / D157, hybrid: projects
// from AboveRow when `base >= -(1 << upsampleAbove)`; otherwise
// redoes the projection along the Y axis through LeftCol.
// * step 9 (`pAngle > 180`) — D203-like, projects from LeftCol only
// (mirror of step 7).
//
// `ANGLE_STEP = 3` per the §6.10.x intra-mode enumeration (the
// `angle_delta_y` / `angle_delta_uv` are 7-step ranges centred on 0; a
// single delta unit moves `pAngle` by 3°). r188 hard-codes
// `angleDelta = 0` (the §5.11.5 walker has not yet stitched in
// `intra_angle_info`), so `pAngle == Mode_To_Angle[ mode ]` for all six
// D-modes, falling cleanly into one of steps 7 / 8 / 9 per the table:
//
// D45_PRED (3) → 45° → step 7 (above-only projection)
// D135_PRED (4) → 135° → step 8 (hybrid above/left projection)
// D113_PRED (5) → 113° → step 8
// D157_PRED (6) → 157° → step 8
// D203_PRED (7) → 203° → step 9 (left-only projection)
// D67_PRED (8) → 67° → step 7
//
// r188 takes the "no-upsample, no-filter" path through the §7.11.2.4
// step-4 pre-pass: `enable_intra_edge_filter` is read from the parsed
// sequence header but the pre-passes themselves run a no-op when the
// strength / useUpsample selection returns 0. The §7.11.2.{9..11}
// selection / upsample / filter helpers are exposed as standalone
// functions so a future arc can stitch them into the walker; for the
// dispatcher's per-plane task list the `upsampleAbove =
// upsampleLeft = 0` short-circuit covers the §5.11.5 path the walker
// currently emits. (When `useUpsample = 0` step-7..9 collapse the
// `upsampleAbove` shifts to zero and produce the "base" un-upsampled
// projection — the lower-quality but correct shape per the spec.)
//
// Sample-generation kernels are exposed both as a single
// `predict_intra_directional` dispatcher (keyed by `pAngle`) and as
// `predict_intra_d_mode` (keyed by the §6.10.x intra-mode ordinal); the
// latter is the dispatcher's entry point.
// ---------------------------------------------------------------------
/// `D45_PRED` (§6.10.x intra-mode enumeration) — first directional
/// D-mode ordinal. `Mode_To_Angle[ D45_PRED ] == 45` (av1-spec p.485).
/// The §7.11.2.4 sample-generation body routes through step 7
/// (`pAngle < 90` — above-only projection).
pub const D45_PRED: usize = 3;
/// `D135_PRED` (§6.10.x intra-mode enumeration). `Mode_To_Angle[
/// D135_PRED ] == 135`. §7.11.2.4 step 8 (hybrid above/left).
pub const D135_PRED: usize = 4;
/// `D113_PRED` (§6.10.x intra-mode enumeration). `Mode_To_Angle[
/// D113_PRED ] == 113`. §7.11.2.4 step 8.
pub const D113_PRED: usize = 5;
/// `D157_PRED` (§6.10.x intra-mode enumeration). `Mode_To_Angle[
/// D157_PRED ] == 157`. §7.11.2.4 step 8.
pub const D157_PRED: usize = 6;
/// `D203_PRED` (§6.10.x intra-mode enumeration). `Mode_To_Angle[
/// D203_PRED ] == 203`. §7.11.2.4 step 9 (left-only projection,
/// mirror of step 7).
pub const D203_PRED: usize = 7;
// Note: `D67_PRED == 8` is already exported above (the original r180
// boundary marker for `V_PRED <= mode <= D67_PRED` predicate). Its
// `Mode_To_Angle[ D67_PRED ] == 67`. §7.11.2.4 step 7.
/// `ANGLE_STEP` per av1-spec p.485 — the per-unit angular increment
/// each `angle_delta_y` / `angle_delta_uv` step contributes to
/// `pAngle = Mode_To_Angle[ mode ] + angleDelta * ANGLE_STEP`. Three
/// degrees per step; with `angle_delta_y ∈ -3..=3` the per-mode
/// `pAngle` ranges +/-9° around the table entry.
pub const ANGLE_STEP: i32 = 3;
/// `Mode_To_Angle[ INTRA_MODES ]` (av1-spec p.485). Maps each of the
/// thirteen Y intra-mode ordinals to its base directional angle in
/// degrees. Non-directional modes (`DC_PRED == 0`, `SMOOTH_PRED == 9`,
/// `SMOOTH_V_PRED == 10`, `SMOOTH_H_PRED == 11`, `PAETH_PRED == 12`)
/// map to `0`. The seven directional entries are
/// `V_PRED (1) → 90`, `H_PRED (2) → 180`, `D45_PRED (3) → 45`,
/// `D135_PRED (4) → 135`, `D113_PRED (5) → 113`, `D157_PRED (6) → 157`,
/// `D203_PRED (7) → 203`, `D67_PRED (8) → 67`. `V_PRED` (`pAngle ==
/// 90`) and `H_PRED` (`pAngle == 180`) are the §7.11.2.4 step-10 /
/// step-11 degenerate cases (already lifted in r186 by
/// [`predict_intra_v_pred`] / [`predict_intra_h_pred`]).
pub const MODE_TO_ANGLE: [i32; INTRA_MODES] = [0, 90, 180, 45, 135, 113, 157, 203, 67, 0, 0, 0, 0];
/// `Dr_Intra_Derivative[ 90 ]` (av1-spec p.487). Per-angle directional
/// derivative the §7.11.2.4 sample-generation body multiplies by
/// `(i + 1)` (step 7) / `(j + 1)` (step 9) to produce the `idx` value
/// driving the per-cell projection along the directional edge.
///
/// Indexed by one of `pAngle`, `180 - pAngle`, `pAngle - 90`, or
/// `270 - pAngle` per the §7.11.2.4 step-5 / step-6 dispatch. Length
/// is 90 (the spec's `[90]`), with non-zero entries only at the angles
/// the §6.10.x enumeration of `Mode_To_Angle` + `ANGLE_STEP *
/// angle_delta` actually emits (`3°, 6°, 9°, …, 87°`).
///
/// The non-zero indices (i, value) per spec transcription:
/// `(3, 1023)`, `(6, 547)`, `(9, 372)`, `(14, 273)`, `(17, 215)`,
/// `(20, 178)`, `(23, 151)`, `(26, 132)`, `(29, 116)`, `(32, 102)`,
/// `(36, 90)`, `(39, 80)`, `(42, 71)`, `(45, 64)`, `(48, 57)`,
/// `(51, 51)`, `(54, 45)`, `(58, 40)`, `(61, 35)`, `(64, 31)`,
/// `(67, 27)`, `(70, 23)`, `(73, 19)`, `(76, 15)`, `(81, 11)`,
/// `(84, 7)`, `(87, 3)`.
pub const DR_INTRA_DERIVATIVE: [u16; 90] = [
0, 0, 0, 1023, 0, 0, 547, 0, 0, 372, 0, 0, 0, 0, 273, 0, 0, 215, 0, 0, 178, 0, 0, 151, 0, 0,
132, 0, 0, 116, 0, 0, 102, 0, 0, 0, 90, 0, 0, 80, 0, 0, 71, 0, 0, 64, 0, 0, 57, 0, 0, 51, 0, 0,
45, 0, 0, 0, 40, 0, 0, 35, 0, 0, 31, 0, 0, 27, 0, 0, 23, 0, 0, 19, 0, 0, 15, 0, 0, 0, 0, 11, 0,
0, 7, 0, 0, 3, 0, 0,
];
/// `INTRA_EDGE_TAPS = 5` per §7.11.2.12 (av1-spec p.255-256). Per-row
/// width of [`INTRA_EDGE_KERNEL`].
pub const INTRA_EDGE_TAPS: usize = 5;
/// `INTRA_EDGE_KERNELS = 3` per §7.11.2.12 — the three filter strengths
/// `[1, 2, 3]` selected by the §7.11.2.9 strength-selection process.
/// Strength `0` skips the §7.11.2.12 filter entirely.
pub const INTRA_EDGE_KERNELS: usize = 3;
/// `Intra_Edge_Kernel[ INTRA_EDGE_KERNELS ][ INTRA_EDGE_TAPS ]` per
/// av1-spec p.256. The three 5-tap symmetric filters keyed by
/// `strength - 1`. The §7.11.2.12 filter applies
/// `s = Σ_j kernel[strength-1][j] * edge[Clip3(0, sz-1, i - 2 + j)]`
/// then `buf[i - 1] = (s + 8) >> 4` — i.e. `Round2(s, 4)`.
pub const INTRA_EDGE_KERNEL: [[i32; INTRA_EDGE_TAPS]; INTRA_EDGE_KERNELS] =
[[0, 4, 8, 4, 0], [0, 5, 6, 5, 0], [2, 4, 4, 4, 2]];
/// §7.11.2.10 intra edge upsample selection process (av1-spec p.254).
/// Decides whether the §7.11.2.11 2x upsample pre-pass is applied to
/// the above edge (`pAngle - 90` input) or the left edge (`pAngle -
/// 180` input). Output is `1` (apply upsample) or `0` (skip).
///
/// The decision is driven by `|delta|` and `w + h`:
/// * `|delta| <= 0 || |delta| >= 40` ⇒ `0` (no upsample for the
/// near-axis and near-corner angles where the projection step is
/// small enough that the un-upsampled edge suffices).
/// * `filterType == 0`: upsample iff `w + h <= 16`.
/// * `filterType == 1`: upsample iff `w + h <= 8`.
pub fn intra_edge_upsample_selection(w: usize, h: usize, filter_type: u8, delta: i32) -> u8 {
let d = delta.unsigned_abs();
if d == 0 || d >= 40 {
return 0;
}
let blk_wh = w + h;
if filter_type == 0 {
if blk_wh <= 16 {
1
} else {
0
}
} else if blk_wh <= 8 {
1
} else {
0
}
}
/// §7.11.2.9 intra edge filter strength selection (av1-spec p.253). The
/// `delta` argument is `pAngle - 90` (for the above edge) or
/// `pAngle - 180` (for the left edge). Output is in `0..=3`. Strength
/// `0` skips the §7.11.2.12 filter (an early-return path).
pub fn intra_edge_filter_strength_selection(w: usize, h: usize, filter_type: u8, delta: i32) -> u8 {
let d = delta.unsigned_abs();
let blk_wh = w + h;
let mut strength: u8 = 0;
if filter_type == 0 {
if blk_wh <= 8 {
if d >= 56 {
strength = 1;
}
} else if blk_wh <= 16 {
// §7.11.2.9 spec lists `blk_wh <= 12` and `blk_wh <= 16` as
// separate branches with the same body (`d >= 40 ⇒ strength
// = 1`); collapsed into one `<= 16` arm.
if d >= 40 {
strength = 1;
}
} else if blk_wh <= 24 {
if d >= 8 {
strength = 1;
}
if d >= 16 {
strength = 2;
}
if d >= 32 {
strength = 3;
}
} else if blk_wh <= 32 {
strength = 1;
if d >= 4 {
strength = 2;
}
if d >= 32 {
strength = 3;
}
} else {
strength = 3;
}
} else if blk_wh <= 8 {
if d >= 40 {
strength = 1;
}
if d >= 64 {
strength = 2;
}
} else if blk_wh <= 16 {
if d >= 20 {
strength = 1;
}
if d >= 48 {
strength = 2;
}
} else if blk_wh <= 24 {
if d >= 4 {
strength = 3;
}
} else {
strength = 3;
}
strength
}
/// §7.11.2.7 filter corner process (av1-spec p.252). Three-tap filter
/// producing the new value for the top-left corner cell from
/// `LeftCol[0]`, `AboveRow[-1]`, `AboveRow[0]`. The output replaces
/// both `LeftCol[-1]` and `AboveRow[-1]` (the §7.11.2.1 invariant
/// `LeftCol[-1] = AboveRow[-1]` is preserved by the filter — a single
/// scalar is returned).
///
/// `s = LeftCol[0] * 5 + AboveRow[-1] * 6 + AboveRow[0] * 5`, then
/// `Round2(s, 4)`. Inputs are clipped per `BitDepth` on the way in by
/// the §7.11.2.1 derivation helpers; the output is in
/// `[0, (1 << BitDepth) - 1]` because the kernel sums to 16 and the
/// shift is by 4 (so the un-clipped output is already bit-depth-bounded).
pub fn filter_corner(left_col_0: u16, above_left: u16, above_row_0: u16) -> u16 {
let s: u32 = (left_col_0 as u32) * 5 + (above_left as u32) * 6 + (above_row_0 as u32) * 5;
((s + 8) >> 4) as u16
}
/// §7.11.2.11 intra edge upsample process (av1-spec p.255). 2x
/// upsamples `num_px` samples in-place. The buffer's valid range
/// before this call is `-1 .. num_px - 1`; after this call the valid
/// range is `-2 .. 2 * num_px - 2`. The caller is responsible for
/// allocating the buffer with the required head + tail headroom.
///
/// The spec's interpretation of indices `-1`, `-2`, `2 * i - 1`,
/// `2 * i` is carried by a slice that holds the negative slots in the
/// first two cells (offset `0` ↔ index `-2`, offset `1` ↔ index `-1`,
/// offset `2` ↔ index `0`, …).
///
/// `dir` is `0` for the above edge (caller passes `&mut AboveRow[]`)
/// or `1` for the left edge (`&mut LeftCol[]`); the spec routes the
/// `buf` reference accordingly, but with a single-buffer signature the
/// caller passes the appropriate edge.
///
/// `bit_depth` clips each upsampled sample to `[0, (1 << BitDepth) -
/// 1]` per the §7.11.2.11 `Clip1` step.
///
/// `buf` layout: the first `2` slots hold pre-process indices `-2` and
/// `-1`; offset `i + 2` holds pre-process index `i`. Post-process,
/// offset `2 * i + 2` holds index `2 * i` and offset `2 * i + 1` holds
/// index `2 * i - 1`. The buffer must be of length
/// `>= 2 * num_px + 2`. (The §7.11.2.11 spec's `dup[]` intermediate
/// is consumed in-place from the original `buf[-1 .. num_px - 1]`
/// values via a small temporary array.)
///
/// Returns `Err(Error::PartitionWalkOutOfRange)` when `num_px == 0`
/// (the spec requires `num_px >= 1`) or `buf.len() < 2 * num_px + 2`.
pub fn intra_edge_upsample(
num_px: usize,
bit_depth: u8,
buf: &mut [u16],
) -> Result<(), crate::Error> {
if num_px == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(8..=12).contains(&bit_depth) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if buf.len() < 2 * num_px + 2 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Build `dup[ 0 .. num_px + 3 ]` by extending `buf[ -1 .. num_px -
// 1 ]` (offset `1 .. num_px + 1` in our representation) by one
// sample at each end. `dup[ 0 ] = buf[ -1 ]`; `dup[ i + 2 ] = buf[
// i ]` for `i ∈ -1 .. num_px`; `dup[ num_px + 2 ] = buf[ num_px - 1
// ]`. With our offset convention `dup[ k ] = buf_offset[ k + 1 ]`
// for `k ∈ 0..num_px + 1`, then duplicate the last live cell into
// the trailing slot.
let mut dup = vec![0i32; num_px + 3];
dup[0] = buf[1] as i32; // dup[0] = buf[-1]
for i in 0..num_px {
// dup[ i + 2 ] = buf[ i ] (offset i + 2 ↔ index i)
dup[i + 2] = buf[i + 2] as i32;
}
// dup[1] = buf[-1] (= dup[0] per i = -1 iteration of the loop)
dup[1] = buf[1] as i32;
// dup[ num_px + 2 ] = buf[ num_px - 1 ] (tail extension)
dup[num_px + 2] = buf[num_px + 1] as i32;
// `buf[ -2 ] = dup[ 0 ]` (per spec head extension; index -2 in our
// offset convention is offset 0).
buf[0] = dup[0] as u16;
let max_val: u32 = (1u32 << bit_depth) - 1;
for i in 0..num_px {
let s_raw = -dup[i] + 9 * dup[i + 1] + 9 * dup[i + 2] - dup[i + 3];
// Round2(s, 4) — the spec's symmetric rounding for a signed `s`
// is `(s + 8) >> 4` when `s >= 0`; for negative `s` we mirror
// the rounding direction. Use `round2_signed_local` to keep
// the symmetric semantics.
let rounded = if s_raw >= 0 {
(s_raw + 8) >> 4
} else {
-(((-s_raw) + 8) >> 4)
};
let clipped = if rounded < 0 {
0u32
} else if (rounded as u32) > max_val {
max_val
} else {
rounded as u32
};
// buf[ 2 * i - 1 ] ↔ offset (2 * i - 1) + 2 = 2 * i + 1.
buf[2 * i + 1] = clipped as u16;
// buf[ 2 * i ] = dup[ i + 2 ] (= the original buf[ i ]).
let copy_in = dup[i + 2] as u32;
let copy_clipped = if copy_in > max_val { max_val } else { copy_in };
buf[2 * i + 2] = copy_clipped as u16;
}
Ok(())
}
/// §7.11.2.12 intra edge filter process (av1-spec p.255-256). Applies
/// the `Intra_Edge_Kernel[ strength - 1 ]` 5-tap kernel to the edge
/// samples (`AboveRow[]` when `left == 0`, `LeftCol[]` when `left ==
/// 1`).
///
/// `sz` is the §7.11.2.4 `numPx` value the caller derived (always
/// `<= 129`). `strength` is in `0..=3` (the §7.11.2.9 strength
/// selection output); `0` is a no-op early-return.
///
/// The buffer layout matches [`intra_edge_upsample`]: offset `0`
/// holds pre-process index `-2`, offset `1` holds index `-1`, offset
/// `i + 2` holds index `i`. The spec's `edge[ i ]` for `i ∈ 0 .. sz -
/// 1` is `buf[ i - 1 ]` (offset `i + 1`); the output `buf[ i - 1 ]
/// = (s + 8) >> 4` writes back to the same offsets `1 .. sz`.
///
/// Returns `Err(Error::PartitionWalkOutOfRange)` for caller-bug args
/// (`sz > 129` per the spec's `sz <= 129` bound, `strength > 3`,
/// `buf.len() < sz + 1`).
pub fn intra_edge_filter(sz: usize, strength: u8, buf: &mut [u16]) -> Result<(), crate::Error> {
if sz > 129 || strength > 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if buf.len() < sz + 1 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if strength == 0 || sz == 0 {
return Ok(());
}
let kernel = &INTRA_EDGE_KERNEL[(strength - 1) as usize];
// Snapshot `edge[ i ] = buf[ i - 1 ]` for `i ∈ 0..sz`. Output
// writes overwrite the same cells, so we need a read-side copy.
let mut edge = vec![0i32; sz];
for (i, slot) in edge.iter_mut().enumerate().take(sz) {
// edge[ i ] = buf[ i - 1 ] ↔ offset (i - 1) + 2 = i + 1.
*slot = buf[i + 1] as i32;
}
for i in 1..sz {
let mut s: i32 = 0;
for (j, &kj) in kernel.iter().enumerate().take(INTRA_EDGE_TAPS) {
// k = Clip3( 0, sz - 1, i - 2 + j ).
let k_raw = i as i32 - 2 + j as i32;
let k = if k_raw < 0 {
0
} else if k_raw > (sz as i32 - 1) {
sz - 1
} else {
k_raw as usize
};
s += kj * edge[k];
}
let out = (s + 8) >> 4;
let clipped = if out < 0 { 0 } else { out };
// Output writes to buf[ i - 1 ] (offset i + 1).
buf[i + 1] = clipped as u16;
}
Ok(())
}
/// §7.11.2.4 directional intra prediction process (av1-spec p.245-248)
/// — the unified body covering the six non-degenerate D-modes (D45 /
/// D67 / D113 / D135 / D157 / D203). Routes to one of step 7 / step 8
/// / step 9 keyed by `p_angle`. The two degenerate cases (`p_angle ==
/// 90` ⇒ V_PRED, `p_angle == 180` ⇒ H_PRED) are handled by the
/// dedicated [`predict_intra_v_pred`] / [`predict_intra_h_pred`]
/// leaves (r186) and rejected here as caller-bug.
///
/// ## Arguments
///
/// * `w` / `h` — prediction-region dimensions in plane samples; both
/// in `4..=64`.
/// * `p_angle` — the directional angle in degrees per
/// `pAngle = MODE_TO_ANGLE[ mode ] + angleDelta * ANGLE_STEP`.
/// Must be one of the §7.11.2.4 non-degenerate values: `pAngle <
/// 90`, `90 < pAngle < 180`, or `pAngle > 180` (with `0 <= pAngle <
/// 360`). `pAngle == 90` / `pAngle == 180` is caller-bug.
/// * `upsample_above` / `upsample_left` — `0` (the §7.11.2.11
/// 2x-upsample pre-pass was not applied) or `1` (it was applied).
/// The r188 walker integration runs the no-upsample path (`0` /
/// `0`); future arcs can stitch the pre-pass into the dispatcher and
/// pass `1` on the small-block / steep-angle subset selected by
/// [`intra_edge_upsample_selection`].
/// * `above_row` — `AboveRow[ 0 .. (w + h) * (1 << upsample_above) -
/// 1 ]`. The `(w + h)` extent comes from the §7.11.2.1 derivation
/// (`AboveRow[]` is sized `w + h` plus the `-1` corner). After
/// upsampling the length doubles plus the two-slot head extension.
/// * `left_col` — `LeftCol[ 0 .. (w + h) * (1 << upsample_left) - 1 ]`.
/// * `pred` — output buffer; length must be `>= h * w`.
///
/// Buffer layout for `above_row` / `left_col` mirrors
/// [`intra_edge_upsample`]: offset `0` holds index `-2`, offset `1`
/// holds index `-1`, offset `i + 2` holds index `i`. When `upsample_*
/// == 0` only offset `1 .. w + h + 1` is read (the index `-1` slot
/// holds the §7.11.2.1 corner cell).
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments.
#[allow(clippy::too_many_arguments)]
pub fn predict_intra_directional(
w: usize,
h: usize,
p_angle: i32,
upsample_above: u8,
upsample_left: u8,
above_row: &[u16],
left_col: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
if w == 0 || h == 0 || w > 64 || h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if upsample_above > 1 || upsample_left > 1 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(0..360).contains(&p_angle) || p_angle == 90 || p_angle == 180 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §7.11.2.4 step 5: `dx`.
let dx: i32 = if p_angle < 90 {
DR_INTRA_DERIVATIVE[p_angle as usize] as i32
} else if p_angle < 180 {
DR_INTRA_DERIVATIVE[(180 - p_angle) as usize] as i32
} else {
0 // undefined per spec; not read on the step-9 path.
};
// §7.11.2.4 step 6: `dy`.
let dy: i32 = if (90..180).contains(&p_angle) {
DR_INTRA_DERIVATIVE[(p_angle - 90) as usize] as i32
} else if p_angle > 180 {
DR_INTRA_DERIVATIVE[(270 - p_angle) as usize] as i32
} else {
0 // undefined per spec; not read on the step-7 path.
};
// Offsets into the head-extended buffers. Index `k` (spec) ↔ offset
// `k + 2` (our representation). Index `-1` ↔ offset `1`; index `-2`
// ↔ offset `0`. `above[ k ]` reads `above_row[ k + 2 ]`, but we
// guard the negative range carefully.
let read_above = |k: i32| -> Result<u16, crate::Error> {
let off = k + 2;
if off < 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let o = off as usize;
if o >= above_row.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
Ok(above_row[o])
};
let read_left = |k: i32| -> Result<u16, crate::Error> {
let off = k + 2;
if off < 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let o = off as usize;
if o >= left_col.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
Ok(left_col[o])
};
if p_angle < 90 {
// §7.11.2.4 step 7 — D45 / D67 family.
let max_base_x: i32 = ((w + h - 1) << upsample_above) as i32;
for i in 0..h {
for j in 0..w {
let idx = (i as i32 + 1) * dx;
let base = (idx >> (6 - upsample_above as i32)) + ((j as i32) << upsample_above);
let shift = ((idx << upsample_above as i32) >> 1) & 0x1F;
let pv: u32 = if base < max_base_x {
let a0 = read_above(base)? as u32;
let a1 = read_above(base + 1)? as u32;
let s = a0 * (32 - shift as u32) + a1 * (shift as u32);
(s + 16) >> 5
} else {
read_above(max_base_x)? as u32
};
pred[i * w + j] = pv as u16;
}
}
} else if p_angle < 180 {
// §7.11.2.4 step 8 — D113 / D135 / D157 (hybrid). Note step 8
// does NOT define a `max_base_x` short-circuit; the spec gates
// on `base >= -(1 << upsampleAbove)` ⇒ read AboveRow; else
// re-project along Y through LeftCol.
for i in 0..h {
for j in 0..w {
let idx_x = ((j as i32) << 6) - (i as i32 + 1) * dx;
let base_x = idx_x >> (6 - upsample_above as i32);
let lower_bound: i32 = -(1 << upsample_above as i32);
if base_x >= lower_bound {
let shift = ((idx_x << upsample_above as i32) >> 1) & 0x1F;
let a0 = read_above(base_x)? as u32;
let a1 = read_above(base_x + 1)? as u32;
let s = a0 * (32 - shift as u32) + a1 * (shift as u32);
pred[i * w + j] = ((s + 16) >> 5) as u16;
} else {
let idx_y = ((i as i32) << 6) - (j as i32 + 1) * dy;
let base_y = idx_y >> (6 - upsample_left as i32);
let shift = ((idx_y << upsample_left as i32) >> 1) & 0x1F;
let l0 = read_left(base_y)? as u32;
let l1 = read_left(base_y + 1)? as u32;
let s = l0 * (32 - shift as u32) + l1 * (shift as u32);
pred[i * w + j] = ((s + 16) >> 5) as u16;
}
}
}
} else {
// §7.11.2.4 step 9 — D203 family (mirror of step 7 along the
// left edge).
let max_base_y: i32 = ((w + h - 1) << upsample_left) as i32;
for i in 0..h {
for j in 0..w {
let idx = (j as i32 + 1) * dy;
let base = (idx >> (6 - upsample_left as i32)) + ((i as i32) << upsample_left);
let shift = ((idx << upsample_left as i32) >> 1) & 0x1F;
let pv: u32 = if base < max_base_y {
let l0 = read_left(base)? as u32;
let l1 = read_left(base + 1)? as u32;
let s = l0 * (32 - shift as u32) + l1 * (shift as u32);
(s + 16) >> 5
} else {
read_left(max_base_y)? as u32
};
pred[i * w + j] = pv as u16;
}
}
}
Ok(())
}
/// §7.11.2.4 directional intra prediction keyed by the §6.10.x
/// intra-mode ordinal (`D45_PRED == 3`, `D135_PRED == 4`, `D113_PRED
/// == 5`, `D157_PRED == 6`, `D203_PRED == 7`, `D67_PRED == 8`). Looks
/// up `Mode_To_Angle[ mode ]`, applies `pAngle = MODE_TO_ANGLE[ mode ]
/// + angle_delta * ANGLE_STEP`, then delegates to
/// [`predict_intra_directional`].
///
/// Caller-bug guards: rejects modes outside `3..=8` (the non-degenerate
/// D-mode range) — the degenerate `V_PRED` / `H_PRED` cases route
/// through [`predict_intra_v_pred`] / [`predict_intra_h_pred`].
#[allow(clippy::too_many_arguments)]
pub fn predict_intra_d_mode(
mode: usize,
angle_delta: i32,
w: usize,
h: usize,
upsample_above: u8,
upsample_left: u8,
above_row: &[u16],
left_col: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
if !(D45_PRED..=D67_PRED).contains(&mode) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(-3..=3).contains(&angle_delta) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let p_angle = MODE_TO_ANGLE[mode] + angle_delta * ANGLE_STEP;
predict_intra_directional(
w,
h,
p_angle,
upsample_above,
upsample_left,
above_row,
left_col,
pred,
)
}
// ---------------------------------------------------------------------
// Round 149 — §5.11.49 caller-side argument derivation.
// Computes the four `palette_tokens_plane` size arguments
// (`block_w`, `block_h`, `onscreen_w`, `onscreen_h`) from the parser-
// scope variables (`MiSize`, `MiRow`, `MiCol`, `MiRows`, `MiCols`,
// `subsampling_{x,y}`) using only the §9.3 block-size tables staged
// in r148 and the §5.11.49 spec text (av1-spec p.101-102). Bridges the
// data-flow gap pinned by the r147 follow-up test
// `palette_tokens_args_from_mi_size_pins_data_flow`: the r147 walker
// took fully-derived sizes; this round stages the derivation itself so
// `read_block` can call `palette_tokens` directly once the parser
// surfaces the variables.
// ---------------------------------------------------------------------
/// `BLOCK_8X8` (§3 / §9.3 enumeration) — the smallest `MiSize` whose
/// block is `8` luma samples on a side. Numerically `3` in the spec's
/// `BLOCK_SIZES` enumeration (`BLOCK_4X4 = 0`, `BLOCK_4X8 = 1`,
/// `BLOCK_8X4 = 2`, `BLOCK_8X8 = 3`); this matches the entry-`3` row
/// of every §9.3 table (`MI_WIDTH_LOG2[3] = MI_HEIGHT_LOG2[3] = 1`,
/// `NUM_4X4_BLOCKS_WIDE[3] = NUM_4X4_BLOCKS_HIGH[3] = 2`,
/// `block_width(3) = block_height(3) = 8`).
///
/// Used as the §5.11.46 palette gate (`MiSize >= BLOCK_8X8 &&
/// Block_Width[MiSize] <= 64 && Block_Height[MiSize] <= 64`) — both
/// `palette_mode_info` call sites (av1-spec p.4171 / p.4605) read it.
/// The `>=` comparison is on the spec enum's numeric ordering, so the
/// gate also admits the extended block sizes (`BLOCK_4X16` through
/// `BLOCK_64X16`, indices 16..=21); the per-plane `<4`-bump branch in
/// §5.11.49 catches the post-subsampling dimensions that the extended
/// sizes can drop below `4`.
pub const BLOCK_8X8: usize = 3;
/// §5.11.49 palette-tokens size arguments — the four dimensions a
/// conformant `palette_tokens( )` caller passes to
/// [`palette_tokens_plane`] for one plane.
///
/// Derived by [`palette_tokens_args`] from the parser-scope variables
/// per §5.11.49:
///
/// ```text
/// blockHeight = Block_Height[ MiSize ]
/// blockWidth = Block_Width[ MiSize ]
/// onscreenHeight = Min( blockHeight, (MiRows - MiRow) * MI_SIZE )
/// onscreenWidth = Min( blockWidth, (MiCols - MiCol) * MI_SIZE )
/// ```
///
/// For the UV plane the spec then applies the §5.11.49 subsampling +
/// `<4`-bump adjustment:
///
/// ```text
/// blockHeight >>= subsampling_y
/// blockWidth >>= subsampling_x
/// onscreenHeight >>= subsampling_y
/// onscreenWidth >>= subsampling_x
/// if ( blockWidth < 4 ) { blockWidth += 2; onscreenWidth += 2 }
/// if ( blockHeight < 4 ) { blockHeight += 2; onscreenHeight += 2 }
/// ```
///
/// The fields satisfy `1 <= onscreen_w <= block_w` and
/// `1 <= onscreen_h <= block_h` (both equal-bound when the block sits
/// entirely on-screen; strict-less when the block straddles the
/// frame's right / bottom edge). The bump preserves these invariants
/// because it adds the same `2` to both `block_*` and `onscreen_*`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PaletteTokensArgs {
/// `blockWidth` (post-subsampling, post-bump if applicable).
pub block_w: usize,
/// `blockHeight` (post-subsampling, post-bump if applicable).
pub block_h: usize,
/// `onscreenWidth` (post-subsampling, post-bump if applicable).
pub onscreen_w: usize,
/// `onscreenHeight` (post-subsampling, post-bump if applicable).
pub onscreen_h: usize,
}
/// §5.11.49 caller-side derivation: computes the four
/// [`PaletteTokensArgs`] dimensions from the parser-scope variables.
///
/// Parameters:
///
/// * `mi_size` — the §5.11.x `MiSize` (a `BLOCK_SIZES` index). The
/// helper returns `None` if the §5.11.46 palette gate would have
/// rejected this block — specifically when `mi_size < BLOCK_8X8` or
/// when `Block_Width[mi_size] > 64` or `Block_Height[mi_size] > 64`.
/// A conformant caller has already evaluated the gate, but returning
/// `None` keeps the helper safe to call defensively.
/// * `mi_row` / `mi_col` — the block's top-left position in the
/// mode-info grid (`0..MiRows`, `0..MiCols`).
/// * `mi_rows` / `mi_cols` — the frame's mode-info grid size per
/// §5.9.9 (`MiRows = 2 * ((FrameHeight + 7) >> 3)`, similarly for
/// `MiCols`). Both are positive for a frame with any decode work.
/// * `plane` — see [`PalettePlane`]. The Y branch returns the four
/// `Block_*` / `onscreen*` values as-is; the UV branch applies the
/// §5.11.49 `>> subsampling_{x,y}` + `<4`-bump adjustment.
/// * `subsampling_x` / `subsampling_y` — the §5.7.2 `subsampling_*`
/// flags (each `0` or `1`). Ignored on the Y path.
///
/// Returns `None` when:
///
/// * `mi_size >= BLOCK_SIZES` (caller bug, would index out of the
/// §9.3 tables).
/// * `mi_size < BLOCK_8X8` or `Block_Width[mi_size] > 64` or
/// `Block_Height[mi_size] > 64` (palette gate violation).
/// * `mi_row >= mi_rows` or `mi_col >= mi_cols` or `mi_rows == 0`
/// or `mi_cols == 0` (caller bug, would produce a zero on-screen
/// dimension).
/// * `subsampling_x > 1` or `subsampling_y > 1` (only the values `0`
/// and `1` are defined by §5.7.2).
///
/// On success the returned [`PaletteTokensArgs`] feeds the
/// [`palette_tokens_plane`] caller for the matching plane verbatim.
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn palette_tokens_args(
mi_size: usize,
mi_row: usize,
mi_col: usize,
mi_rows: usize,
mi_cols: usize,
plane: PalettePlane,
subsampling_x: usize,
subsampling_y: usize,
) -> Option<PaletteTokensArgs> {
if mi_size >= BLOCK_SIZES {
return None;
}
if mi_size < BLOCK_8X8 {
return None;
}
if mi_rows == 0 || mi_cols == 0 {
return None;
}
if mi_row >= mi_rows || mi_col >= mi_cols {
return None;
}
if subsampling_x > 1 || subsampling_y > 1 {
return None;
}
// §5.11.49 base derivation, identical for both planes.
let block_w_luma = block_width(mi_size);
let block_h_luma = block_height(mi_size);
// §5.11.46 palette gate: both block dimensions in luma samples must
// be <= 64. Belt-and-braces with `mi_size < BLOCK_SIZES`.
if block_w_luma > 64 || block_h_luma > 64 {
return None;
}
// (MiRows - MiRow) * MI_SIZE and (MiCols - MiCol) * MI_SIZE are
// both >= MI_SIZE = 4 here, since the bounds-check above guarantees
// mi_row < mi_rows and mi_col < mi_cols.
let rem_rows_samples = (mi_rows - mi_row) * MI_SIZE;
let rem_cols_samples = (mi_cols - mi_col) * MI_SIZE;
let onscreen_h_luma = core::cmp::min(block_h_luma, rem_rows_samples);
let onscreen_w_luma = core::cmp::min(block_w_luma, rem_cols_samples);
let args = match plane {
PalettePlane::Y => PaletteTokensArgs {
block_w: block_w_luma,
block_h: block_h_luma,
onscreen_w: onscreen_w_luma,
onscreen_h: onscreen_h_luma,
},
PalettePlane::Uv => {
// §5.11.49 UV branch: shift both block_* and onscreen_*
// by the subsampling factor, then apply the `<4`-bump.
let mut block_w = block_w_luma >> subsampling_x;
let mut block_h = block_h_luma >> subsampling_y;
let mut onscreen_w = onscreen_w_luma >> subsampling_x;
let mut onscreen_h = onscreen_h_luma >> subsampling_y;
if block_w < 4 {
block_w += 2;
onscreen_w += 2;
}
if block_h < 4 {
block_h += 2;
onscreen_h += 2;
}
PaletteTokensArgs {
block_w,
block_h,
onscreen_w,
onscreen_h,
}
}
};
Some(args)
}
/// `DIRECTIONAL_MODES` (§3) — number of directional intra modes, i.e. the
/// span of `YMode` / `UVMode` values from `V_PRED` to `D67_PRED`
/// inclusive. Drives the outer dimension of [`DEFAULT_ANGLE_DELTA_CDF`].
/// The §8.3.2 `angle_delta_y` / `angle_delta_uv` selections index that
/// table by `YMode - V_PRED` / `UVMode - V_PRED`, a value in
/// `0..DIRECTIONAL_MODES`.
pub const DIRECTIONAL_MODES: usize = 8;
/// `MAX_ANGLE_DELTA` (§3) — maximum magnitude of `AngleDeltaY` and
/// `AngleDeltaUV`. The coded `angle_delta_y` / `angle_delta_uv` syntax
/// elements are biased by `MAX_ANGLE_DELTA` so as to encode a positive
/// value, so they range over `0..(2 * MAX_ANGLE_DELTA + 1)`; that span
/// (`2 * MAX_ANGLE_DELTA + 1 == 7`) is the cumulative-frequency width of
/// [`DEFAULT_ANGLE_DELTA_CDF`].
pub const MAX_ANGLE_DELTA: usize = 3;
/// `DC_PRED` (§6.10.x intra-mode enumeration) — the zero ordinal of
/// the §3 intra-mode enumeration (`DC_PRED == 0`, `V_PRED == 1`,
/// `H_PRED == 2`, …, `PAETH_PRED == 12`). The §7.11.2.5 DC
/// intra-prediction process leaf is [`predict_intra_dc_pred`]; the
/// §5.11.33 `compute_prediction` dispatcher routes a plane with
/// `mode == DC_PRED` to it.
pub const DC_PRED: usize = 0;
/// `V_PRED` (§6.10.x intra-mode enumeration) — the first directional
/// intra mode (`DC_PRED == 0`, `V_PRED == 1`, …, `D67_PRED == 8`). The
/// §8.3.2 `angle_delta` selections rebase a directional `YMode` / `UVMode`
/// onto `0..DIRECTIONAL_MODES` by subtracting this value. The
/// §7.11.2.4 step-10 sample-generation leaf is
/// [`predict_intra_v_pred`].
pub const V_PRED: usize = 1;
/// `H_PRED` (§6.10.x intra-mode enumeration) — the horizontal
/// directional intra mode (`DC_PRED == 0`, `V_PRED == 1`,
/// `H_PRED == 2`). `Mode_To_Angle[H_PRED] == 180`, so the §7.11.2.4
/// directional process collapses to step 11 on `angleDelta == 0`.
/// The sample-generation leaf is [`predict_intra_h_pred`].
pub const H_PRED: usize = 2;
/// `D67_PRED` (§6.10.x intra-mode enumeration) — the last directional
/// intra mode. §5.11.44 `is_directional_mode(mode)` returns `1` for
/// `V_PRED <= mode <= D67_PRED`.
pub const D67_PRED: usize = 8;
/// `UV_CFL_PRED` (§3 / §6.10.x chroma-mode enumeration) — the
/// chroma-from-luma intra mode. Numerically `13` in the §3 enumeration
/// (one past the largest Y intra mode `PAETH_PRED = 12`). The §5.11.22
/// `if ( UVMode == UV_CFL_PRED )` arm gates the §5.11.45
/// `read_cfl_alphas` reader; the §8.3.2 `uv_mode` CFL-allowed CDF
/// (`Default_Uv_Mode_Cfl_Allowed_Cdf`) is the only row carrying a
/// non-zero probability for this symbol.
pub const UV_CFL_PRED: usize = 13;
/// `INTERINTRA_MODES` (§3 / §6.10.27) — number of values for
/// `interintra_mode` (`II_DC_PRED = 0`, `II_V_PRED = 1`, `II_H_PRED = 2`,
/// `II_SMOOTH_PRED = 3`). Drives the row width of
/// [`DEFAULT_INTER_INTRA_MODE_CDF`] (`INTERINTRA_MODES + 1` cumulative
/// frequencies + the §8.3 adaptation counter). The §5.11.28
/// `read_interintra_mode` syntax gate restricts coded blocks to the
/// `BLOCK_8X8`..`BLOCK_32X32` band (per §6.10.27), where `Size_Group[
/// MiSize ]` evaluates to `1`, `2`, or `3` — hence the `[
/// BLOCK_SIZE_GROUPS - 1 ]` outer dimension on the matching default
/// CDFs.
pub const INTERINTRA_MODES: usize = 4;
/// `II_DC_PRED` (§6.10.27) — `interintra_mode` ordinal `0`. Maps to
/// `DC_PRED` via the §7.11.5 `interintra_mode → mode` translation
/// table (av1-spec p.176). Consumed by the §5.11.28
/// `read_interintra_mode` `interintra_mode` S() when `interintra == 1`.
pub const II_DC_PRED: u8 = 0;
/// `II_V_PRED` (§6.10.27) — `interintra_mode` ordinal `1`. Maps to
/// `V_PRED`.
pub const II_V_PRED: u8 = 1;
/// `II_H_PRED` (§6.10.27) — `interintra_mode` ordinal `2`. Maps to
/// `H_PRED`.
pub const II_H_PRED: u8 = 2;
/// `II_SMOOTH_PRED` (§6.10.27) — `interintra_mode` ordinal `3`. Maps
/// to `SMOOTH_PRED`.
pub const II_SMOOTH_PRED: u8 = 3;
/// `WEDGE_TYPES` (§3) — number of directions for the wedge mask process
/// (the spec text reads: *"Number of directions for the wedge mask
/// process"*). Drives the symbol axis of [`DEFAULT_WEDGE_INDEX_CDF`]
/// (`WEDGE_TYPES + 1` cumulative frequencies + the §8.3 adaptation
/// counter). `wedge_index` is read by the §5.11.28
/// `read_interintra_mode` (the inter-intra wedge sub-branch, when
/// `wedge_interintra == 1`) and by the §5.11.29 `read_compound_type`
/// (the inter-inter `COMPOUND_WEDGE` branch, when
/// `compound_type == COMPOUND_WEDGE`) syntax elements; the same default
/// CDF is selected by both call sites per §8.3.2 (the cdf is
/// `TileWedgeIndexCdf[ MiSize ]`).
pub const WEDGE_TYPES: usize = 16;
/// `Wedge_Bits[ BLOCK_SIZES ]` (§3 table, av1-spec p.470). The
/// per-`MiSize` count of bits associated with a wedge mask: rows where
/// `Wedge_Bits[ MiSize ] > 0` are the only ones for which the §5.11.29
/// `read_compound_type` inner branch is allowed to code
/// `compound_type` via the `TileCompoundTypeCdf[ MiSize ]` S(). The
/// `n == 0` rows force `compound_type = COMPOUND_DIFFWTD` (no S()
/// read), per the spec body `if ( n == 0 ) compound_type =
/// COMPOUND_DIFFWTD else compound_type S()`. The non-zero positions
/// (indices 3..=9 and 18..=19) coincide with the §9.4
/// `Default_Wedge_Index_Cdf` non-placeholder rows.
pub const WEDGE_BITS: [u8; BLOCK_SIZES] = [
0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0,
];
/// `wedge_bits( mi_size )` — §3 `Wedge_Bits[ MiSize ]` accessor.
/// Returns `0` for `mi_size >= BLOCK_SIZES` (a caller bug; the §5.11.29
/// `read_compound_type` outer gate guards against this earlier in the
/// dispatcher).
#[inline]
#[must_use]
pub const fn wedge_bits(mi_size: usize) -> u8 {
if mi_size < BLOCK_SIZES {
WEDGE_BITS[mi_size]
} else {
0
}
}
/// §6.10.24 `compound_type` enumeration ordinals (av1-spec p.470).
/// Only `COMPOUND_WEDGE` and `COMPOUND_DIFFWTD` are signalled by the
/// `compound_type` S() (`COMPOUND_TYPES = 2`); `COMPOUND_AVERAGE` /
/// `COMPOUND_INTRA` / `COMPOUND_DISTANCE` are inferred from the
/// surrounding §5.11.29 body per the spec note.
pub const COMPOUND_WEDGE: u8 = 0;
/// §6.10.24 ordinal — see [`COMPOUND_WEDGE`].
pub const COMPOUND_DIFFWTD: u8 = 1;
/// §6.10.24 ordinal — see [`COMPOUND_WEDGE`].
pub const COMPOUND_AVERAGE: u8 = 2;
/// §6.10.24 ordinal — see [`COMPOUND_WEDGE`]. Reached only via the
/// §5.11.29 `interintra && wedge_interintra == 0` else-arm assignment
/// `compound_type = COMPOUND_INTRA`.
pub const COMPOUND_INTRA: u8 = 3;
/// §6.10.24 ordinal — see [`COMPOUND_WEDGE`]. Reached only via the
/// §5.11.29 `comp_group_idx == 0 && enable_jnt_comp` arm's
/// `compound_idx ? COMPOUND_AVERAGE : COMPOUND_DISTANCE` assignment.
pub const COMPOUND_DISTANCE: u8 = 4;
/// §6.10.24 `mask_type` enumeration ordinals (av1-spec p.470). Coded
/// as `mask_type L(1)` on the §5.11.29 `compound_type == COMPOUND_DIFFWTD`
/// arm. `UNIFORM_45` is the default mask direction; `UNIFORM_45_INV`
/// is the inverted variant — see §7.11.3.13 (the difference-weight
/// mask process).
pub const UNIFORM_45: u8 = 0;
/// §6.10.24 ordinal — see [`UNIFORM_45`].
pub const UNIFORM_45_INV: u8 = 1;
// ---------------------------------------------------------------------
// Round 136 — coefficient-token entry constants (§3). These drive the
// outer/inner dimensions of the `init_coeff_cdfs` entry sub-group:
// `Default_Txb_Skip_Cdf` + `Default_Eob_Pt_*_Cdf` + `Default_Eob_Extra_Cdf`
// + `Default_Dc_Sign_Cdf` (§9.4). `PLANE_TYPES` / `TX_SIZES` are shared
// with later coeff_base/coeff_br tables (deferred to a later round).
// ---------------------------------------------------------------------
/// `PLANE_TYPES` (§3) — number of different plane types (luma or chroma).
/// Drives the per-plane axis of the EOB-position / DC-sign coefficient
/// CDFs (`Default_Eob_Pt_*_Cdf`, `Default_Eob_Extra_Cdf`,
/// `Default_Dc_Sign_Cdf`).
pub const PLANE_TYPES: usize = 2;
/// `COEFF_CDF_Q_CTXS` (§3) — number of selectable context types for the
/// `coeff( )` syntax structure. This is the outermost dimension of every
/// `init_coeff_cdfs` default table; the working `Tile*Cdf` copy drops it,
/// selecting `Default_*_Cdf[ idx ]` where `idx` is derived from
/// `base_q_idx` (§8.3.1 `init_coeff_cdfs`). See [`coeff_cdf_q_ctx`].
pub const COEFF_CDF_Q_CTXS: usize = 4;
/// `TXB_SKIP_CONTEXTS` (§3) — number of contexts for `all_zero` (the
/// transform-block skip flag). Indexes the inner axis of
/// [`DEFAULT_TXB_SKIP_CDF`].
pub const TXB_SKIP_CONTEXTS: usize = 13;
/// `EOB_COEF_CONTEXTS` (§3) — number of contexts for `eob_extra`. Indexes
/// the innermost context axis of [`DEFAULT_EOB_EXTRA_CDF`].
pub const EOB_COEF_CONTEXTS: usize = 9;
/// `DC_SIGN_CONTEXTS` (§3) — number of contexts for `dc_sign`. Indexes the
/// inner axis of [`DEFAULT_DC_SIGN_CDF`].
pub const DC_SIGN_CONTEXTS: usize = 3;
/// `SIG_COEF_CONTEXTS_EOB` (§3) — number of contexts for `coeff_base_eob`.
/// Indexes the innermost context axis of [`DEFAULT_COEFF_BASE_EOB_CDF`].
/// The §8.3.2 derivation reduces the full `get_coeff_base_ctx()` result
/// onto `0..SIG_COEF_CONTEXTS_EOB` via
/// `ctx = get_coeff_base_ctx(...) - SIG_COEF_CONTEXTS + SIG_COEF_CONTEXTS_EOB`.
pub const SIG_COEF_CONTEXTS_EOB: usize = 4;
/// `SIG_COEF_CONTEXTS` (§3) — number of contexts for `coeff_base`. Indexes
/// the innermost context axis of [`DEFAULT_COEFF_BASE_CDF`]. The §8.3.2
/// `get_coeff_base_ctx()` derivation returns a value in
/// `0..SIG_COEF_CONTEXTS` for non-EOB positions (the EOB position is
/// reduced onto `0..SIG_COEF_CONTEXTS_EOB` per the formula above). The
/// §3 partition tags `SIG_COEF_CONTEXTS_2D` (`= 26`) split this range
/// between the two-dimensional scan prefix and the one-dimensional
/// horizontal- / vertical-only tails.
pub const SIG_COEF_CONTEXTS: usize = 42;
/// `LEVEL_CONTEXTS` (§3) — number of contexts for `coeff_br`. Indexes the
/// innermost context axis of [`DEFAULT_COEFF_BR_CDF`]. The §8.3.2
/// `get_br_ctx()` derivation returns a value in `0..LEVEL_CONTEXTS` for
/// every transform-coefficient position that needs a base-range increment.
pub const LEVEL_CONTEXTS: usize = 21;
/// `BR_CDF_SIZE` (§3) — number of values for `coeff_br` (the alphabet
/// size of the per-coefficient base-range increment). Each `coeff_br`
/// read codes a value in `0..BR_CDF_SIZE`; up to
/// `COEFF_BASE_RANGE / (BR_CDF_SIZE - 1)` `coeff_br` reads can stack per
/// coefficient, as enumerated by §5.11.39. The CDF row therefore has
/// `BR_CDF_SIZE + 1` entries (`BR_CDF_SIZE` cumulative frequencies +
/// the §8.3 adaptation counter).
pub const BR_CDF_SIZE: usize = 4;
/// `SIG_COEF_CONTEXTS_2D` (§3) — the split point inside the
/// `0..SIG_COEF_CONTEXTS` `coeff_base` context range. The §8.3.2
/// `get_coeff_base_ctx()` two-dimensional branch returns values in
/// `0..SIG_COEF_CONTEXTS_2D`; the one-dimensional vertical- /
/// horizontal-only branch returns `SIG_COEF_CONTEXTS_2D` and above via
/// [`COEFF_BASE_POS_CTX_OFFSET`].
pub const SIG_COEF_CONTEXTS_2D: usize = 26;
/// `SIG_REF_DIFF_OFFSET_NUM` (§3) — the number of neighbour offsets
/// scanned by the §8.3.2 `get_coeff_base_ctx()` magnitude accumulation
/// (the row count of each [`SIG_REF_DIFF_OFFSET`] sub-table).
pub const SIG_REF_DIFF_OFFSET_NUM: usize = 5;
/// `NUM_BASE_LEVELS` (§3) — the number of base levels coded by
/// `coeff_base` before `coeff_br` increments take over. Used by the
/// §8.3.2 `get_br_ctx()` magnitude clamp
/// `Min(Quant[..], COEFF_BASE_RANGE + NUM_BASE_LEVELS + 1)`.
pub const NUM_BASE_LEVELS: usize = 2;
/// `COEFF_BASE_RANGE` (§3) — the maximum cumulative base-range increment
/// a coefficient can accrue from `coeff_br` reads. Used by the §8.3.2
/// `get_br_ctx()` magnitude clamp (see [`NUM_BASE_LEVELS`]).
pub const COEFF_BASE_RANGE: usize = 12;
// ---------------------------------------------------------------------
// §9.4 default CDF tables (the intra-frame mode / partition subset).
//
// Each innermost array has length `N + 1`: `N` cumulative frequencies
// (the last of which is `1 << 15 == 32768`) followed by the §8.3
// adaptation counter, which starts at 0.
// ---------------------------------------------------------------------
/// `Default_Intra_Frame_Y_Mode_Cdf[ INTRA_MODE_CONTEXTS ][ INTRA_MODE_CONTEXTS ][ INTRA_MODES + 1 ]`
/// (§9.4). Indexed `[abovemode][leftmode]` (§8.3.2).
pub const DEFAULT_INTRA_FRAME_Y_MODE_CDF: [[[u16; INTRA_MODES + 1]; INTRA_MODE_CONTEXTS];
INTRA_MODE_CONTEXTS] = [
[
[
15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466,
32768, 0,
],
[
12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409,
32768, 0,
],
[
10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567,
32768, 0,
],
[
14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462,
32768, 0,
],
[
12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475,
32768, 0,
],
],
[
[
10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436,
32768, 0,
],
[
5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794,
32768, 0,
],
[
7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492,
32768, 0,
],
[
8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210,
32768, 0,
],
[
7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994,
32768, 0,
],
],
[
[
12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077,
32768, 0,
],
[
9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907,
32768, 0,
],
[
6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086,
32768, 0,
],
[
10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252,
32768, 0,
],
[
9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463,
32768, 0,
],
],
[
[
12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409,
32768, 0,
],
[
8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342,
32768, 0,
],
[
8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018,
32768, 0,
],
[
7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528, 32768,
0,
],
[
8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485,
32768, 0,
],
],
[
[
12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477,
32768, 0,
],
[
9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277,
32768, 0,
],
[
8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261,
32768, 0,
],
[
10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482,
32768, 0,
],
[
7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593, 32768,
0,
],
],
];
/// `Default_Partition_W8_Cdf[ PARTITION_CONTEXTS ][ 5 ]` (§9.4). Codes a
/// 4-value symbol (`PARTITION_NONE/HORZ/VERT/SPLIT`).
pub const DEFAULT_PARTITION_W8_CDF: [[u16; 5]; PARTITION_CONTEXTS] = [
[19132, 25510, 30392, 32768, 0],
[13928, 19855, 28540, 32768, 0],
[12522, 23679, 28629, 32768, 0],
[9896, 18783, 25853, 32768, 0],
];
/// `Default_Partition_W16_Cdf[ PARTITION_CONTEXTS ][ 11 ]` (§9.4). Codes
/// a 10-value symbol (the full `EXT_PARTITION_TYPES`).
pub const DEFAULT_PARTITION_W16_CDF: [[u16; 11]; PARTITION_CONTEXTS] = [
[
15597, 20929, 24571, 26706, 27664, 28821, 29601, 30571, 31902, 32768, 0,
],
[
7925, 11043, 16785, 22470, 23971, 25043, 26651, 28701, 29834, 32768, 0,
],
[
5414, 13269, 15111, 20488, 22360, 24500, 25537, 26336, 32117, 32768, 0,
],
[
2662, 6362, 8614, 20860, 23053, 24778, 26436, 27829, 31171, 32768, 0,
],
];
/// `Default_Partition_W32_Cdf[ PARTITION_CONTEXTS ][ 11 ]` (§9.4).
pub const DEFAULT_PARTITION_W32_CDF: [[u16; 11]; PARTITION_CONTEXTS] = [
[
18462, 20920, 23124, 27647, 28227, 29049, 29519, 30178, 31544, 32768, 0,
],
[
7689, 9060, 12056, 24992, 25660, 26182, 26951, 28041, 29052, 32768, 0,
],
[
6015, 9009, 10062, 24544, 25409, 26545, 27071, 27526, 32047, 32768, 0,
],
[
1394, 2208, 2796, 28614, 29061, 29466, 29840, 30185, 31899, 32768, 0,
],
];
/// `Default_Partition_W64_Cdf[ PARTITION_CONTEXTS ][ 11 ]` (§9.4).
pub const DEFAULT_PARTITION_W64_CDF: [[u16; 11]; PARTITION_CONTEXTS] = [
[
20137, 21547, 23078, 29566, 29837, 30261, 30524, 30892, 31724, 32768, 0,
],
[
6732, 7490, 9497, 27944, 28250, 28515, 28969, 29630, 30104, 32768, 0,
],
[
5945, 7663, 8348, 28683, 29117, 29749, 30064, 30298, 32238, 32768, 0,
],
[
870, 1212, 1487, 31198, 31394, 31574, 31743, 31881, 32332, 32768, 0,
],
];
/// `Default_Partition_W128_Cdf[ PARTITION_CONTEXTS ][ 9 ]` (§9.4). The
/// 128×128 superblock omits the two `*_4` partitions, so the symbol has
/// 8 values.
pub const DEFAULT_PARTITION_W128_CDF: [[u16; 9]; PARTITION_CONTEXTS] = [
[27899, 28219, 28529, 32484, 32539, 32619, 32639, 32768, 0],
[6607, 6990, 8268, 32060, 32219, 32338, 32371, 32768, 0],
[5429, 6676, 7122, 32027, 32227, 32531, 32582, 32768, 0],
[711, 966, 1172, 32448, 32538, 32617, 32664, 32768, 0],
];
/// `Default_Skip_Cdf[ SKIP_CONTEXTS ][ 3 ]` (§9.4). A binary symbol.
pub const DEFAULT_SKIP_CDF: [[u16; 3]; SKIP_CONTEXTS] =
[[31671, 32768, 0], [16515, 32768, 0], [4576, 32768, 0]];
/// `Default_Use_Wiener_Cdf[ 2 + 1 ]` (§9.4 — av1-spec p.438). Binary
/// symbol `use_wiener` (§5.11.58 `read_lr_unit`, the
/// `FrameRestorationType[ plane ] == RESTORE_WIENER` arm) — `1` means
/// the unit uses the Wiener filter, `0` means `RESTORE_NONE`. Not
/// context-indexed (§8.3.1 sets `UseWienerCdf` to a single copy of this
/// default). The trailing `0` is the §8.3.1 adaptation counter slot.
pub const DEFAULT_USE_WIENER_CDF: [u16; 3] = [11570, 32768, 0];
/// `Default_Use_Sgrproj_Cdf[ 2 + 1 ]` (§9.4 — av1-spec p.438). Binary
/// symbol `use_sgrproj` (§5.11.58 `read_lr_unit`, the
/// `FrameRestorationType[ plane ] == RESTORE_SGRPROJ` arm) — `1` means
/// the unit uses the self-guided projection filter, `0` means
/// `RESTORE_NONE`. Not context-indexed.
pub const DEFAULT_USE_SGRPROJ_CDF: [u16; 3] = [16855, 32768, 0];
/// `Default_Restoration_Type_Cdf[ RESTORE_SWITCHABLE + 1 ]` (§9.4 —
/// av1-spec p.438). 3-symbol `restoration_type` (§5.11.58 `read_lr_unit`,
/// the `FrameRestorationType[ plane ] == RESTORE_SWITCHABLE` arm) coding
/// `RESTORE_NONE` (0) / `RESTORE_WIENER` (1) / `RESTORE_SGRPROJ` (2). The
/// CDF length is `RESTORE_SWITCHABLE + 1 == 4` (three symbols plus the
/// §8.3.1 counter slot). Not context-indexed.
pub const DEFAULT_RESTORATION_TYPE_CDF: [u16; 4] = [9413, 22581, 32768, 0];
/// `Default_Segment_Id_Cdf[ SEGMENT_ID_CONTEXTS ][ MAX_SEGMENTS + 1 ]`
/// (§9.4). Codes the `segment_id` (`MAX_SEGMENTS == 8` values).
pub const DEFAULT_SEGMENT_ID_CDF: [[u16; MAX_SEGMENTS + 1]; SEGMENT_ID_CONTEXTS] = [
[5622, 7893, 16093, 18233, 27809, 28373, 32533, 32768, 0],
[14274, 18230, 22557, 24935, 29980, 30851, 32344, 32768, 0],
[27527, 28487, 28723, 28890, 32397, 32647, 32679, 32768, 0],
];
/// `Default_Segment_Id_Predicted_Cdf[ SEGMENT_ID_PREDICTED_CONTEXTS ][ 3 ]`
/// (§9.4 — av1-spec p.442). Binary symbol `seg_id_predicted` (§5.11.19,
/// the `segmentation_temporal_update == 1` arm) — `1` means the block's
/// `segment_id` is taken from the previous frame's segmentation map via
/// `get_segment_id()`, `0` means the `read_segment_id()` follow-on
/// reads the literal id. Per the §9.4 table all three contexts share the
/// uniform start `{ 128 * 128, 32768, 0 } = { 16384, 32768, 0 }` (the
/// adaptation counter slot is the trailing zero per the §8.3.1 init
/// convention). The §8.3.2 selection is
/// `TileSegmentIdPredictedCdf[ ctx ]` with `ctx =
/// LeftSegPredContext[ MiRow ] + AboveSegPredContext[ MiCol ]`.
pub const DEFAULT_SEGMENT_ID_PREDICTED_CDF: [[u16; 3]; SEGMENT_ID_PREDICTED_CONTEXTS] = [
[128 * 128, 32768, 0],
[128 * 128, 32768, 0],
[128 * 128, 32768, 0],
];
// ---------------------------------------------------------------------
// §9.4 motion-vector default CDF tables (round 17).
//
// Per §8.3.1 every per-tile `Mv*Cdf[ i ]` array (`i = 0..MV_CONTEXTS-1`)
// is "set equal to a copy of" the corresponding `Default_Mv_*_Cdf`. The
// per-component (`comp = 0..1`) decomposition for `MvSign`/`MvBit`/
// `MvHp`/`MvClass0Bit`/`MvClass0Hp` similarly broadcasts the same flat
// default row to both components; `MvClassCdf`/`MvClass0FrCdf`/
// `MvFrCdf` carry distinct per-component rows in the source default
// (the inner `2` in the spec dimension is the `comp` axis).
//
// Each innermost array has length `N + 1`: `N` cumulative frequencies
// (the last `1 << 15 == 32768`) followed by the §8.3 adaptation
// counter, which starts at 0.
// ---------------------------------------------------------------------
/// `Default_Mv_Joint_Cdf[ MV_JOINTS + 1 ]` (§9.4). The spec uses
/// `MV_JOINTS + 1` as both the symbol count and the cumulative-array
/// length (the row holds 4 frequencies + 1 counter).
pub const DEFAULT_MV_JOINT_CDF: [u16; MV_JOINTS + 1] = [4096, 11264, 19328, 32768, 0];
/// `Default_Mv_Sign_Cdf[ 3 ]` (§9.4). Binary symbol; the cumulative
/// value `128*128 = 16384` is transcribed expanded.
pub const DEFAULT_MV_SIGN_CDF: [u16; 3] = [128 * 128, 32768, 0];
/// `Default_Mv_Class_Cdf[ 2 ][ MV_CLASSES + 1 ]` (§9.4). The leading `2`
/// is the `comp = 0..1` axis (both rows are identical per spec).
pub const DEFAULT_MV_CLASS_CDF: [[u16; MV_CLASSES + 1]; MV_COMPS] = [
[
28672, 30976, 31858, 32320, 32551, 32656, 32740, 32757, 32762, 32767, 32768, 0,
],
[
28672, 30976, 31858, 32320, 32551, 32656, 32740, 32757, 32762, 32767, 32768, 0,
],
];
/// `Default_Mv_Class0_Bit_Cdf[ 3 ]` (§9.4). Binary symbol; broadcast to
/// every `[comp]` slot at §8.3.1 init.
pub const DEFAULT_MV_CLASS0_BIT_CDF: [u16; 3] = [216 * 128, 32768, 0];
/// `Default_Mv_Class0_Fr_Cdf[ 2 ][ CLASS0_SIZE ][ MV_JOINTS + 1 ]`
/// (§9.4). The leading `2` is the `comp = 0..1` axis; the middle
/// dimension is `mv_class0_bit = 0..1` (the literal §5.11.32 dispatch
/// `[ MvCtx ][ comp ][ mv_class0_bit ]`).
pub const DEFAULT_MV_CLASS0_FR_CDF: [[[u16; MV_JOINTS + 1]; CLASS0_SIZE]; MV_COMPS] = [
[
[16384, 24576, 26624, 32768, 0],
[12288, 21248, 24128, 32768, 0],
],
[
[16384, 24576, 26624, 32768, 0],
[12288, 21248, 24128, 32768, 0],
],
];
/// `Default_Mv_Class0_Hp_Cdf[ 3 ]` (§9.4). Binary symbol.
pub const DEFAULT_MV_CLASS0_HP_CDF: [u16; 3] = [160 * 128, 32768, 0];
/// `Default_Mv_Bit_Cdf[ MV_OFFSET_BITS ][ 3 ]` (§9.4). One binary
/// distribution per offset-bit position `i = 0..MV_OFFSET_BITS-1`. The
/// `*128` factor expands the `8.7`-style fixed-point notation used in
/// the spec.
pub const DEFAULT_MV_BIT_CDF: [[u16; 3]; MV_OFFSET_BITS] = [
[136 * 128, 32768, 0],
[140 * 128, 32768, 0],
[148 * 128, 32768, 0],
[160 * 128, 32768, 0],
[176 * 128, 32768, 0],
[192 * 128, 32768, 0],
[224 * 128, 32768, 0],
[234 * 128, 32768, 0],
[234 * 128, 32768, 0],
[240 * 128, 32768, 0],
];
/// `Default_Mv_Fr_Cdf[ 2 ][ MV_JOINTS + 1 ]` (§9.4). The leading `2` is
/// the `comp = 0..1` axis. Both rows are identical per spec; the inner
/// `MV_JOINTS + 1` matches the 4-value `mv_fr` symbol (4 frequencies +
/// 1 counter).
pub const DEFAULT_MV_FR_CDF: [[u16; MV_JOINTS + 1]; MV_COMPS] = [
[8192, 17408, 21248, 32768, 0],
[8192, 17408, 21248, 32768, 0],
];
/// `Default_Mv_Hp_Cdf[ 3 ]` (§9.4). Binary symbol.
pub const DEFAULT_MV_HP_CDF: [u16; 3] = [128 * 128, 32768, 0];
// ---------------------------------------------------------------------
// §9.4 inter-mode / reference-frame default CDF tables (round 18).
//
// Per §8.3.1 each of `NewMvCdf`, `ZeroMvCdf`, `RefMvCdf`, `DrlModeCdf`,
// `IsInterCdf`, `CompModeCdf`, `SkipModeCdf`, `CompRefCdf`,
// `CompBwdRefCdf`, `SingleRefCdf`, `CompoundModeCdf`, `CompRefTypeCdf`,
// `UniCompRefCdf` "is set to a copy of" the corresponding `Default_*_Cdf`.
//
// Each innermost array has length `N + 1`: `N` cumulative frequencies
// (the last `1 << 15 == 32768`) followed by the §8.3 adaptation
// counter, which starts at 0.
// ---------------------------------------------------------------------
/// `Default_New_Mv_Cdf[ NEW_MV_CONTEXTS ][ 3 ]` (§9.4). Binary; codes
/// `new_mv` ("is the predicted mv a NEWMV?") per §8.3.2 selection
/// `TileNewMvCdf[ NewMvContext ]`.
pub const DEFAULT_NEW_MV_CDF: [[u16; 3]; NEW_MV_CONTEXTS] = [
[24035, 32768, 0],
[16630, 32768, 0],
[15339, 32768, 0],
[8386, 32768, 0],
[12222, 32768, 0],
[4676, 32768, 0],
];
/// `Default_Zero_Mv_Cdf[ ZERO_MV_CONTEXTS ][ 3 ]` (§9.4). Binary; codes
/// `zero_mv` per §8.3.2 `TileZeroMvCdf[ ZeroMvContext ]`.
pub const DEFAULT_ZERO_MV_CDF: [[u16; 3]; ZERO_MV_CONTEXTS] = [[2175, 32768, 0], [1054, 32768, 0]];
/// `Default_Ref_Mv_Cdf[ REF_MV_CONTEXTS ][ 3 ]` (§9.4). Binary; codes
/// `ref_mv` per §8.3.2 `TileRefMvCdf[ RefMvContext ]`.
pub const DEFAULT_REF_MV_CDF: [[u16; 3]; REF_MV_CONTEXTS] = [
[23974, 32768, 0],
[24188, 32768, 0],
[17848, 32768, 0],
[28622, 32768, 0],
[24312, 32768, 0],
[19923, 32768, 0],
];
/// `Default_Drl_Mode_Cdf[ DRL_MODE_CONTEXTS ][ 3 ]` (§9.4). Binary; codes
/// `drl_mode` per §8.3.2 `TileDrlModeCdf[ DrlCtxStack[ idx ] ]`.
pub const DEFAULT_DRL_MODE_CDF: [[u16; 3]; DRL_MODE_CONTEXTS] =
[[13104, 32768, 0], [24560, 32768, 0], [18945, 32768, 0]];
/// `Default_Is_Inter_Cdf[ IS_INTER_CONTEXTS ][ 3 ]` (§9.4). Binary; codes
/// `is_inter` per §8.3.2 `TileIsInterCdf[ ctx ]`.
pub const DEFAULT_IS_INTER_CDF: [[u16; 3]; IS_INTER_CONTEXTS] = [
[806, 32768, 0],
[16662, 32768, 0],
[20186, 32768, 0],
[26538, 32768, 0],
];
/// `Default_Comp_Mode_Cdf[ COMP_INTER_CONTEXTS ][ 3 ]` (§9.4). Binary;
/// codes `comp_mode` per §8.3.2 `TileCompModeCdf[ ctx ]`.
pub const DEFAULT_COMP_MODE_CDF: [[u16; 3]; COMP_INTER_CONTEXTS] = [
[26828, 32768, 0],
[24035, 32768, 0],
[12031, 32768, 0],
[10640, 32768, 0],
[2901, 32768, 0],
];
/// `Default_Skip_Mode_Cdf[ SKIP_MODE_CONTEXTS ][ 3 ]` (§9.4). Binary;
/// codes `skip_mode` per §8.3.2 `TileSkipModeCdf[ ctx ]`.
pub const DEFAULT_SKIP_MODE_CDF: [[u16; 3]; SKIP_MODE_CONTEXTS] =
[[32621, 32768, 0], [20708, 32768, 0], [8127, 32768, 0]];
/// `Default_Intrabc_Cdf[ 2 + 1 ]` (§9.4, av1-spec p.430). Binary; codes
/// `use_intrabc` per §8.3.2 `TileIntrabcCdf` (no context index — the
/// §8.3.2 selection text is "use_intrabc: The cdf for use_intrabc is
/// given by TileIntrabcCdf", with no `[ctx]`-style subscript, so the
/// working `TileIntrabcCdf` is a single row instead of a per-context
/// `[CONTEXTS][..]` array — the same shape as
/// [`DEFAULT_DELTA_Q_CDF`] / [`DEFAULT_DELTA_LF_CDF`]). The spec
/// listing is `{ 30531, 32768, 0 }` (av1-spec p.430): two cumulative
/// frequencies (`use_intrabc = 0` ⇒ `30531`, `use_intrabc = 1` ⇒
/// `32768`) plus the §8.3 adaptation counter (starts at 0). The skew
/// matches the §5.11.7 control-flow expectation that `use_intrabc`
/// stays off most of the time even on `allow_intrabc == 1` frames.
pub const DEFAULT_INTRABC_CDF: [u16; 3] = [30531, 32768, 0];
/// `Default_Delta_Q_Cdf[ DELTA_Q_SMALL + 2 ]` (§9.4, av1-spec p.431).
/// Codes `delta_q_abs` per §8.3.2 `TileDeltaQCdf`. There is **no**
/// context index for this syntax element — the spec lists one
/// `Default_Delta_Q_Cdf` table directly and §8.3.2 reads "the cdf is
/// given by `TileDeltaQCdf`" (no `[ctx]`-style subscript), so the
/// working `TileDeltaQCdf` is a single row instead of a per-context
/// `[CONTEXTS][..]` array.
pub const DEFAULT_DELTA_Q_CDF: [u16; DELTA_Q_SMALL + 2] = [28160, 32120, 32677, 32768, 0];
/// `Default_Delta_Lf_Cdf[ DELTA_LF_SMALL + 2 ]` (§9.4, av1-spec p.431).
/// Codes `delta_lf_abs` per §8.3.2 `TileDeltaLFCdf` (when
/// `delta_lf_multi == 0`) and is also the per-plane initialiser for
/// every row of `TileDeltaLFMultiCdf[ FRAME_LF_COUNT ]` (when
/// `delta_lf_multi == 1`) — the §8.3.1 init step reads "`DeltaLFCdf`
/// is set to a copy of `Default_Delta_Lf_Cdf`" and "`DeltaLFMultiCdf[
/// i ]` is set to a copy of `Default_Delta_Lf_Cdf` for
/// `i = 0..FRAME_LF_COUNT-1`". The §9.4 spec listing shows a value
/// row literally equal to `Default_Delta_Q_Cdf`; this is the spec's
/// intent — the two §5.11.x delta syntax elements share their
/// fresh-context distribution. Both rows are transcribed
/// independently so that any future adaptation drift (via §8.4.4) on
/// one does not leak through a shared reference.
pub const DEFAULT_DELTA_LF_CDF: [u16; DELTA_LF_SMALL + 2] = [28160, 32120, 32677, 32768, 0];
/// `Default_Comp_Ref_Cdf[ REF_CONTEXTS ][ FWD_REFS - 1 ][ 3 ]` (§9.4).
/// Binary; codes `comp_ref` / `comp_ref_p1` / `comp_ref_p2` per §8.3.2
/// `TileCompRefCdf[ ctx ][ 0..2 ]`.
pub const DEFAULT_COMP_REF_CDF: [[[u16; 3]; FWD_REFS - 1]; REF_CONTEXTS] = [
[[4946, 32768, 0], [9468, 32768, 0], [1503, 32768, 0]],
[[19891, 32768, 0], [22441, 32768, 0], [15160, 32768, 0]],
[[30731, 32768, 0], [31059, 32768, 0], [27544, 32768, 0]],
];
/// `Default_Comp_Bwd_Ref_Cdf[ REF_CONTEXTS ][ BWD_REFS - 1 ][ 3 ]`
/// (§9.4). Binary; codes `comp_bwdref` / `comp_bwdref_p1` per §8.3.2
/// `TileCompBwdRefCdf[ ctx ][ 0..1 ]`.
pub const DEFAULT_COMP_BWD_REF_CDF: [[[u16; 3]; BWD_REFS - 1]; REF_CONTEXTS] = [
[[2235, 32768, 0], [1423, 32768, 0]],
[[17182, 32768, 0], [15175, 32768, 0]],
[[30606, 32768, 0], [30489, 32768, 0]],
];
/// `Default_Single_Ref_Cdf[ REF_CONTEXTS ][ SINGLE_REFS - 1 ][ 3 ]`
/// (§9.4). Binary; codes `single_ref_p1` .. `single_ref_p6` per §8.3.2
/// `TileSingleRefCdf[ ctx ][ 0..5 ]` (the 6th `single_ref_p*` slot
/// `[5]` is `single_ref_p6` per the §8.3.2 list).
pub const DEFAULT_SINGLE_REF_CDF: [[[u16; 3]; SINGLE_REFS - 1]; REF_CONTEXTS] = [
[
[4897, 32768, 0],
[1555, 32768, 0],
[4236, 32768, 0],
[8650, 32768, 0],
[904, 32768, 0],
[1444, 32768, 0],
],
[
[16973, 32768, 0],
[16751, 32768, 0],
[19647, 32768, 0],
[24773, 32768, 0],
[11014, 32768, 0],
[15087, 32768, 0],
],
[
[29744, 32768, 0],
[30279, 32768, 0],
[31194, 32768, 0],
[31895, 32768, 0],
[26875, 32768, 0],
[30304, 32768, 0],
],
];
/// `Default_Compound_Mode_Cdf[ COMPOUND_MODE_CONTEXTS ][ COMPOUND_MODES + 1 ]`
/// (§9.4). Codes the 8-value `compound_mode` per §8.3.2
/// `TileCompoundModeCdf[ ctx ]`, where `ctx` is taken from
/// `Compound_Mode_Ctx_Map[ RefMvContext >> 1 ][ Min(NewMvContext,
/// COMP_NEWMV_CTXS - 1) ]`.
pub const DEFAULT_COMPOUND_MODE_CDF: [[u16; COMPOUND_MODES + 1]; COMPOUND_MODE_CONTEXTS] = [
[7760, 13823, 15808, 17641, 19156, 20666, 26891, 32768, 0],
[10730, 19452, 21145, 22749, 24039, 25131, 28724, 32768, 0],
[10664, 20221, 21588, 22906, 24295, 25387, 28436, 32768, 0],
[13298, 16984, 20471, 24182, 25067, 25736, 26422, 32768, 0],
[18904, 23325, 25242, 27432, 27898, 28258, 30758, 32768, 0],
[10725, 17454, 20124, 22820, 24195, 25168, 26046, 32768, 0],
[17125, 24273, 25814, 27492, 28214, 28704, 30592, 32768, 0],
[13046, 23214, 24505, 25942, 27435, 28442, 29330, 32768, 0],
];
/// `Default_Comp_Ref_Type_Cdf[ COMP_REF_TYPE_CONTEXTS ][ 3 ]` (§9.4).
/// Binary; codes `comp_ref_type` per §8.3.2 `TileCompRefTypeCdf[ ctx ]`.
pub const DEFAULT_COMP_REF_TYPE_CDF: [[u16; 3]; COMP_REF_TYPE_CONTEXTS] = [
[1198, 32768, 0],
[2070, 32768, 0],
[9166, 32768, 0],
[7499, 32768, 0],
[22475, 32768, 0],
];
/// `Default_Uni_Comp_Ref_Cdf[ REF_CONTEXTS ][ UNIDIR_COMP_REFS - 1 ][ 3 ]`
/// (§9.4). Binary; codes `uni_comp_ref` / `uni_comp_ref_p1` /
/// `uni_comp_ref_p2` per §8.3.2 `TileUniCompRefCdf[ ctx ][ 0..2 ]`.
pub const DEFAULT_UNI_COMP_REF_CDF: [[[u16; 3]; UNIDIR_COMP_REFS - 1]; REF_CONTEXTS] = [
[[5284, 32768, 0], [3865, 32768, 0], [3128, 32768, 0]],
[[23152, 32768, 0], [14173, 32768, 0], [15270, 32768, 0]],
[[31774, 32768, 0], [25120, 32768, 0], [26710, 32768, 0]],
];
// ---------------------------------------------------------------------
// §9.4 palette / filter-intra / CFL default CDF tables (round 19).
//
// Per §8.3.1 each working `*Cdf` array "is set to a copy of" its
// `Default_*_Cdf` (no per-context broadcast for this group). Each
// innermost array has length `N + 1`: `N` cumulative frequencies (the
// last `1 << 15 == 32768`) followed by the §8.3 adaptation counter,
// which starts at 0.
// ---------------------------------------------------------------------
/// `Default_Filter_Intra_Mode_Cdf[ INTRA_FILTER_MODES + 1 ]` (§9.4).
/// Codes the 5-value `filter_intra_mode`.
pub const DEFAULT_FILTER_INTRA_MODE_CDF: [u16; INTRA_FILTER_MODES + 1] =
[8949, 12776, 17211, 29558, 32768, 0];
/// `Default_Filter_Intra_Cdf[ BLOCK_SIZES ][ 3 ]` (§9.4). Binary; codes
/// `use_filter_intra` per §8.3.2 `TileFilterIntraCdf[ MiSize ]`. Per the
/// §9.4 note, first-dimension indices 10..=15 and 20..=21 are never
/// reached but are transcribed verbatim.
pub const DEFAULT_FILTER_INTRA_CDF: [[u16; 3]; BLOCK_SIZES] = [
[4621, 32768, 0],
[6743, 32768, 0],
[5893, 32768, 0],
[7866, 32768, 0],
[12551, 32768, 0],
[9394, 32768, 0],
[12408, 32768, 0],
[14301, 32768, 0],
[12756, 32768, 0],
[22343, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[12770, 32768, 0],
[10368, 32768, 0],
[20229, 32768, 0],
[18101, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
];
/// `Default_Palette_Y_Mode_Cdf[ PALETTE_BLOCK_SIZE_CONTEXTS ][ PALETTE_Y_MODE_CONTEXTS ][ 3 ]`
/// (§9.4). Binary; codes `has_palette_y` per §8.3.2
/// `TilePaletteYModeCdf[ bsizeCtx ][ ctx ]`.
pub const DEFAULT_PALETTE_Y_MODE_CDF: [[[u16; 3]; PALETTE_Y_MODE_CONTEXTS];
PALETTE_BLOCK_SIZE_CONTEXTS] = [
[[31676, 32768, 0], [3419, 32768, 0], [1261, 32768, 0]],
[[31912, 32768, 0], [2859, 32768, 0], [980, 32768, 0]],
[[31823, 32768, 0], [3400, 32768, 0], [781, 32768, 0]],
[[32030, 32768, 0], [3561, 32768, 0], [904, 32768, 0]],
[[32309, 32768, 0], [7337, 32768, 0], [1462, 32768, 0]],
[[32265, 32768, 0], [4015, 32768, 0], [1521, 32768, 0]],
[[32450, 32768, 0], [7946, 32768, 0], [129, 32768, 0]],
];
/// `Default_Palette_Uv_Mode_Cdf[ PALETTE_UV_MODE_CONTEXTS ][ 3 ]` (§9.4).
/// Binary; codes `has_palette_uv` per §8.3.2 `TilePaletteUVModeCdf[ ctx ]`.
pub const DEFAULT_PALETTE_UV_MODE_CDF: [[u16; 3]; PALETTE_UV_MODE_CONTEXTS] =
[[32461, 32768, 0], [21488, 32768, 0]];
/// `Default_Palette_Y_Size_Cdf[ PALETTE_BLOCK_SIZE_CONTEXTS ][ PALETTE_SIZES + 1 ]`
/// (§9.4). Codes the 7-value `palette_size_y_minus_2` per §8.3.2
/// `TilePaletteYSizeCdf[ bsizeCtx ]`.
pub const DEFAULT_PALETTE_Y_SIZE_CDF: [[u16; PALETTE_SIZES + 1]; PALETTE_BLOCK_SIZE_CONTEXTS] = [
[7952, 13000, 18149, 21478, 25527, 29241, 32768, 0],
[7139, 11421, 16195, 19544, 23666, 28073, 32768, 0],
[7788, 12741, 17325, 20500, 24315, 28530, 32768, 0],
[8271, 14064, 18246, 21564, 25071, 28533, 32768, 0],
[12725, 19180, 21863, 24839, 27535, 30120, 32768, 0],
[9711, 14888, 16923, 21052, 25661, 27875, 32768, 0],
[14940, 20797, 21678, 24186, 27033, 28999, 32768, 0],
];
/// `Default_Palette_Uv_Size_Cdf[ PALETTE_BLOCK_SIZE_CONTEXTS ][ PALETTE_SIZES + 1 ]`
/// (§9.4). Codes the 7-value `palette_size_uv_minus_2` per §8.3.2
/// `TilePaletteUVSizeCdf[ bsizeCtx ]`.
pub const DEFAULT_PALETTE_UV_SIZE_CDF: [[u16; PALETTE_SIZES + 1]; PALETTE_BLOCK_SIZE_CONTEXTS] = [
[8713, 19979, 27128, 29609, 31331, 32272, 32768, 0],
[5839, 15573, 23581, 26947, 29848, 31700, 32768, 0],
[4426, 11260, 17999, 21483, 25863, 29430, 32768, 0],
[3228, 9464, 14993, 18089, 22523, 27420, 32768, 0],
[3768, 8886, 13091, 17852, 22495, 27207, 32768, 0],
[2464, 8451, 12861, 21632, 25525, 28555, 32768, 0],
[1269, 5435, 10433, 18963, 21700, 25865, 32768, 0],
];
/// `Default_Palette_Size_2_Y_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 3 ]`
/// (§9.4). Codes the 2-value `palette_color_idx_y` for `PaletteSizeY == 2`.
pub const DEFAULT_PALETTE_SIZE_2_Y_COLOR_CDF: [[u16; 3]; PALETTE_COLOR_CONTEXTS] = [
[28710, 32768, 0],
[16384, 32768, 0],
[10553, 32768, 0],
[27036, 32768, 0],
[31603, 32768, 0],
];
/// `Default_Palette_Size_3_Y_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 4 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_3_Y_COLOR_CDF: [[u16; 4]; PALETTE_COLOR_CONTEXTS] = [
[27877, 30490, 32768, 0],
[11532, 25697, 32768, 0],
[6544, 30234, 32768, 0],
[23018, 28072, 32768, 0],
[31915, 32385, 32768, 0],
];
/// `Default_Palette_Size_4_Y_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 5 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_4_Y_COLOR_CDF: [[u16; 5]; PALETTE_COLOR_CONTEXTS] = [
[25572, 28046, 30045, 32768, 0],
[9478, 21590, 27256, 32768, 0],
[7248, 26837, 29824, 32768, 0],
[19167, 24486, 28349, 32768, 0],
[31400, 31825, 32250, 32768, 0],
];
/// `Default_Palette_Size_5_Y_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 6 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_5_Y_COLOR_CDF: [[u16; 6]; PALETTE_COLOR_CONTEXTS] = [
[24779, 26955, 28576, 30282, 32768, 0],
[8669, 20364, 24073, 28093, 32768, 0],
[4255, 27565, 29377, 31067, 32768, 0],
[19864, 23674, 26716, 29530, 32768, 0],
[31646, 31893, 32147, 32426, 32768, 0],
];
/// `Default_Palette_Size_6_Y_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 7 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_6_Y_COLOR_CDF: [[u16; 7]; PALETTE_COLOR_CONTEXTS] = [
[23132, 25407, 26970, 28435, 30073, 32768, 0],
[7443, 17242, 20717, 24762, 27982, 32768, 0],
[6300, 24862, 26944, 28784, 30671, 32768, 0],
[18916, 22895, 25267, 27435, 29652, 32768, 0],
[31270, 31550, 31808, 32059, 32353, 32768, 0],
];
/// `Default_Palette_Size_7_Y_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 8 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_7_Y_COLOR_CDF: [[u16; 8]; PALETTE_COLOR_CONTEXTS] = [
[23105, 25199, 26464, 27684, 28931, 30318, 32768, 0],
[6950, 15447, 18952, 22681, 25567, 28563, 32768, 0],
[7560, 23474, 25490, 27203, 28921, 30708, 32768, 0],
[18544, 22373, 24457, 26195, 28119, 30045, 32768, 0],
[31198, 31451, 31670, 31882, 32123, 32391, 32768, 0],
];
/// `Default_Palette_Size_8_Y_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 9 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_8_Y_COLOR_CDF: [[u16; 9]; PALETTE_COLOR_CONTEXTS] = [
[21689, 23883, 25163, 26352, 27506, 28827, 30195, 32768, 0],
[6892, 15385, 17840, 21606, 24287, 26753, 29204, 32768, 0],
[5651, 23182, 25042, 26518, 27982, 29392, 30900, 32768, 0],
[19349, 22578, 24418, 25994, 27524, 29031, 30448, 32768, 0],
[31028, 31270, 31504, 31705, 31927, 32153, 32392, 32768, 0],
];
/// `Default_Palette_Size_2_Uv_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 3 ]`
/// (§9.4). Codes the 2-value `palette_color_idx_uv` for `PaletteSizeUV == 2`.
pub const DEFAULT_PALETTE_SIZE_2_UV_COLOR_CDF: [[u16; 3]; PALETTE_COLOR_CONTEXTS] = [
[29089, 32768, 0],
[16384, 32768, 0],
[8713, 32768, 0],
[29257, 32768, 0],
[31610, 32768, 0],
];
/// `Default_Palette_Size_3_Uv_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 4 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_3_UV_COLOR_CDF: [[u16; 4]; PALETTE_COLOR_CONTEXTS] = [
[25257, 29145, 32768, 0],
[12287, 27293, 32768, 0],
[7033, 27960, 32768, 0],
[20145, 25405, 32768, 0],
[30608, 31639, 32768, 0],
];
/// `Default_Palette_Size_4_Uv_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 5 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_4_UV_COLOR_CDF: [[u16; 5]; PALETTE_COLOR_CONTEXTS] = [
[24210, 27175, 29903, 32768, 0],
[9888, 22386, 27214, 32768, 0],
[5901, 26053, 29293, 32768, 0],
[18318, 22152, 28333, 32768, 0],
[30459, 31136, 31926, 32768, 0],
];
/// `Default_Palette_Size_5_Uv_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 6 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_5_UV_COLOR_CDF: [[u16; 6]; PALETTE_COLOR_CONTEXTS] = [
[22980, 25479, 27781, 29986, 32768, 0],
[8413, 21408, 24859, 28874, 32768, 0],
[2257, 29449, 30594, 31598, 32768, 0],
[19189, 21202, 25915, 28620, 32768, 0],
[31844, 32044, 32281, 32518, 32768, 0],
];
/// `Default_Palette_Size_6_Uv_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 7 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_6_UV_COLOR_CDF: [[u16; 7]; PALETTE_COLOR_CONTEXTS] = [
[22217, 24567, 26637, 28683, 30548, 32768, 0],
[7307, 16406, 19636, 24632, 28424, 32768, 0],
[4441, 25064, 26879, 28942, 30919, 32768, 0],
[17210, 20528, 23319, 26750, 29582, 32768, 0],
[30674, 30953, 31396, 31735, 32207, 32768, 0],
];
/// `Default_Palette_Size_7_Uv_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 8 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_7_UV_COLOR_CDF: [[u16; 8]; PALETTE_COLOR_CONTEXTS] = [
[21239, 23168, 25044, 26962, 28705, 30506, 32768, 0],
[6545, 15012, 18004, 21817, 25503, 28701, 32768, 0],
[3448, 26295, 27437, 28704, 30126, 31442, 32768, 0],
[15889, 18323, 21704, 24698, 26976, 29690, 32768, 0],
[30988, 31204, 31479, 31734, 31983, 32325, 32768, 0],
];
/// `Default_Palette_Size_8_Uv_Color_Cdf[ PALETTE_COLOR_CONTEXTS ][ 9 ]` (§9.4).
pub const DEFAULT_PALETTE_SIZE_8_UV_COLOR_CDF: [[u16; 9]; PALETTE_COLOR_CONTEXTS] = [
[21442, 23288, 24758, 26246, 27649, 28980, 30563, 32768, 0],
[5863, 14933, 17552, 20668, 23683, 26411, 29273, 32768, 0],
[3415, 25810, 26877, 27990, 29223, 30394, 31618, 32768, 0],
[17965, 20084, 22232, 23974, 26274, 28402, 30390, 32768, 0],
[31190, 31329, 31516, 31679, 31825, 32026, 32322, 32768, 0],
];
/// `Default_Cfl_Sign_Cdf[ CFL_JOINT_SIGNS + 1 ]` (§9.4). Codes the
/// 8-value `cfl_alpha_signs` per §8.3.2 `TileCflSignCdf`.
pub const DEFAULT_CFL_SIGN_CDF: [u16; CFL_JOINT_SIGNS + 1] =
[1418, 2123, 13340, 18405, 26972, 28343, 32294, 32768, 0];
/// `Default_Cfl_Alpha_Cdf[ CFL_ALPHA_CONTEXTS ][ CFL_ALPHABET_SIZE + 1 ]`
/// (§9.4). Codes the 16-value `cfl_alpha_u` / `cfl_alpha_v` per §8.3.2
/// `TileCflAlphaCdf[ ctx ]`.
pub const DEFAULT_CFL_ALPHA_CDF: [[u16; CFL_ALPHABET_SIZE + 1]; CFL_ALPHA_CONTEXTS] = [
[
7637, 20719, 31401, 32481, 32657, 32688, 32692, 32696, 32700, 32704, 32708, 32712, 32716,
32720, 32724, 32768, 0,
],
[
14365, 23603, 28135, 31168, 32167, 32395, 32487, 32573, 32620, 32647, 32668, 32672, 32676,
32680, 32684, 32768, 0,
],
[
11532, 22380, 28445, 31360, 32349, 32523, 32584, 32649, 32673, 32677, 32681, 32685, 32689,
32693, 32697, 32768, 0,
],
[
26990, 31402, 32282, 32571, 32692, 32696, 32700, 32704, 32708, 32712, 32716, 32720, 32724,
32728, 32732, 32768, 0,
],
[
17248, 26058, 28904, 30608, 31305, 31877, 32126, 32321, 32394, 32464, 32516, 32560, 32576,
32593, 32622, 32768, 0,
],
[
14738, 21678, 25779, 27901, 29024, 30302, 30980, 31843, 32144, 32413, 32520, 32594, 32622,
32656, 32660, 32768, 0,
],
];
// ---------------------------------------------------------------------
// Round 20 — transform-size group (§9.4): `Default_Tx_*_Cdf` (four
// per-`maxRectTxSize` arrays codes `tx_depth`) + `Default_Txfm_Split_Cdf`
// (codes the binary `txfm_split`).
// ---------------------------------------------------------------------
/// `Default_Tx_8x8_Cdf[ TX_SIZE_CONTEXTS ][ MAX_TX_DEPTH + 1 ]` (§9.4).
/// Selected by §8.3.2 when `maxTxDepth == 1` (the small block-size
/// `tx_depth` group, two symbol values).
pub const DEFAULT_TX_8X8_CDF: [[u16; MAX_TX_DEPTH + 1]; TX_SIZE_CONTEXTS] =
[[19968, 32768, 0], [19968, 32768, 0], [24320, 32768, 0]];
/// `Default_Tx_16x16_Cdf[ TX_SIZE_CONTEXTS ][ MAX_TX_DEPTH + 2 ]` (§9.4).
/// Selected by §8.3.2 when `maxTxDepth == 2` (three symbol values).
pub const DEFAULT_TX_16X16_CDF: [[u16; MAX_TX_DEPTH + 2]; TX_SIZE_CONTEXTS] = [
[12272, 30172, 32768, 0],
[12272, 30172, 32768, 0],
[18677, 30848, 32768, 0],
];
/// `Default_Tx_32x32_Cdf[ TX_SIZE_CONTEXTS ][ MAX_TX_DEPTH + 2 ]` (§9.4).
/// Selected by §8.3.2 when `maxTxDepth == 3`.
pub const DEFAULT_TX_32X32_CDF: [[u16; MAX_TX_DEPTH + 2]; TX_SIZE_CONTEXTS] = [
[12986, 15180, 32768, 0],
[12986, 15180, 32768, 0],
[24302, 25602, 32768, 0],
];
/// `Default_Tx_64x64_Cdf[ TX_SIZE_CONTEXTS ][ MAX_TX_DEPTH + 2 ]` (§9.4).
/// Selected by §8.3.2 when `maxTxDepth == 4`.
pub const DEFAULT_TX_64X64_CDF: [[u16; MAX_TX_DEPTH + 2]; TX_SIZE_CONTEXTS] = [
[5782, 11475, 32768, 0],
[5782, 11475, 32768, 0],
[16803, 22759, 32768, 0],
];
/// `Default_Txfm_Split_Cdf[ TXFM_PARTITION_CONTEXTS ][ 3 ]` (§9.4).
/// Codes the binary `txfm_split` syntax element per §8.3.2.
pub const DEFAULT_TXFM_SPLIT_CDF: [[u16; 3]; TXFM_PARTITION_CONTEXTS] = [
[28581, 32768, 0],
[23846, 32768, 0],
[20847, 32768, 0],
[24315, 32768, 0],
[18196, 32768, 0],
[12133, 32768, 0],
[18791, 32768, 0],
[10887, 32768, 0],
[11005, 32768, 0],
[27179, 32768, 0],
[20004, 32768, 0],
[11281, 32768, 0],
[26549, 32768, 0],
[19308, 32768, 0],
[14224, 32768, 0],
[28015, 32768, 0],
[21546, 32768, 0],
[14400, 32768, 0],
[28165, 32768, 0],
[22401, 32768, 0],
[16088, 32768, 0],
];
// ---------------------------------------------------------------------
// Round 21 — inter-frame transform-type group (§9.4):
// `Default_Inter_Tx_Type_Set{1,2,3}_Cdf`. Codes `inter_tx_type` per
// §5.11.47 / §8.3.2 against the §6.10.19 transform-set returned by
// §5.11.48 `get_tx_set()`. The intra counterparts
// (`Default_Intra_Tx_Type_Set{1,2}_Cdf`) are a follow-up — they carry
// an extra `intraDir` axis and are handled in a separate round.
// ---------------------------------------------------------------------
/// `Default_Inter_Tx_Type_Set1_Cdf[ 2 ][ TX_TYPES + 1 ]` (§9.4).
///
/// Selected by §8.3.2 when `set == TX_SET_INTER_1` (the full inter
/// transform set, `Tx_Type_Inter_Inv_Set1` with 16 entries). The first
/// axis is `Tx_Size_Sqr[ txSz ] ∈ { TX_4X4, TX_8X8 }` — the only sizes
/// that reach `TX_SET_INTER_1` per §5.11.48 (`txSzSqr == TX_16X16`
/// would already have been routed to `TX_SET_INTER_2`).
pub const DEFAULT_INTER_TX_TYPE_SET1_CDF: [[u16; TX_TYPES + 1]; INTER_TX_TYPE_SET1_SIZES] = [
[
4458, 5560, 7695, 9709, 13330, 14789, 17537, 20266, 21504, 22848, 23934, 25474, 27727,
28915, 30631, 32768, 0,
],
[
1645, 2573, 4778, 5711, 7807, 8622, 10522, 15357, 17674, 20408, 22517, 25010, 27116, 28856,
30749, 32768, 0,
],
];
/// `Default_Inter_Tx_Type_Set2_Cdf[ TX_TYPES_SET2 + 1 ]` (§9.4).
///
/// Selected by §8.3.2 when `set == TX_SET_INTER_2` (the 16x16-only
/// inter transform set, `Tx_Type_Inter_Inv_Set2` with 12 entries).
/// `TX_SET_INTER_2` is reached only when `txSzSqr == TX_16X16` and
/// `reduced_tx_set == 0`, so there is no per-`Tx_Size_Sqr` first axis.
pub const DEFAULT_INTER_TX_TYPE_SET2_CDF: [u16; TX_TYPES_SET2 + 1] = [
770, 2421, 5225, 12907, 15819, 18927, 21561, 24089, 26595, 28526, 30529, 32768, 0,
];
/// `Default_Inter_Tx_Type_Set3_Cdf[ 4 ][ TX_TYPES_SET3 + 1 ]` (§9.4).
///
/// Selected by §8.3.2 when `set == TX_SET_INTER_3` (the reduced inter
/// transform set, `Tx_Type_Inter_Inv_Set3 = { IDTX, DCT_DCT }`, two
/// entries). The first axis is `Tx_Size_Sqr[ txSz ] ∈ { TX_4X4, TX_8X8,
/// TX_16X16, TX_32X32 }` — `txSzSqrUp > TX_32X32` would already have
/// been routed to `TX_SET_DCTONLY` per §5.11.48.
pub const DEFAULT_INTER_TX_TYPE_SET3_CDF: [[u16; TX_TYPES_SET3 + 1]; INTER_TX_TYPE_SET3_SIZES] = [
[16384, 32768, 0],
[4167, 32768, 0],
[1998, 32768, 0],
[748, 32768, 0],
];
// ---------------------------------------------------------------------
// Round 137 — intra-frame transform-type group (§9.4):
// `Default_Intra_Tx_Type_Set{1,2}_Cdf`. Codes `intra_tx_type` per
// §5.11.47 / §8.3.2 against the §6.10.19 intra transform set returned
// by §5.11.48 `get_tx_set()` on the `is_inter == 0` branch. Both
// tables carry an extra `intraDir` axis (`INTRA_MODES = 13`) on top of
// the `Tx_Size_Sqr` axis seen in the inter counterparts; `intraDir` is
// `YMode` when `use_filter_intra == 0` and
// `Filter_Intra_Mode_To_Intra_Dir[ filter_intra_mode ]` otherwise (see
// [`FILTER_INTRA_MODE_TO_INTRA_DIR`]).
// ---------------------------------------------------------------------
/// `Default_Intra_Tx_Type_Set1_Cdf[ 2 ][ INTRA_MODES ][ 8 ]` (§9.4).
///
/// Selected by §8.3.2 when `set == TX_SET_INTRA_1` (the full intra
/// transform set, `Tx_Type_Intra_Inv_Set1` with 7 entries; the row
/// width is `TX_TYPES_INTRA_SET1 + 1 == 8` per the §8.3 adaptation-
/// counter convention). The first axis is `Tx_Size_Sqr[ txSz ] ∈
/// { TX_4X4, TX_8X8 }` — `txSzSqr == TX_16X16` is routed to
/// `TX_SET_INTRA_2` by §5.11.48 and never reaches this table. The
/// second axis is `intraDir ∈ 0..INTRA_MODES` (`YMode` or
/// `Filter_Intra_Mode_To_Intra_Dir[ filter_intra_mode ]`).
pub const DEFAULT_INTRA_TX_TYPE_SET1_CDF: [[[u16; TX_TYPES_INTRA_SET1 + 1]; INTRA_MODES];
INTRA_TX_TYPE_SET1_SIZES] = [
[
[1535, 8035, 9461, 12751, 23467, 27825, 32768, 0],
[564, 3335, 9709, 10870, 18143, 28094, 32768, 0],
[672, 3247, 3676, 11982, 19415, 23127, 32768, 0],
[5279, 13885, 15487, 18044, 23527, 30252, 32768, 0],
[4423, 6074, 7985, 10416, 25693, 29298, 32768, 0],
[1486, 4241, 9460, 10662, 16456, 27694, 32768, 0],
[439, 2838, 3522, 6737, 18058, 23754, 32768, 0],
[1190, 4233, 4855, 11670, 20281, 24377, 32768, 0],
[1045, 4312, 8647, 10159, 18644, 29335, 32768, 0],
[202, 3734, 4747, 7298, 17127, 24016, 32768, 0],
[447, 4312, 6819, 8884, 16010, 23858, 32768, 0],
[277, 4369, 5255, 8905, 16465, 22271, 32768, 0],
[3409, 5436, 10599, 15599, 19687, 24040, 32768, 0],
],
[
[1870, 13742, 14530, 16498, 23770, 27698, 32768, 0],
[326, 8796, 14632, 15079, 19272, 27486, 32768, 0],
[484, 7576, 7712, 14443, 19159, 22591, 32768, 0],
[1126, 15340, 15895, 17023, 20896, 30279, 32768, 0],
[655, 4854, 5249, 5913, 22099, 27138, 32768, 0],
[1299, 6458, 8885, 9290, 14851, 25497, 32768, 0],
[311, 5295, 5552, 6885, 16107, 22672, 32768, 0],
[883, 8059, 8270, 11258, 17289, 21549, 32768, 0],
[741, 7580, 9318, 10345, 16688, 29046, 32768, 0],
[110, 7406, 7915, 9195, 16041, 23329, 32768, 0],
[363, 7974, 9357, 10673, 15629, 24474, 32768, 0],
[153, 7647, 8112, 9936, 15307, 19996, 32768, 0],
[3511, 6332, 11165, 15335, 19323, 23594, 32768, 0],
],
];
/// `Default_Intra_Tx_Type_Set2_Cdf[ 3 ][ INTRA_MODES ][ 6 ]` (§9.4).
///
/// Selected by §8.3.2 when `set == TX_SET_INTRA_2` (the reduced intra
/// transform set, `Tx_Type_Intra_Inv_Set2` with 5 entries; the row
/// width is `TX_TYPES_INTRA_SET2 + 1 == 6` per the §8.3 adaptation-
/// counter convention). The first axis is `Tx_Size_Sqr[ txSz ] ∈
/// { TX_4X4, TX_8X8, TX_16X16 }` — `txSzSqrUp > TX_32X32` would
/// already have been routed to `TX_SET_DCTONLY` per §5.11.48. The
/// second axis is `intraDir ∈ 0..INTRA_MODES`.
///
/// Note: the §9.4 source lists the first two `Tx_Size_Sqr` rows
/// (`TX_4X4`, `TX_8X8`) as the uniform `{ 6554, 13107, 19661, 26214,
/// 32768, 0 }` distribution (i.e. a flat 5-way prior, since `32768 / 5
/// ≈ 6554`). Only the `TX_16X16` row carries adapted seeds. This
/// matches the §5.11.48 routing where `txSzSqr == TX_16X16` always
/// reaches `TX_SET_INTRA_2`, while the smaller sizes can only reach it
/// via `reduced_tx_set == 1`.
pub const DEFAULT_INTRA_TX_TYPE_SET2_CDF: [[[u16; TX_TYPES_INTRA_SET2 + 1]; INTRA_MODES];
INTRA_TX_TYPE_SET2_SIZES] = [
[
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
],
[
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
[6554, 13107, 19661, 26214, 32768, 0],
],
[
[1127, 12814, 22772, 27483, 32768, 0],
[145, 6761, 11980, 26667, 32768, 0],
[362, 5887, 11678, 16725, 32768, 0],
[385, 15213, 18587, 30693, 32768, 0],
[25, 2914, 23134, 27903, 32768, 0],
[60, 4470, 11749, 23991, 32768, 0],
[37, 3332, 14511, 21448, 32768, 0],
[157, 6320, 13036, 17439, 32768, 0],
[119, 6719, 12906, 29396, 32768, 0],
[47, 5537, 12576, 21499, 32768, 0],
[269, 6076, 11258, 23115, 32768, 0],
[83, 5615, 12001, 17228, 32768, 0],
[1968, 5556, 12023, 18547, 32768, 0],
],
];
// ---------------------------------------------------------------------
// Round 22 — inter-frame interpolation-filter group (§9.4):
// `Default_Interp_Filter_Cdf`. Codes `interp_filter` per §5.11.x and
// the §8.3.2 selection. The §8.3.2 `ctx` formula folds two scalar
// neighbour inputs (`aboveType` / `leftType`) plus the §5.11.x scope
// `dir` / `RefFrame[1]` into a single `0..INTERP_FILTER_CONTEXTS`
// index — see [`interp_filter_ctx`].
// ---------------------------------------------------------------------
/// `Default_Interp_Filter_Cdf[ INTERP_FILTER_CONTEXTS ][ INTERP_FILTERS + 1 ]`
/// (§9.4). Indexed by the §8.3.2 `interp_filter` ctx (in
/// `0..INTERP_FILTER_CONTEXTS`); the innermost row codes the
/// `interp_filter ∈ { EIGHTTAP, EIGHTTAP_SMOOTH, EIGHTTAP_SHARP }`
/// symbol (`INTERP_FILTERS = 3` cumulative frequencies plus the
/// §8.3 adaptation counter that starts at 0).
pub const DEFAULT_INTERP_FILTER_CDF: [[u16; INTERP_FILTERS + 1]; INTERP_FILTER_CONTEXTS] = [
[31935, 32720, 32768, 0],
[5568, 32719, 32768, 0],
[422, 2938, 32768, 0],
[28244, 32608, 32768, 0],
[31206, 31953, 32768, 0],
[4862, 32121, 32768, 0],
[770, 1152, 32768, 0],
[20889, 25637, 32768, 0],
[31910, 32724, 32768, 0],
[4120, 32712, 32768, 0],
[305, 2247, 32768, 0],
[27403, 32636, 32768, 0],
[31022, 32009, 32768, 0],
[2963, 32093, 32768, 0],
[601, 943, 32768, 0],
[14969, 21398, 32768, 0],
];
// ---------------------------------------------------------------------
// Round 23 — motion-mode group (§9.4): `Default_Motion_Mode_Cdf`. The
// §8.3.2 selection (§7.4.x "motion_mode: the cdf is given by
// `TileMotionModeCdf[ MiSize ]`") is a straight `BLOCK_SIZES` index —
// no neighbour-context arithmetic. Per the §9.4 note, first-dimension
// indices 0..=2 and 16..=17 are never reached by the §5.11.x
// `read_motion_mode` selection (those `MiSize` values do not satisfy
// the §5.11.x `Block_Width >= 8 && Block_Height >= 8` gate that lets
// the syntax element appear), but the table is still transcribed
// full-width.
// ---------------------------------------------------------------------
/// `Default_Motion_Mode_Cdf[ BLOCK_SIZES ][ MOTION_MODES + 1 ]` (§9.4).
/// Indexed by `MiSize` per the §8.3.2 selection; the innermost row codes
/// the `motion_mode ∈ { SIMPLE, OBMC, LOCALWARP }` symbol (`MOTION_MODES
/// = 3` cumulative frequencies plus the §8.3 adaptation counter that
/// starts at 0). Rows 0..=2 and 16..=17 are spec-flagged as unreachable
/// — see the §9.4 note quoted in the group banner above.
pub const DEFAULT_MOTION_MODE_CDF: [[u16; MOTION_MODES + 1]; BLOCK_SIZES] = [
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[7651, 24760, 32768, 0],
[4738, 24765, 32768, 0],
[5391, 25528, 32768, 0],
[19419, 26810, 32768, 0],
[5123, 23606, 32768, 0],
[11606, 24308, 32768, 0],
[26260, 29116, 32768, 0],
[20360, 28062, 32768, 0],
[21679, 26830, 32768, 0],
[29516, 30701, 32768, 0],
[28898, 30397, 32768, 0],
[30878, 31335, 32768, 0],
[32507, 32558, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[28799, 31390, 32768, 0],
[26431, 30774, 32768, 0],
[28973, 31594, 32768, 0],
[29742, 31203, 32768, 0],
];
/// `Default_Use_Obmc_Cdf[ BLOCK_SIZES ][ 3 ]` (§9.4). Binary symbol
/// `use_obmc` (§5.11.27 — selects between OBMC and SIMPLE on the
/// post-`find_warp_samples` arm gated by `force_integer_mv ||
/// NumSamples == 0 || !allow_warped_motion || is_scaled(RefFrame[0])`).
/// Indexed by `MiSize` per the §8.3.2 selection (`TileUseObmcCdf[ MiSize
/// ]`). Per the §9.4 note, first-dimension indices `0..=2` and `16..=17`
/// are never reached by the §5.11.27 selection (those `MiSize` values
/// fail the §5.11.27 `Min(Block_Width, Block_Height) >= 8` gate); the
/// table is still transcribed full-width.
pub const DEFAULT_USE_OBMC_CDF: [[u16; 3]; BLOCK_SIZES] = [
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[10437, 32768, 0],
[9371, 32768, 0],
[9301, 32768, 0],
[17432, 32768, 0],
[14423, 32768, 0],
[15142, 32768, 0],
[25817, 32768, 0],
[22823, 32768, 0],
[22083, 32768, 0],
[30128, 32768, 0],
[31014, 32768, 0],
[31560, 32768, 0],
[32638, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[23664, 32768, 0],
[20901, 32768, 0],
[24008, 32768, 0],
[26879, 32768, 0],
];
// ---------------------------------------------------------------------
// Round 24 — compound-prediction group (§9.4):
// `Default_Comp_Group_Idx_Cdf`, `Default_Compound_Idx_Cdf`,
// `Default_Compound_Type_Cdf`. The first two are binary, keyed by a
// precomputed `ctx` (the §8.3.2 paragraphs derive `ctx` from
// neighbour state — `CompGroupIdxs` / `CompoundIdxs`, `AvailU` /
// `AvailL`, `AboveSingle` / `LeftSingle`, `AltRef` reference frames —
// which belongs in the future tile-walk; the selectors here take the
// already-computed index). `Default_Compound_Type_Cdf` is keyed by a
// straight `MiSize` index (the §8.3.2 selection text reads
// "`TileCompoundTypeCdf[ MiSize ]`"). Per the §9.4 note, first-
// dimension indices 0..=2, 10..=17 and 20..=21 of
// `Default_Compound_Type_Cdf` are never used (those block sizes don't
// satisfy the §5.11.x masked-compound gate); the table is still
// transcribed full-width.
// ---------------------------------------------------------------------
/// `Default_Compound_Idx_Cdf[ COMPOUND_IDX_CONTEXTS ][ 3 ]` (§9.4).
/// Binary symbol `compound_idx` (distance-weighted vs averaging blend;
/// see §6.10.24). Indexed by the precomputed `ctx` from the §8.3.2
/// `compound_idx` paragraph (`0..COMPOUND_IDX_CONTEXTS`). Each row is
/// two cumulative frequencies plus the §8.3 adaptation counter (0).
pub const DEFAULT_COMPOUND_IDX_CDF: [[u16; 3]; COMPOUND_IDX_CONTEXTS] = [
[18244, 32768, 0],
[12865, 32768, 0],
[7053, 32768, 0],
[13259, 32768, 0],
[9334, 32768, 0],
[4644, 32768, 0],
];
/// `Default_Comp_Group_Idx_Cdf[ COMP_GROUP_IDX_CONTEXTS ][ 3 ]` (§9.4).
/// Binary symbol `comp_group_idx` (whether `compound_idx` is present;
/// see §6.10.24). Indexed by the precomputed `ctx` from the §8.3.2
/// `comp_group_idx` paragraph (`ctx = Min(5, ctx)`, hence
/// `0..COMP_GROUP_IDX_CONTEXTS`). Each row is two cumulative
/// frequencies plus the §8.3 adaptation counter (0).
pub const DEFAULT_COMP_GROUP_IDX_CDF: [[u16; 3]; COMP_GROUP_IDX_CONTEXTS] = [
[26607, 32768, 0],
[22891, 32768, 0],
[18840, 32768, 0],
[24594, 32768, 0],
[19934, 32768, 0],
[22674, 32768, 0],
];
/// `Default_Compound_Type_Cdf[ BLOCK_SIZES ][ COMPOUND_TYPES + 1 ]`
/// (§9.4). Indexed by `MiSize` per the §8.3.2 selection; the innermost
/// row codes the binary `compound_type ∈ { COMPOUND_WEDGE,
/// COMPOUND_DIFFWTD }` symbol (`COMPOUND_TYPES = 2` cumulative
/// frequencies plus the §8.3 adaptation counter, which starts at 0).
/// Per the §9.4 note, rows 0..=2, 10..=17 and 20..=21 are never used in
/// the first dimension — see the group banner above.
pub const DEFAULT_COMPOUND_TYPE_CDF: [[u16; COMPOUND_TYPES + 1]; BLOCK_SIZES] = [
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[23431, 32768, 0],
[13171, 32768, 0],
[11470, 32768, 0],
[9770, 32768, 0],
[9100, 32768, 0],
[8233, 32768, 0],
[6172, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[11820, 32768, 0],
[7701, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
];
/// `Default_Y_Mode_Cdf[ BLOCK_SIZE_GROUPS ][ INTRA_MODES + 1 ]` (§9.4).
/// The inter-frame (non-keyframe) luma intra-mode CDF, indexed by
/// `ctx = Size_Group[ MiSize ]` per the §8.3.2 `y_mode` selection. Each
/// row carries `INTRA_MODES = 13` cumulative frequencies (the last being
/// `1 << 15 == 32768`) plus the §8.3 adaptation counter (starts at 0).
/// Distinct from [`DEFAULT_INTRA_FRAME_Y_MODE_CDF`], which is the
/// keyframe / intra-frame variant keyed by `[abovemode][leftmode]`.
pub const DEFAULT_Y_MODE_CDF: [[u16; INTRA_MODES + 1]; BLOCK_SIZE_GROUPS] = [
[
22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349, 32768,
0,
],
[
18673, 19845, 22631, 23318, 23950, 24649, 25527, 27364, 28152, 29701, 29984, 30852, 32768,
0,
],
[
19770, 20979, 23396, 23939, 24241, 24654, 25136, 27073, 27830, 29360, 29730, 30659, 32768,
0,
],
[
20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049, 32768,
0,
],
];
/// `Default_Uv_Mode_Cfl_Not_Allowed_Cdf[ INTRA_MODES ][ UV_INTRA_MODES_CFL_NOT_ALLOWED + 1 ]`
/// (§9.4). The chroma intra-mode CDF used when chroma-from-luma is **not**
/// allowed; indexed by `YMode` per the §8.3.2 `uv_mode` selection. Each
/// row carries `UV_INTRA_MODES_CFL_NOT_ALLOWED = 13` cumulative
/// frequencies plus the §8.3 adaptation counter (starts at 0).
pub const DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF: [[u16; UV_INTRA_MODES_CFL_NOT_ALLOWED + 1];
INTRA_MODES] = [
[
22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961, 32768,
0,
],
[
9513, 26881, 26973, 27046, 27118, 27664, 27739, 27824, 28359, 29505, 29800, 31796, 32768, 0,
],
[
9845, 9915, 28663, 28704, 28757, 28780, 29198, 29822, 29854, 30764, 31777, 32029, 32768, 0,
],
[
13639, 13897, 14171, 25331, 25606, 25727, 25953, 27148, 28577, 30612, 31355, 32493, 32768,
0,
],
[
9764, 9835, 9930, 9954, 25386, 27053, 27958, 28148, 28243, 31101, 31744, 32363, 32768, 0,
],
[
11825, 13589, 13677, 13720, 15048, 29213, 29301, 29458, 29711, 31161, 31441, 32550, 32768,
0,
],
[
14175, 14399, 16608, 16821, 17718, 17775, 28551, 30200, 30245, 31837, 32342, 32667, 32768,
0,
],
[
12885, 13038, 14978, 15590, 15673, 15748, 16176, 29128, 29267, 30643, 31961, 32461, 32768,
0,
],
[
12026, 13661, 13874, 15305, 15490, 15726, 15995, 16273, 28443, 30388, 30767, 32416, 32768,
0,
],
[
19052, 19840, 20579, 20916, 21150, 21467, 21885, 22719, 23174, 28861, 30379, 32175, 32768,
0,
],
[
18627, 19649, 20974, 21219, 21492, 21816, 22199, 23119, 23527, 27053, 31397, 32148, 32768,
0,
],
[
17026, 19004, 19997, 20339, 20586, 21103, 21349, 21907, 22482, 25896, 26541, 31819, 32768,
0,
],
[
12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606, 32768,
0,
],
];
/// `Default_Uv_Mode_Cfl_Allowed_Cdf[ INTRA_MODES ][ UV_INTRA_MODES_CFL_ALLOWED + 1 ]`
/// (§9.4). The chroma intra-mode CDF used when chroma-from-luma **is**
/// allowed (so `UV_CFL_PRED` is a coded value); indexed by `YMode` per
/// the §8.3.2 `uv_mode` selection. Each row carries
/// `UV_INTRA_MODES_CFL_ALLOWED = 14` cumulative frequencies plus the §8.3
/// adaptation counter (starts at 0).
pub const DEFAULT_UV_MODE_CFL_ALLOWED_CDF: [[u16; UV_INTRA_MODES_CFL_ALLOWED + 1]; INTRA_MODES] = [
[
10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455, 24212,
32768, 0,
],
[
4532, 19780, 20057, 20215, 20428, 21071, 21199, 21451, 22099, 24228, 24693, 27032, 29472,
32768, 0,
],
[
5273, 5379, 20177, 20270, 20385, 20439, 20949, 21695, 21774, 23138, 24256, 24703, 26679,
32768, 0,
],
[
6740, 7167, 7662, 14152, 14536, 14785, 15034, 16741, 18371, 21520, 22206, 23389, 24182,
32768, 0,
],
[
4987, 5368, 5928, 6068, 19114, 20315, 21857, 22253, 22411, 24911, 25380, 26027, 26376,
32768, 0,
],
[
5370, 6889, 7247, 7393, 9498, 21114, 21402, 21753, 21981, 24780, 25386, 26517, 27176,
32768, 0,
],
[
4816, 4961, 7204, 7326, 8765, 8930, 20169, 20682, 20803, 23188, 23763, 24455, 24940, 32768,
0,
],
[
6608, 6740, 8529, 9049, 9257, 9356, 9735, 18827, 19059, 22336, 23204, 23964, 24793, 32768,
0,
],
[
5998, 7419, 7781, 8933, 9255, 9549, 9753, 10417, 18898, 22494, 23139, 24764, 25989, 32768,
0,
],
[
10660, 11298, 12550, 12957, 13322, 13624, 14040, 15004, 15534, 20714, 21789, 23443, 24861,
32768, 0,
],
[
10522, 11530, 12552, 12963, 13378, 13779, 14245, 15235, 15902, 20102, 22696, 23774, 25838,
32768, 0,
],
[
10099, 10691, 12639, 13049, 13386, 13665, 14125, 15163, 15636, 19676, 20474, 23519, 25208,
32768, 0,
],
[
3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048, 32768, 0,
],
];
/// `Default_Angle_Delta_Cdf[ DIRECTIONAL_MODES ][ (2 * MAX_ANGLE_DELTA + 1) + 1 ]`
/// (§9.4). The CDF for the `angle_delta_y` / `angle_delta_uv` syntax
/// elements (the directional intra-prediction angle offset, biased by
/// `MAX_ANGLE_DELTA`). Indexed by the directional mode rebased onto
/// `0..DIRECTIONAL_MODES` (`YMode - V_PRED` for luma, `UVMode - V_PRED`
/// for chroma) per the §8.3.2 selection. Each row carries
/// `2 * MAX_ANGLE_DELTA + 1 = 7` cumulative frequencies (the last being
/// `1 << 15 == 32768`) plus the §8.3 adaptation counter (starts at 0).
pub const DEFAULT_ANGLE_DELTA_CDF: [[u16; (2 * MAX_ANGLE_DELTA + 1) + 1]; DIRECTIONAL_MODES] = [
[2180, 5032, 7567, 22776, 26989, 30217, 32768, 0],
[2301, 5608, 8801, 23487, 26974, 30330, 32768, 0],
[3780, 11018, 13699, 19354, 23083, 31286, 32768, 0],
[4581, 11226, 15147, 17138, 21834, 28397, 32768, 0],
[1737, 10927, 14509, 19588, 22745, 28823, 32768, 0],
[2664, 10176, 12485, 17650, 21600, 30495, 32768, 0],
[2240, 11096, 15453, 20341, 22561, 28917, 32768, 0],
[3605, 10428, 12459, 17676, 21244, 30655, 32768, 0],
];
// ---------------------------------------------------------------------
// Round 136 — coefficient-token entry sub-group default CDFs (§9.4),
// the gateway to tile-content decode. These are the tables reset by the
// §8.3.1 init_coeff_cdfs function: the transform-block skip flag
// (Default_Txb_Skip_Cdf), the end-of-block position class
// (Default_Eob_Pt_{16,32,64,128,256,512,1024}_Cdf), the EOB extra-bit
// (Default_Eob_Extra_Cdf), and the DC sign (Default_Dc_Sign_Cdf). The
// coeff_base / coeff_base_eob / coeff_br braid is deferred to a later
// round. Transcribed verbatim from the §9.4 source; the outer
// COEFF_CDF_Q_CTXS axis is selected by base_q_idx at init_coeff_cdfs.
// ---------------------------------------------------------------------
/// `Default_Txb_Skip_Cdf[ COEFF_CDF_Q_CTXS ][ TX_SIZES ][ TXB_SKIP_CONTEXTS ][ 3 ]`
/// (§9.4). Codes the `all_zero` (transform-block skip) flag. Selected at
/// `init_coeff_cdfs` by `idx` (the [`coeff_cdf_q_ctx`] value), then by
/// `txSzCtx` and the `all_zero` context.
pub const DEFAULT_TXB_SKIP_CDF: [[[[u16; 3]; TXB_SKIP_CONTEXTS]; TX_SIZES]; COEFF_CDF_Q_CTXS] = [
[
[
[31849, 32768, 0],
[5892, 32768, 0],
[12112, 32768, 0],
[21935, 32768, 0],
[20289, 32768, 0],
[27473, 32768, 0],
[32487, 32768, 0],
[7654, 32768, 0],
[19473, 32768, 0],
[29984, 32768, 0],
[9961, 32768, 0],
[30242, 32768, 0],
[32117, 32768, 0],
],
[
[31548, 32768, 0],
[1549, 32768, 0],
[10130, 32768, 0],
[16656, 32768, 0],
[18591, 32768, 0],
[26308, 32768, 0],
[32537, 32768, 0],
[5403, 32768, 0],
[18096, 32768, 0],
[30003, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[29957, 32768, 0],
[5391, 32768, 0],
[18039, 32768, 0],
[23566, 32768, 0],
[22431, 32768, 0],
[25822, 32768, 0],
[32197, 32768, 0],
[3778, 32768, 0],
[15336, 32768, 0],
[28981, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[17920, 32768, 0],
[1818, 32768, 0],
[7282, 32768, 0],
[25273, 32768, 0],
[10923, 32768, 0],
[31554, 32768, 0],
[32624, 32768, 0],
[1366, 32768, 0],
[15628, 32768, 0],
[30462, 32768, 0],
[146, 32768, 0],
[5132, 32768, 0],
[31657, 32768, 0],
],
[
[6308, 32768, 0],
[117, 32768, 0],
[1638, 32768, 0],
[2161, 32768, 0],
[16384, 32768, 0],
[10923, 32768, 0],
[30247, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[30371, 32768, 0],
[7570, 32768, 0],
[13155, 32768, 0],
[20751, 32768, 0],
[20969, 32768, 0],
[27067, 32768, 0],
[32013, 32768, 0],
[5495, 32768, 0],
[17942, 32768, 0],
[28280, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[31782, 32768, 0],
[1836, 32768, 0],
[10689, 32768, 0],
[17604, 32768, 0],
[21622, 32768, 0],
[27518, 32768, 0],
[32399, 32768, 0],
[4419, 32768, 0],
[16294, 32768, 0],
[28345, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[31901, 32768, 0],
[10311, 32768, 0],
[18047, 32768, 0],
[24806, 32768, 0],
[23288, 32768, 0],
[27914, 32768, 0],
[32296, 32768, 0],
[4215, 32768, 0],
[15756, 32768, 0],
[28341, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[26726, 32768, 0],
[1045, 32768, 0],
[11703, 32768, 0],
[20590, 32768, 0],
[18554, 32768, 0],
[25970, 32768, 0],
[31938, 32768, 0],
[5583, 32768, 0],
[21313, 32768, 0],
[29390, 32768, 0],
[641, 32768, 0],
[22265, 32768, 0],
[31452, 32768, 0],
],
[
[26584, 32768, 0],
[188, 32768, 0],
[8847, 32768, 0],
[24519, 32768, 0],
[22938, 32768, 0],
[30583, 32768, 0],
[32608, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[29614, 32768, 0],
[9068, 32768, 0],
[12924, 32768, 0],
[19538, 32768, 0],
[17737, 32768, 0],
[24619, 32768, 0],
[30642, 32768, 0],
[4119, 32768, 0],
[16026, 32768, 0],
[25657, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[31957, 32768, 0],
[3230, 32768, 0],
[11153, 32768, 0],
[18123, 32768, 0],
[20143, 32768, 0],
[26536, 32768, 0],
[31986, 32768, 0],
[3050, 32768, 0],
[14603, 32768, 0],
[25155, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[32363, 32768, 0],
[10692, 32768, 0],
[19090, 32768, 0],
[24357, 32768, 0],
[24442, 32768, 0],
[28312, 32768, 0],
[32169, 32768, 0],
[3648, 32768, 0],
[15690, 32768, 0],
[26815, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[30669, 32768, 0],
[3832, 32768, 0],
[11663, 32768, 0],
[18889, 32768, 0],
[19782, 32768, 0],
[23313, 32768, 0],
[31330, 32768, 0],
[5124, 32768, 0],
[18719, 32768, 0],
[28468, 32768, 0],
[3082, 32768, 0],
[20982, 32768, 0],
[29443, 32768, 0],
],
[
[28573, 32768, 0],
[3183, 32768, 0],
[17802, 32768, 0],
[25977, 32768, 0],
[26677, 32768, 0],
[27832, 32768, 0],
[32387, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[26887, 32768, 0],
[6729, 32768, 0],
[10361, 32768, 0],
[17442, 32768, 0],
[15045, 32768, 0],
[22478, 32768, 0],
[29072, 32768, 0],
[2713, 32768, 0],
[11861, 32768, 0],
[20773, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[31903, 32768, 0],
[2044, 32768, 0],
[7528, 32768, 0],
[14618, 32768, 0],
[16182, 32768, 0],
[24168, 32768, 0],
[31037, 32768, 0],
[2786, 32768, 0],
[11194, 32768, 0],
[20155, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[32510, 32768, 0],
[8430, 32768, 0],
[17318, 32768, 0],
[24154, 32768, 0],
[23674, 32768, 0],
[28789, 32768, 0],
[32139, 32768, 0],
[3440, 32768, 0],
[13117, 32768, 0],
[22702, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[31671, 32768, 0],
[2056, 32768, 0],
[11746, 32768, 0],
[16852, 32768, 0],
[18635, 32768, 0],
[24715, 32768, 0],
[31484, 32768, 0],
[4656, 32768, 0],
[16074, 32768, 0],
[24704, 32768, 0],
[1806, 32768, 0],
[14645, 32768, 0],
[25336, 32768, 0],
],
[
[31539, 32768, 0],
[8433, 32768, 0],
[20576, 32768, 0],
[27904, 32768, 0],
[27852, 32768, 0],
[30026, 32768, 0],
[32441, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
];
/// `Default_Eob_Pt_16_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ 2 ][ 6 ]`
/// (§9.4). Codes `eob_pt_16` (the EOB position class for a 16-coefficient block).
pub const DEFAULT_EOB_PT_16_CDF: [[[[u16; 6]; 2]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
[840, 1039, 1980, 4895, 32768, 0],
[370, 671, 1883, 4471, 32768, 0],
],
[
[3247, 4950, 9688, 14563, 32768, 0],
[1904, 3354, 7763, 14647, 32768, 0],
],
],
[
[
[2125, 2551, 5165, 8946, 32768, 0],
[513, 765, 1859, 6339, 32768, 0],
],
[
[7637, 9498, 14259, 19108, 32768, 0],
[2497, 4096, 8866, 16993, 32768, 0],
],
],
[
[
[4016, 4897, 8881, 14968, 32768, 0],
[716, 1105, 2646, 10056, 32768, 0],
],
[
[11139, 13270, 18241, 23566, 32768, 0],
[3192, 5032, 10297, 19755, 32768, 0],
],
],
[
[
[6708, 8958, 14746, 22133, 32768, 0],
[1222, 2074, 4783, 15410, 32768, 0],
],
[
[19575, 21766, 26044, 29709, 32768, 0],
[7297, 10767, 19273, 28194, 32768, 0],
],
],
];
/// `Default_Eob_Pt_32_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ 2 ][ 7 ]`
/// (§9.4). Codes `eob_pt_32`.
pub const DEFAULT_EOB_PT_32_CDF: [[[[u16; 7]; 2]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
[400, 520, 977, 2102, 6542, 32768, 0],
[210, 405, 1315, 3326, 7537, 32768, 0],
],
[
[2636, 4273, 7588, 11794, 20401, 32768, 0],
[1786, 3179, 6902, 11357, 19054, 32768, 0],
],
],
[
[
[989, 1249, 2019, 4151, 10785, 32768, 0],
[313, 441, 1099, 2917, 8562, 32768, 0],
],
[
[8394, 10352, 13932, 18855, 26014, 32768, 0],
[2578, 4124, 8181, 13670, 24234, 32768, 0],
],
],
[
[
[2515, 3003, 4452, 8162, 16041, 32768, 0],
[574, 821, 1836, 5089, 13128, 32768, 0],
],
[
[13468, 16303, 20361, 25105, 29281, 32768, 0],
[3542, 5502, 10415, 16760, 25644, 32768, 0],
],
],
[
[
[4617, 5709, 8446, 13584, 23135, 32768, 0],
[1156, 1702, 3675, 9274, 20539, 32768, 0],
],
[
[22086, 24282, 27010, 29770, 31743, 32768, 0],
[7699, 10897, 20891, 26926, 31628, 32768, 0],
],
],
];
/// `Default_Eob_Pt_64_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ 2 ][ 8 ]`
/// (§9.4). Codes `eob_pt_64`.
pub const DEFAULT_EOB_PT_64_CDF: [[[[u16; 8]; 2]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
[329, 498, 1101, 1784, 3265, 7758, 32768, 0],
[335, 730, 1459, 5494, 8755, 12997, 32768, 0],
],
[
[3505, 5304, 10086, 13814, 17684, 23370, 32768, 0],
[1563, 2700, 4876, 10911, 14706, 22480, 32768, 0],
],
],
[
[
[1260, 1446, 2253, 3712, 6652, 13369, 32768, 0],
[401, 605, 1029, 2563, 5845, 12626, 32768, 0],
],
[
[8609, 10612, 14624, 18714, 22614, 29024, 32768, 0],
[1923, 3127, 5867, 9703, 14277, 27100, 32768, 0],
],
],
[
[
[2374, 2772, 4583, 7276, 12288, 19706, 32768, 0],
[497, 810, 1315, 3000, 7004, 15641, 32768, 0],
],
[
[15050, 17126, 21410, 24886, 28156, 30726, 32768, 0],
[4034, 6290, 10235, 14982, 21214, 28491, 32768, 0],
],
],
[
[
[6307, 7541, 12060, 16358, 22553, 27865, 32768, 0],
[1289, 2320, 3971, 7926, 14153, 24291, 32768, 0],
],
[
[24212, 25708, 28268, 30035, 31307, 32049, 32768, 0],
[8726, 12378, 19409, 26450, 30038, 32462, 32768, 0],
],
],
];
/// `Default_Eob_Pt_128_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ 2 ][ 9 ]`
/// (§9.4). Codes `eob_pt_128`.
pub const DEFAULT_EOB_PT_128_CDF: [[[[u16; 9]; 2]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
[219, 482, 1140, 2091, 3680, 6028, 12586, 32768, 0],
[371, 699, 1254, 4830, 9479, 12562, 17497, 32768, 0],
],
[
[5245, 7456, 12880, 15852, 20033, 23932, 27608, 32768, 0],
[2054, 3472, 5869, 14232, 18242, 20590, 26752, 32768, 0],
],
],
[
[
[685, 933, 1488, 2714, 4766, 8562, 19254, 32768, 0],
[217, 352, 618, 2303, 5261, 9969, 17472, 32768, 0],
],
[
[8045, 11200, 15497, 19595, 23948, 27408, 30938, 32768, 0],
[2310, 4160, 7471, 14997, 17931, 20768, 30240, 32768, 0],
],
],
[
[
[1366, 1738, 2527, 5016, 9355, 15797, 24643, 32768, 0],
[354, 558, 944, 2760, 7287, 14037, 21779, 32768, 0],
],
[
[13627, 16246, 20173, 24429, 27948, 30415, 31863, 32768, 0],
[6275, 9889, 14769, 23164, 27988, 30493, 32272, 32768, 0],
],
],
[
[
[3472, 4885, 7489, 12481, 18517, 24536, 29635, 32768, 0],
[886, 1731, 3271, 8469, 15569, 22126, 28383, 32768, 0],
],
[
[24313, 26062, 28385, 30107, 31217, 31898, 32345, 32768, 0],
[9165, 13282, 21150, 30286, 31894, 32571, 32712, 32768, 0],
],
],
];
/// `Default_Eob_Pt_256_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ 2 ][ 10 ]`
/// (§9.4). Codes `eob_pt_256`.
pub const DEFAULT_EOB_PT_256_CDF: [[[[u16; 10]; 2]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
[310, 584, 1887, 3589, 6168, 8611, 11352, 15652, 32768, 0],
[998, 1850, 2998, 5604, 17341, 19888, 22899, 25583, 32768, 0],
],
[
[2520, 3240, 5952, 8870, 12577, 17558, 19954, 24168, 32768, 0],
[
2203, 4130, 7435, 10739, 20652, 23681, 25609, 27261, 32768, 0,
],
],
],
[
[
[1448, 2109, 4151, 6263, 9329, 13260, 17944, 23300, 32768, 0],
[399, 1019, 1749, 3038, 10444, 15546, 22739, 27294, 32768, 0],
],
[
[
6402, 8148, 12623, 15072, 18728, 22847, 26447, 29377, 32768, 0,
],
[
1674, 3252, 5734, 10159, 22397, 23802, 24821, 30940, 32768, 0,
],
],
],
[
[
[3089, 3920, 6038, 9460, 14266, 19881, 25766, 29176, 32768, 0],
[1084, 2358, 3488, 5122, 11483, 18103, 26023, 29799, 32768, 0],
],
[
[
11514, 13794, 17480, 20754, 24361, 27378, 29492, 31277, 32768, 0,
],
[
6571, 9610, 15516, 21826, 29092, 30829, 31842, 32708, 32768, 0,
],
],
],
[
[
[
5348, 7113, 11820, 15924, 22106, 26777, 30334, 31757, 32768, 0,
],
[2453, 4474, 6307, 8777, 16474, 22975, 29000, 31547, 32768, 0],
],
[
[
23110, 24597, 27140, 28894, 30167, 30927, 31392, 32094, 32768, 0,
],
[
9998, 17661, 25178, 28097, 31308, 32038, 32403, 32695, 32768, 0,
],
],
],
];
/// `Default_Eob_Pt_512_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ 11 ]`
/// (§9.4). Codes `eob_pt_512` (no per-`ptype`-pair axis).
pub const DEFAULT_EOB_PT_512_CDF: [[[u16; 11]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
641, 983, 3707, 5430, 10234, 14958, 18788, 23412, 26061, 32768, 0,
],
[
5095, 6446, 9996, 13354, 16017, 17986, 20919, 26129, 29140, 32768, 0,
],
],
[
[
1230, 2278, 5035, 7776, 11871, 15346, 19590, 24584, 28749, 32768, 0,
],
[
7265, 9979, 15819, 19250, 21780, 23846, 26478, 28396, 31811, 32768, 0,
],
],
[
[
2624, 3936, 6480, 9686, 13979, 17726, 23267, 28410, 31078, 32768, 0,
],
[
12015, 14769, 19588, 22052, 24222, 25812, 27300, 29219, 32114, 32768, 0,
],
],
[
[
5927, 7809, 10923, 14597, 19439, 24135, 28456, 31142, 32060, 32768, 0,
],
[
21093, 23043, 25742, 27658, 29097, 29716, 30073, 30820, 31956, 32768, 0,
],
],
];
/// `Default_Eob_Pt_1024_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ 12 ]`
/// (§9.4). Codes `eob_pt_1024`.
pub const DEFAULT_EOB_PT_1024_CDF: [[[u16; 12]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
393, 421, 751, 1623, 3160, 6352, 13345, 18047, 22571, 25830, 32768, 0,
],
[
1865, 1988, 2930, 4242, 10533, 16538, 21354, 27255, 28546, 31784, 32768, 0,
],
],
[
[
696, 948, 3145, 5702, 9706, 13217, 17851, 21856, 25692, 28034, 32768, 0,
],
[
2672, 3591, 9330, 17084, 22725, 24284, 26527, 28027, 28377, 30876, 32768, 0,
],
],
[
[
2784, 3831, 7041, 10521, 14847, 18844, 23155, 26682, 29229, 31045, 32768, 0,
],
[
9577, 12466, 17739, 20750, 22061, 23215, 24601, 25483, 25843, 32056, 32768, 0,
],
],
[
[
6698, 8334, 11961, 15762, 20186, 23862, 27434, 29326, 31082, 32050, 32768, 0,
],
[
20569, 22426, 25569, 26859, 28053, 28913, 29486, 29724, 29807, 32570, 32768, 0,
],
],
];
/// `Default_Eob_Extra_Cdf[ COEFF_CDF_Q_CTXS ][ TX_SIZES ][ PLANE_TYPES ]
/// [ EOB_COEF_CONTEXTS ][ 3 ]` (§9.4). Codes the binary `eob_extra` flag.
pub const DEFAULT_EOB_EXTRA_CDF: [[[[[u16; 3]; EOB_COEF_CONTEXTS]; PLANE_TYPES]; TX_SIZES];
COEFF_CDF_Q_CTXS] = [
[
[
[
[16961, 32768, 0],
[17223, 32768, 0],
[7621, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[19069, 32768, 0],
[22525, 32768, 0],
[13377, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[20401, 32768, 0],
[17025, 32768, 0],
[12845, 32768, 0],
[12873, 32768, 0],
[14094, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[20681, 32768, 0],
[20701, 32768, 0],
[15250, 32768, 0],
[15017, 32768, 0],
[14928, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[23905, 32768, 0],
[17194, 32768, 0],
[16170, 32768, 0],
[17695, 32768, 0],
[13826, 32768, 0],
[15810, 32768, 0],
[12036, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[23959, 32768, 0],
[20799, 32768, 0],
[19021, 32768, 0],
[16203, 32768, 0],
[17886, 32768, 0],
[14144, 32768, 0],
[12010, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[27399, 32768, 0],
[16327, 32768, 0],
[18071, 32768, 0],
[19584, 32768, 0],
[20721, 32768, 0],
[18432, 32768, 0],
[19560, 32768, 0],
[10150, 32768, 0],
[8805, 32768, 0],
],
[
[24932, 32768, 0],
[20833, 32768, 0],
[12027, 32768, 0],
[16670, 32768, 0],
[19914, 32768, 0],
[15106, 32768, 0],
[17662, 32768, 0],
[13783, 32768, 0],
[28756, 32768, 0],
],
],
[
[
[23406, 32768, 0],
[21845, 32768, 0],
[18432, 32768, 0],
[16384, 32768, 0],
[17096, 32768, 0],
[12561, 32768, 0],
[17320, 32768, 0],
[22395, 32768, 0],
[21370, 32768, 0],
],
[
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
],
[
[
[
[17471, 32768, 0],
[20223, 32768, 0],
[11357, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[20335, 32768, 0],
[21667, 32768, 0],
[14818, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[20430, 32768, 0],
[20662, 32768, 0],
[15367, 32768, 0],
[16970, 32768, 0],
[14657, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[22117, 32768, 0],
[22028, 32768, 0],
[18650, 32768, 0],
[16042, 32768, 0],
[15885, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[22409, 32768, 0],
[21012, 32768, 0],
[15650, 32768, 0],
[17395, 32768, 0],
[15469, 32768, 0],
[20205, 32768, 0],
[19511, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[24220, 32768, 0],
[22480, 32768, 0],
[17737, 32768, 0],
[18916, 32768, 0],
[19268, 32768, 0],
[18412, 32768, 0],
[18844, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[25991, 32768, 0],
[20314, 32768, 0],
[17731, 32768, 0],
[19678, 32768, 0],
[18649, 32768, 0],
[17307, 32768, 0],
[21798, 32768, 0],
[17549, 32768, 0],
[15630, 32768, 0],
],
[
[26585, 32768, 0],
[21469, 32768, 0],
[20432, 32768, 0],
[17735, 32768, 0],
[19280, 32768, 0],
[15235, 32768, 0],
[20297, 32768, 0],
[22471, 32768, 0],
[28997, 32768, 0],
],
],
[
[
[26605, 32768, 0],
[11304, 32768, 0],
[16726, 32768, 0],
[16560, 32768, 0],
[20866, 32768, 0],
[23524, 32768, 0],
[19878, 32768, 0],
[13469, 32768, 0],
[23084, 32768, 0],
],
[
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
],
[
[
[
[18983, 32768, 0],
[20512, 32768, 0],
[14885, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[20090, 32768, 0],
[19444, 32768, 0],
[17286, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[19139, 32768, 0],
[21487, 32768, 0],
[18959, 32768, 0],
[20910, 32768, 0],
[19089, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[20536, 32768, 0],
[20664, 32768, 0],
[20625, 32768, 0],
[19123, 32768, 0],
[14862, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[19833, 32768, 0],
[21502, 32768, 0],
[17485, 32768, 0],
[20267, 32768, 0],
[18353, 32768, 0],
[23329, 32768, 0],
[21478, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[22041, 32768, 0],
[23434, 32768, 0],
[20001, 32768, 0],
[20554, 32768, 0],
[20951, 32768, 0],
[20145, 32768, 0],
[15562, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[23312, 32768, 0],
[21607, 32768, 0],
[16526, 32768, 0],
[18957, 32768, 0],
[18034, 32768, 0],
[18934, 32768, 0],
[24247, 32768, 0],
[16921, 32768, 0],
[17080, 32768, 0],
],
[
[26579, 32768, 0],
[24910, 32768, 0],
[18637, 32768, 0],
[19800, 32768, 0],
[20388, 32768, 0],
[9887, 32768, 0],
[15642, 32768, 0],
[30198, 32768, 0],
[24721, 32768, 0],
],
],
[
[
[26998, 32768, 0],
[16737, 32768, 0],
[17838, 32768, 0],
[18922, 32768, 0],
[19515, 32768, 0],
[18636, 32768, 0],
[17333, 32768, 0],
[15776, 32768, 0],
[22658, 32768, 0],
],
[
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
],
[
[
[
[20177, 32768, 0],
[20789, 32768, 0],
[20262, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[21416, 32768, 0],
[20855, 32768, 0],
[23410, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[20238, 32768, 0],
[21057, 32768, 0],
[19159, 32768, 0],
[22337, 32768, 0],
[20159, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[20125, 32768, 0],
[20559, 32768, 0],
[21707, 32768, 0],
[22296, 32768, 0],
[17333, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[19941, 32768, 0],
[20527, 32768, 0],
[21470, 32768, 0],
[22487, 32768, 0],
[19558, 32768, 0],
[22354, 32768, 0],
[20331, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
[
[22752, 32768, 0],
[25006, 32768, 0],
[22075, 32768, 0],
[21576, 32768, 0],
[17740, 32768, 0],
[21690, 32768, 0],
[19211, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
[
[
[21442, 32768, 0],
[22358, 32768, 0],
[18503, 32768, 0],
[20291, 32768, 0],
[19945, 32768, 0],
[21294, 32768, 0],
[21178, 32768, 0],
[19400, 32768, 0],
[10556, 32768, 0],
],
[
[24648, 32768, 0],
[24949, 32768, 0],
[20708, 32768, 0],
[23905, 32768, 0],
[20501, 32768, 0],
[9558, 32768, 0],
[9423, 32768, 0],
[30365, 32768, 0],
[19253, 32768, 0],
],
],
[
[
[26064, 32768, 0],
[22098, 32768, 0],
[19613, 32768, 0],
[20525, 32768, 0],
[17595, 32768, 0],
[16618, 32768, 0],
[20497, 32768, 0],
[18989, 32768, 0],
[15513, 32768, 0],
],
[
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
],
],
],
];
/// `Default_Dc_Sign_Cdf[ COEFF_CDF_Q_CTXS ][ PLANE_TYPES ][ DC_SIGN_CONTEXTS ]
/// [ 3 ]` (§9.4). Codes the binary `dc_sign`. The §9.4 source gives the
/// frequencies as `128 * N` fixed-point products (preserved here); the four
/// `COEFF_CDF_Q_CTXS` slices are identical in the spec.
pub const DEFAULT_DC_SIGN_CDF: [[[[u16; 3]; DC_SIGN_CONTEXTS]; PLANE_TYPES]; COEFF_CDF_Q_CTXS] = [
[
[
[128 * 125, 32768, 0],
[128 * 102, 32768, 0],
[128 * 147, 32768, 0],
],
[
[128 * 119, 32768, 0],
[128 * 101, 32768, 0],
[128 * 135, 32768, 0],
],
],
[
[
[128 * 125, 32768, 0],
[128 * 102, 32768, 0],
[128 * 147, 32768, 0],
],
[
[128 * 119, 32768, 0],
[128 * 101, 32768, 0],
[128 * 135, 32768, 0],
],
],
[
[
[128 * 125, 32768, 0],
[128 * 102, 32768, 0],
[128 * 147, 32768, 0],
],
[
[128 * 119, 32768, 0],
[128 * 101, 32768, 0],
[128 * 135, 32768, 0],
],
],
[
[
[128 * 125, 32768, 0],
[128 * 102, 32768, 0],
[128 * 147, 32768, 0],
],
[
[128 * 119, 32768, 0],
[128 * 101, 32768, 0],
[128 * 135, 32768, 0],
],
],
];
// ---------------------------------------------------------------------
// Round 138 — coefficient `coeff_base_eob` sub-group default CDF (§9.4),
// the first of the `coeff_base` / `coeff_base_eob` / `coeff_br` braid
// reset by the §8.3.1 `init_coeff_cdfs` function. `coeff_base_eob`
// codes the base level of the last non-zero coefficient: the base level
// is `coeff_base_eob + 1`, and since this coefficient is known to be
// non-zero only base levels 1, 2, or 3 are coded — i.e. the cdf has
// three symbols (a 4-entry row: 3 cumulative frequencies + the §8.3
// adaptation counter). The remaining two tables of the braid
// (`Default_Coeff_Base_Cdf` and `Default_Coeff_Br_Cdf`) are deferred
// to later rounds. Transcribed verbatim from the §9.4 source; the
// outer `COEFF_CDF_Q_CTXS` axis is selected by `base_q_idx` at
// `init_coeff_cdfs`.
// ---------------------------------------------------------------------
/// `Default_Coeff_Base_Eob_Cdf[ COEFF_CDF_Q_CTXS ][ TX_SIZES ][ PLANE_TYPES ][ SIG_COEF_CONTEXTS_EOB ][ 4 ]`
/// (§9.4). Codes `coeff_base_eob`. Selected at `init_coeff_cdfs` by
/// `idx` (the [`coeff_cdf_q_ctx`] value), then by `txSzCtx`, `ptype`,
/// and the `coeff_base_eob` context (the latter is the
/// `get_coeff_base_ctx() - SIG_COEF_CONTEXTS + SIG_COEF_CONTEXTS_EOB`
/// reduction per §8.3.2).
pub const DEFAULT_COEFF_BASE_EOB_CDF: [[[[[u16; 4]; SIG_COEF_CONTEXTS_EOB]; PLANE_TYPES];
TX_SIZES]; COEFF_CDF_Q_CTXS] = [
[
[
[
[17837, 29055, 32768, 0],
[29600, 31446, 32768, 0],
[30844, 31878, 32768, 0],
[24926, 28948, 32768, 0],
],
[
[21365, 30026, 32768, 0],
[30512, 32423, 32768, 0],
[31658, 32621, 32768, 0],
[29630, 31881, 32768, 0],
],
],
[
[
[5717, 26477, 32768, 0],
[30491, 31703, 32768, 0],
[31550, 32158, 32768, 0],
[29648, 31491, 32768, 0],
],
[
[12608, 27820, 32768, 0],
[30680, 32225, 32768, 0],
[30809, 32335, 32768, 0],
[31299, 32423, 32768, 0],
],
],
[
[
[1786, 12612, 32768, 0],
[30663, 31625, 32768, 0],
[32339, 32468, 32768, 0],
[31148, 31833, 32768, 0],
],
[
[18857, 23865, 32768, 0],
[31428, 32428, 32768, 0],
[31744, 32373, 32768, 0],
[31775, 32526, 32768, 0],
],
],
[
[
[1787, 2532, 32768, 0],
[30832, 31662, 32768, 0],
[31824, 32682, 32768, 0],
[32133, 32569, 32768, 0],
],
[
[13751, 22235, 32768, 0],
[32089, 32409, 32768, 0],
[27084, 27920, 32768, 0],
[29291, 32594, 32768, 0],
],
],
[
[
[1725, 3449, 32768, 0],
[31102, 31935, 32768, 0],
[32457, 32613, 32768, 0],
[32412, 32649, 32768, 0],
],
[
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
],
],
],
[
[
[
[17560, 29888, 32768, 0],
[29671, 31549, 32768, 0],
[31007, 32056, 32768, 0],
[27286, 30006, 32768, 0],
],
[
[26594, 31212, 32768, 0],
[31208, 32582, 32768, 0],
[31835, 32637, 32768, 0],
[30595, 32206, 32768, 0],
],
],
[
[
[15239, 29932, 32768, 0],
[31315, 32095, 32768, 0],
[32130, 32434, 32768, 0],
[30864, 31996, 32768, 0],
],
[
[26279, 30968, 32768, 0],
[31142, 32495, 32768, 0],
[31713, 32540, 32768, 0],
[31929, 32594, 32768, 0],
],
],
[
[
[2644, 25198, 32768, 0],
[32038, 32451, 32768, 0],
[32639, 32695, 32768, 0],
[32166, 32518, 32768, 0],
],
[
[17187, 27668, 32768, 0],
[31714, 32550, 32768, 0],
[32283, 32678, 32768, 0],
[31930, 32563, 32768, 0],
],
],
[
[
[1044, 2257, 32768, 0],
[30755, 31923, 32768, 0],
[32208, 32693, 32768, 0],
[32244, 32615, 32768, 0],
],
[
[21317, 26207, 32768, 0],
[29133, 30868, 32768, 0],
[29311, 31231, 32768, 0],
[29657, 31087, 32768, 0],
],
],
[
[
[478, 1834, 32768, 0],
[31005, 31987, 32768, 0],
[32317, 32724, 32768, 0],
[30865, 32648, 32768, 0],
],
[
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
],
],
],
[
[
[
[20092, 30774, 32768, 0],
[30695, 32020, 32768, 0],
[31131, 32103, 32768, 0],
[28666, 30870, 32768, 0],
],
[
[27258, 31095, 32768, 0],
[31804, 32623, 32768, 0],
[31763, 32528, 32768, 0],
[31438, 32506, 32768, 0],
],
],
[
[
[18049, 30489, 32768, 0],
[31706, 32286, 32768, 0],
[32163, 32473, 32768, 0],
[31550, 32184, 32768, 0],
],
[
[27116, 30842, 32768, 0],
[31971, 32598, 32768, 0],
[32088, 32576, 32768, 0],
[32067, 32664, 32768, 0],
],
],
[
[
[12854, 29093, 32768, 0],
[32272, 32558, 32768, 0],
[32667, 32729, 32768, 0],
[32306, 32585, 32768, 0],
],
[
[25476, 30366, 32768, 0],
[32169, 32687, 32768, 0],
[32479, 32689, 32768, 0],
[31673, 32634, 32768, 0],
],
],
[
[
[2809, 19301, 32768, 0],
[32205, 32622, 32768, 0],
[32338, 32730, 32768, 0],
[31786, 32616, 32768, 0],
],
[
[22737, 29105, 32768, 0],
[30810, 32362, 32768, 0],
[30014, 32627, 32768, 0],
[30528, 32574, 32768, 0],
],
],
[
[
[935, 3382, 32768, 0],
[30789, 31909, 32768, 0],
[32466, 32756, 32768, 0],
[30860, 32513, 32768, 0],
],
[
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
],
],
],
[
[
[
[22497, 31198, 32768, 0],
[31715, 32495, 32768, 0],
[31606, 32337, 32768, 0],
[30388, 31990, 32768, 0],
],
[
[27877, 31584, 32768, 0],
[32170, 32728, 32768, 0],
[32155, 32688, 32768, 0],
[32219, 32702, 32768, 0],
],
],
[
[
[21457, 31043, 32768, 0],
[31951, 32483, 32768, 0],
[32153, 32562, 32768, 0],
[31473, 32215, 32768, 0],
],
[
[27558, 31151, 32768, 0],
[32020, 32640, 32768, 0],
[32097, 32575, 32768, 0],
[32242, 32719, 32768, 0],
],
],
[
[
[19980, 30591, 32768, 0],
[32219, 32597, 32768, 0],
[32581, 32706, 32768, 0],
[31803, 32287, 32768, 0],
],
[
[26473, 30507, 32768, 0],
[32431, 32723, 32768, 0],
[32196, 32611, 32768, 0],
[31588, 32528, 32768, 0],
],
],
[
[
[24647, 30463, 32768, 0],
[32412, 32695, 32768, 0],
[32468, 32720, 32768, 0],
[31269, 32523, 32768, 0],
],
[
[28482, 31505, 32768, 0],
[32152, 32701, 32768, 0],
[31732, 32598, 32768, 0],
[31767, 32712, 32768, 0],
],
],
[
[
[12358, 24977, 32768, 0],
[31331, 32385, 32768, 0],
[32634, 32756, 32768, 0],
[30411, 32548, 32768, 0],
],
[
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
[10923, 21845, 32768, 0],
],
],
],
];
// ---------------------------------------------------------------------
// Round 139 — coefficient `coeff_base` sub-group default CDF (§9.4),
// the second of the `coeff_base` / `coeff_base_eob` / `coeff_br` braid
// reset by the §8.3.1 `init_coeff_cdfs` function. `coeff_base` codes
// the base level of each non-EOB coefficient: the four-symbol alphabet
// is `0..3`, so the cdf is a 5-entry row (4 cumulative frequencies +
// the §8.3 adaptation counter). The last remaining table of the braid
// (`Default_Coeff_Br_Cdf`) is deferred to a later round. Transcribed
// verbatim from the §9.4 source; the outer `COEFF_CDF_Q_CTXS` axis is
// selected by `base_q_idx` at `init_coeff_cdfs`.
// ---------------------------------------------------------------------
/// `Default_Coeff_Base_Cdf[ COEFF_CDF_Q_CTXS ][ TX_SIZES ][ PLANE_TYPES ][ SIG_COEF_CONTEXTS ][ 5 ]`
/// (§9.4). Codes `coeff_base`. Selected at `init_coeff_cdfs` by
/// `idx` (the [`coeff_cdf_q_ctx`] value), then by `txSzCtx`, `ptype`,
/// and the `coeff_base` context (the §8.3.2 `get_coeff_base_ctx()`
/// result, in `0..SIG_COEF_CONTEXTS`).
///
/// Declared `static` rather than `const` because the table is large
/// (1680 5-entry rows = 16800 bytes) and `clippy::large_const_arrays`
/// flags const arrays of this size as a per-use copy hazard. The
/// shape and read semantics are otherwise identical to the other
/// `Default_*_Cdf` arrays in this module.
pub static DEFAULT_COEFF_BASE_CDF: [[[[[u16; 5]; SIG_COEF_CONTEXTS]; PLANE_TYPES]; TX_SIZES];
COEFF_CDF_Q_CTXS] = [
[
[
[
[4034, 8930, 12727, 32768, 0],
[18082, 29741, 31877, 32768, 0],
[12596, 26124, 30493, 32768, 0],
[9446, 21118, 27005, 32768, 0],
[6308, 15141, 21279, 32768, 0],
[2463, 6357, 9783, 32768, 0],
[20667, 30546, 31929, 32768, 0],
[13043, 26123, 30134, 32768, 0],
[8151, 18757, 24778, 32768, 0],
[5255, 12839, 18632, 32768, 0],
[2820, 7206, 11161, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[15736, 27553, 30604, 32768, 0],
[11210, 23794, 28787, 32768, 0],
[5947, 13874, 19701, 32768, 0],
[4215, 9323, 13891, 32768, 0],
[2833, 6462, 10059, 32768, 0],
[19605, 30393, 31582, 32768, 0],
[13523, 26252, 30248, 32768, 0],
[8446, 18622, 24512, 32768, 0],
[3818, 10343, 15974, 32768, 0],
[1481, 4117, 6796, 32768, 0],
[22649, 31302, 32190, 32768, 0],
[14829, 27127, 30449, 32768, 0],
[8313, 17702, 23304, 32768, 0],
[3022, 8301, 12786, 32768, 0],
[1536, 4412, 7184, 32768, 0],
[22354, 29774, 31372, 32768, 0],
[14723, 25472, 29214, 32768, 0],
[6673, 13745, 18662, 32768, 0],
[2068, 5766, 9322, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[6302, 16444, 21761, 32768, 0],
[23040, 31538, 32475, 32768, 0],
[15196, 28452, 31496, 32768, 0],
[10020, 22946, 28514, 32768, 0],
[6533, 16862, 23501, 32768, 0],
[3538, 9816, 15076, 32768, 0],
[24444, 31875, 32525, 32768, 0],
[15881, 28924, 31635, 32768, 0],
[9922, 22873, 28466, 32768, 0],
[6527, 16966, 23691, 32768, 0],
[4114, 11303, 17220, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[20201, 30770, 32209, 32768, 0],
[14754, 28071, 31258, 32768, 0],
[8378, 20186, 26517, 32768, 0],
[5916, 15299, 21978, 32768, 0],
[4268, 11583, 17901, 32768, 0],
[24361, 32025, 32581, 32768, 0],
[18673, 30105, 31943, 32768, 0],
[10196, 22244, 27576, 32768, 0],
[5495, 14349, 20417, 32768, 0],
[2676, 7415, 11498, 32768, 0],
[24678, 31958, 32585, 32768, 0],
[18629, 29906, 31831, 32768, 0],
[9364, 20724, 26315, 32768, 0],
[4641, 12318, 18094, 32768, 0],
[2758, 7387, 11579, 32768, 0],
[25433, 31842, 32469, 32768, 0],
[18795, 29289, 31411, 32768, 0],
[7644, 17584, 23592, 32768, 0],
[3408, 9014, 15047, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[4536, 10072, 14001, 32768, 0],
[25459, 31416, 32206, 32768, 0],
[16605, 28048, 30818, 32768, 0],
[11008, 22857, 27719, 32768, 0],
[6915, 16268, 22315, 32768, 0],
[2625, 6812, 10537, 32768, 0],
[24257, 31788, 32499, 32768, 0],
[16880, 29454, 31879, 32768, 0],
[11958, 25054, 29778, 32768, 0],
[7916, 18718, 25084, 32768, 0],
[3383, 8777, 13446, 32768, 0],
[22720, 31603, 32393, 32768, 0],
[14960, 28125, 31335, 32768, 0],
[9731, 22210, 27928, 32768, 0],
[6304, 15832, 22277, 32768, 0],
[2910, 7818, 12166, 32768, 0],
[20375, 30627, 32131, 32768, 0],
[13904, 27284, 30887, 32768, 0],
[9368, 21558, 27144, 32768, 0],
[5937, 14966, 21119, 32768, 0],
[2667, 7225, 11319, 32768, 0],
[23970, 31470, 32378, 32768, 0],
[17173, 29734, 32018, 32768, 0],
[12795, 25441, 29965, 32768, 0],
[8981, 19680, 25893, 32768, 0],
[4728, 11372, 16902, 32768, 0],
[24287, 31797, 32439, 32768, 0],
[16703, 29145, 31696, 32768, 0],
[10833, 23554, 28725, 32768, 0],
[6468, 16566, 23057, 32768, 0],
[2415, 6562, 10278, 32768, 0],
[26610, 32395, 32659, 32768, 0],
[18590, 30498, 32117, 32768, 0],
[12420, 25756, 29950, 32768, 0],
[7639, 18746, 24710, 32768, 0],
[3001, 8086, 12347, 32768, 0],
[25076, 32064, 32580, 32768, 0],
[17946, 30128, 32028, 32768, 0],
[12024, 24985, 29378, 32768, 0],
[7517, 18390, 24304, 32768, 0],
[3243, 8781, 13331, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[6037, 16771, 21957, 32768, 0],
[24774, 31704, 32426, 32768, 0],
[16830, 28589, 31056, 32768, 0],
[10602, 22828, 27760, 32768, 0],
[6733, 16829, 23071, 32768, 0],
[3250, 8914, 13556, 32768, 0],
[25582, 32220, 32668, 32768, 0],
[18659, 30342, 32223, 32768, 0],
[12546, 26149, 30515, 32768, 0],
[8420, 20451, 26801, 32768, 0],
[4636, 12420, 18344, 32768, 0],
[27581, 32362, 32639, 32768, 0],
[18987, 30083, 31978, 32768, 0],
[11327, 24248, 29084, 32768, 0],
[7264, 17719, 24120, 32768, 0],
[3995, 10768, 16169, 32768, 0],
[25893, 31831, 32487, 32768, 0],
[16577, 28587, 31379, 32768, 0],
[10189, 22748, 28182, 32768, 0],
[6832, 17094, 23556, 32768, 0],
[3708, 10110, 15334, 32768, 0],
[25904, 32282, 32656, 32768, 0],
[19721, 30792, 32276, 32768, 0],
[12819, 26243, 30411, 32768, 0],
[8572, 20614, 26891, 32768, 0],
[5364, 14059, 20467, 32768, 0],
[26580, 32438, 32677, 32768, 0],
[20852, 31225, 32340, 32768, 0],
[12435, 25700, 29967, 32768, 0],
[8691, 20825, 26976, 32768, 0],
[4446, 12209, 17269, 32768, 0],
[27350, 32429, 32696, 32768, 0],
[21372, 30977, 32272, 32768, 0],
[12673, 25270, 29853, 32768, 0],
[9208, 20925, 26640, 32768, 0],
[5018, 13351, 18732, 32768, 0],
[27351, 32479, 32713, 32768, 0],
[21398, 31209, 32387, 32768, 0],
[12162, 25047, 29842, 32768, 0],
[7896, 18691, 25319, 32768, 0],
[4670, 12882, 18881, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[5487, 10460, 13708, 32768, 0],
[21597, 28303, 30674, 32768, 0],
[11037, 21953, 26476, 32768, 0],
[8147, 17962, 22952, 32768, 0],
[5242, 13061, 18532, 32768, 0],
[1889, 5208, 8182, 32768, 0],
[26774, 32133, 32590, 32768, 0],
[17844, 29564, 31767, 32768, 0],
[11690, 24438, 29171, 32768, 0],
[7542, 18215, 24459, 32768, 0],
[2993, 8050, 12319, 32768, 0],
[28023, 32328, 32591, 32768, 0],
[18651, 30126, 31954, 32768, 0],
[12164, 25146, 29589, 32768, 0],
[7762, 18530, 24771, 32768, 0],
[3492, 9183, 13920, 32768, 0],
[27591, 32008, 32491, 32768, 0],
[17149, 28853, 31510, 32768, 0],
[11485, 24003, 28860, 32768, 0],
[7697, 18086, 24210, 32768, 0],
[3075, 7999, 12218, 32768, 0],
[28268, 32482, 32654, 32768, 0],
[19631, 31051, 32404, 32768, 0],
[13860, 27260, 31020, 32768, 0],
[9605, 21613, 27594, 32768, 0],
[4876, 12162, 17908, 32768, 0],
[27248, 32316, 32576, 32768, 0],
[18955, 30457, 32075, 32768, 0],
[11824, 23997, 28795, 32768, 0],
[7346, 18196, 24647, 32768, 0],
[3403, 9247, 14111, 32768, 0],
[29711, 32655, 32735, 32768, 0],
[21169, 31394, 32417, 32768, 0],
[13487, 27198, 30957, 32768, 0],
[8828, 21683, 27614, 32768, 0],
[4270, 11451, 17038, 32768, 0],
[28708, 32578, 32731, 32768, 0],
[20120, 31241, 32482, 32768, 0],
[13692, 27550, 31321, 32768, 0],
[9418, 22514, 28439, 32768, 0],
[4999, 13283, 19462, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[5673, 14302, 19711, 32768, 0],
[26251, 30701, 31834, 32768, 0],
[12782, 23783, 27803, 32768, 0],
[9127, 20657, 25808, 32768, 0],
[6368, 16208, 21462, 32768, 0],
[2465, 7177, 10822, 32768, 0],
[29961, 32563, 32719, 32768, 0],
[18318, 29891, 31949, 32768, 0],
[11361, 24514, 29357, 32768, 0],
[7900, 19603, 25607, 32768, 0],
[4002, 10590, 15546, 32768, 0],
[29637, 32310, 32595, 32768, 0],
[18296, 29913, 31809, 32768, 0],
[10144, 21515, 26871, 32768, 0],
[5358, 14322, 20394, 32768, 0],
[3067, 8362, 13346, 32768, 0],
[28652, 32470, 32676, 32768, 0],
[17538, 30771, 32209, 32768, 0],
[13924, 26882, 30494, 32768, 0],
[10496, 22837, 27869, 32768, 0],
[7236, 16396, 21621, 32768, 0],
[30743, 32687, 32746, 32768, 0],
[23006, 31676, 32489, 32768, 0],
[14494, 27828, 31120, 32768, 0],
[10174, 22801, 28352, 32768, 0],
[6242, 15281, 21043, 32768, 0],
[25817, 32243, 32720, 32768, 0],
[18618, 31367, 32325, 32768, 0],
[13997, 28318, 31878, 32768, 0],
[12255, 26534, 31383, 32768, 0],
[9561, 21588, 28450, 32768, 0],
[28188, 32635, 32724, 32768, 0],
[22060, 32365, 32728, 32768, 0],
[18102, 30690, 32528, 32768, 0],
[14196, 28864, 31999, 32768, 0],
[12262, 25792, 30865, 32768, 0],
[24176, 32109, 32628, 32768, 0],
[18280, 29681, 31963, 32768, 0],
[10205, 23703, 29664, 32768, 0],
[7889, 20025, 27676, 32768, 0],
[6060, 16743, 23970, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[5141, 7096, 8260, 32768, 0],
[27186, 29022, 29789, 32768, 0],
[6668, 12568, 15682, 32768, 0],
[2172, 6181, 8638, 32768, 0],
[1126, 3379, 4531, 32768, 0],
[443, 1361, 2254, 32768, 0],
[26083, 31153, 32436, 32768, 0],
[13486, 24603, 28483, 32768, 0],
[6508, 14840, 19910, 32768, 0],
[3386, 8800, 13286, 32768, 0],
[1530, 4322, 7054, 32768, 0],
[29639, 32080, 32548, 32768, 0],
[15897, 27552, 30290, 32768, 0],
[8588, 20047, 25383, 32768, 0],
[4889, 13339, 19269, 32768, 0],
[2240, 6871, 10498, 32768, 0],
[28165, 32197, 32517, 32768, 0],
[20735, 30427, 31568, 32768, 0],
[14325, 24671, 27692, 32768, 0],
[5119, 12554, 17805, 32768, 0],
[1810, 5441, 8261, 32768, 0],
[31212, 32724, 32748, 32768, 0],
[23352, 31766, 32545, 32768, 0],
[14669, 27570, 31059, 32768, 0],
[8492, 20894, 27272, 32768, 0],
[3644, 10194, 15204, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[2461, 7013, 9371, 32768, 0],
[24749, 29600, 30986, 32768, 0],
[9466, 19037, 22417, 32768, 0],
[3584, 9280, 14400, 32768, 0],
[1505, 3929, 5433, 32768, 0],
[677, 1500, 2736, 32768, 0],
[23987, 30702, 32117, 32768, 0],
[13554, 24571, 29263, 32768, 0],
[6211, 14556, 21155, 32768, 0],
[3135, 10972, 15625, 32768, 0],
[2435, 7127, 11427, 32768, 0],
[31300, 32532, 32550, 32768, 0],
[14757, 30365, 31954, 32768, 0],
[4405, 11612, 18553, 32768, 0],
[580, 4132, 7322, 32768, 0],
[1695, 10169, 14124, 32768, 0],
[30008, 32282, 32591, 32768, 0],
[19244, 30108, 31748, 32768, 0],
[11180, 24158, 29555, 32768, 0],
[5650, 14972, 19209, 32768, 0],
[2114, 5109, 8456, 32768, 0],
[31856, 32716, 32748, 32768, 0],
[23012, 31664, 32572, 32768, 0],
[13694, 26656, 30636, 32768, 0],
[8142, 19508, 26093, 32768, 0],
[4253, 10955, 16724, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[601, 983, 1311, 32768, 0],
[18725, 23406, 28087, 32768, 0],
[5461, 8192, 10923, 32768, 0],
[3781, 15124, 21425, 32768, 0],
[2587, 7761, 12072, 32768, 0],
[106, 458, 810, 32768, 0],
[22282, 29710, 31894, 32768, 0],
[8508, 20926, 25984, 32768, 0],
[3726, 12713, 18083, 32768, 0],
[1620, 7112, 10893, 32768, 0],
[729, 2236, 3495, 32768, 0],
[30163, 32474, 32684, 32768, 0],
[18304, 30464, 32000, 32768, 0],
[11443, 26526, 29647, 32768, 0],
[6007, 15292, 21299, 32768, 0],
[2234, 6703, 8937, 32768, 0],
[30954, 32177, 32571, 32768, 0],
[17363, 29562, 31076, 32768, 0],
[9686, 22464, 27410, 32768, 0],
[8192, 16384, 21390, 32768, 0],
[1755, 8046, 11264, 32768, 0],
[31168, 32734, 32748, 32768, 0],
[22486, 31441, 32471, 32768, 0],
[12833, 25627, 29738, 32768, 0],
[6980, 17379, 23122, 32768, 0],
[3111, 8887, 13479, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
[
[
[
[6041, 11854, 15927, 32768, 0],
[20326, 30905, 32251, 32768, 0],
[14164, 26831, 30725, 32768, 0],
[9760, 20647, 26585, 32768, 0],
[6416, 14953, 21219, 32768, 0],
[2966, 7151, 10891, 32768, 0],
[23567, 31374, 32254, 32768, 0],
[14978, 27416, 30946, 32768, 0],
[9434, 20225, 26254, 32768, 0],
[6658, 14558, 20535, 32768, 0],
[3916, 8677, 12989, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[18088, 29545, 31587, 32768, 0],
[13062, 25843, 30073, 32768, 0],
[8940, 16827, 22251, 32768, 0],
[7654, 13220, 17973, 32768, 0],
[5733, 10316, 14456, 32768, 0],
[22879, 31388, 32114, 32768, 0],
[15215, 27993, 30955, 32768, 0],
[9397, 19445, 24978, 32768, 0],
[3442, 9813, 15344, 32768, 0],
[1368, 3936, 6532, 32768, 0],
[25494, 32033, 32406, 32768, 0],
[16772, 27963, 30718, 32768, 0],
[9419, 18165, 23260, 32768, 0],
[2677, 7501, 11797, 32768, 0],
[1516, 4344, 7170, 32768, 0],
[26556, 31454, 32101, 32768, 0],
[17128, 27035, 30108, 32768, 0],
[8324, 15344, 20249, 32768, 0],
[1903, 5696, 9469, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8455, 19003, 24368, 32768, 0],
[23563, 32021, 32604, 32768, 0],
[16237, 29446, 31935, 32768, 0],
[10724, 23999, 29358, 32768, 0],
[6725, 17528, 24416, 32768, 0],
[3927, 10927, 16825, 32768, 0],
[26313, 32288, 32634, 32768, 0],
[17430, 30095, 32095, 32768, 0],
[11116, 24606, 29679, 32768, 0],
[7195, 18384, 25269, 32768, 0],
[4726, 12852, 19315, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[22822, 31648, 32483, 32768, 0],
[16724, 29633, 31929, 32768, 0],
[10261, 23033, 28725, 32768, 0],
[7029, 17840, 24528, 32768, 0],
[4867, 13886, 21502, 32768, 0],
[25298, 31892, 32491, 32768, 0],
[17809, 29330, 31512, 32768, 0],
[9668, 21329, 26579, 32768, 0],
[4774, 12956, 18976, 32768, 0],
[2322, 7030, 11540, 32768, 0],
[25472, 31920, 32543, 32768, 0],
[17957, 29387, 31632, 32768, 0],
[9196, 20593, 26400, 32768, 0],
[4680, 12705, 19202, 32768, 0],
[2917, 8456, 13436, 32768, 0],
[26471, 32059, 32574, 32768, 0],
[18458, 29783, 31909, 32768, 0],
[8400, 19464, 25956, 32768, 0],
[3812, 10973, 17206, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[6779, 13743, 17678, 32768, 0],
[24806, 31797, 32457, 32768, 0],
[17616, 29047, 31372, 32768, 0],
[11063, 23175, 28003, 32768, 0],
[6521, 16110, 22324, 32768, 0],
[2764, 7504, 11654, 32768, 0],
[25266, 32367, 32637, 32768, 0],
[19054, 30553, 32175, 32768, 0],
[12139, 25212, 29807, 32768, 0],
[7311, 18162, 24704, 32768, 0],
[3397, 9164, 14074, 32768, 0],
[25988, 32208, 32522, 32768, 0],
[16253, 28912, 31526, 32768, 0],
[9151, 21387, 27372, 32768, 0],
[5688, 14915, 21496, 32768, 0],
[2717, 7627, 12004, 32768, 0],
[23144, 31855, 32443, 32768, 0],
[16070, 28491, 31325, 32768, 0],
[8702, 20467, 26517, 32768, 0],
[5243, 13956, 20367, 32768, 0],
[2621, 7335, 11567, 32768, 0],
[26636, 32340, 32630, 32768, 0],
[19990, 31050, 32341, 32768, 0],
[13243, 26105, 30315, 32768, 0],
[8588, 19521, 25918, 32768, 0],
[4717, 11585, 17304, 32768, 0],
[25844, 32292, 32582, 32768, 0],
[19090, 30635, 32097, 32768, 0],
[11963, 24546, 28939, 32768, 0],
[6218, 16087, 22354, 32768, 0],
[2340, 6608, 10426, 32768, 0],
[28046, 32576, 32694, 32768, 0],
[21178, 31313, 32296, 32768, 0],
[13486, 26184, 29870, 32768, 0],
[7149, 17871, 23723, 32768, 0],
[2833, 7958, 12259, 32768, 0],
[27710, 32528, 32686, 32768, 0],
[20674, 31076, 32268, 32768, 0],
[12413, 24955, 29243, 32768, 0],
[6676, 16927, 23097, 32768, 0],
[2966, 8333, 12919, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8639, 19339, 24429, 32768, 0],
[24404, 31837, 32525, 32768, 0],
[16997, 29425, 31784, 32768, 0],
[11253, 24234, 29149, 32768, 0],
[6751, 17394, 24028, 32768, 0],
[3490, 9830, 15191, 32768, 0],
[26283, 32471, 32714, 32768, 0],
[19599, 31168, 32442, 32768, 0],
[13146, 26954, 30893, 32768, 0],
[8214, 20588, 26890, 32768, 0],
[4699, 13081, 19300, 32768, 0],
[28212, 32458, 32669, 32768, 0],
[18594, 30316, 32100, 32768, 0],
[11219, 24408, 29234, 32768, 0],
[6865, 17656, 24149, 32768, 0],
[3678, 10362, 16006, 32768, 0],
[25825, 32136, 32616, 32768, 0],
[17313, 29853, 32021, 32768, 0],
[11197, 24471, 29472, 32768, 0],
[6947, 17781, 24405, 32768, 0],
[3768, 10660, 16261, 32768, 0],
[27352, 32500, 32706, 32768, 0],
[20850, 31468, 32469, 32768, 0],
[14021, 27707, 31133, 32768, 0],
[8964, 21748, 27838, 32768, 0],
[5437, 14665, 21187, 32768, 0],
[26304, 32492, 32698, 32768, 0],
[20409, 31380, 32385, 32768, 0],
[13682, 27222, 30632, 32768, 0],
[8974, 21236, 26685, 32768, 0],
[4234, 11665, 16934, 32768, 0],
[26273, 32357, 32711, 32768, 0],
[20672, 31242, 32441, 32768, 0],
[14172, 27254, 30902, 32768, 0],
[9870, 21898, 27275, 32768, 0],
[5164, 13506, 19270, 32768, 0],
[26725, 32459, 32728, 32768, 0],
[20991, 31442, 32527, 32768, 0],
[13071, 26434, 30811, 32768, 0],
[8184, 20090, 26742, 32768, 0],
[4803, 13255, 19895, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[7555, 14942, 18501, 32768, 0],
[24410, 31178, 32287, 32768, 0],
[14394, 26738, 30253, 32768, 0],
[8413, 19554, 25195, 32768, 0],
[4766, 12924, 18785, 32768, 0],
[2029, 5806, 9207, 32768, 0],
[26776, 32364, 32663, 32768, 0],
[18732, 29967, 31931, 32768, 0],
[11005, 23786, 28852, 32768, 0],
[6466, 16909, 23510, 32768, 0],
[3044, 8638, 13419, 32768, 0],
[29208, 32582, 32704, 32768, 0],
[20068, 30857, 32208, 32768, 0],
[12003, 25085, 29595, 32768, 0],
[6947, 17750, 24189, 32768, 0],
[3245, 9103, 14007, 32768, 0],
[27359, 32465, 32669, 32768, 0],
[19421, 30614, 32174, 32768, 0],
[11915, 25010, 29579, 32768, 0],
[6950, 17676, 24074, 32768, 0],
[3007, 8473, 13096, 32768, 0],
[29002, 32676, 32735, 32768, 0],
[22102, 31849, 32576, 32768, 0],
[14408, 28009, 31405, 32768, 0],
[9027, 21679, 27931, 32768, 0],
[4694, 12678, 18748, 32768, 0],
[28216, 32528, 32682, 32768, 0],
[20849, 31264, 32318, 32768, 0],
[12756, 25815, 29751, 32768, 0],
[7565, 18801, 24923, 32768, 0],
[3509, 9533, 14477, 32768, 0],
[30133, 32687, 32739, 32768, 0],
[23063, 31910, 32515, 32768, 0],
[14588, 28051, 31132, 32768, 0],
[9085, 21649, 27457, 32768, 0],
[4261, 11654, 17264, 32768, 0],
[29518, 32691, 32748, 32768, 0],
[22451, 31959, 32613, 32768, 0],
[14864, 28722, 31700, 32768, 0],
[9695, 22964, 28716, 32768, 0],
[4932, 13358, 19502, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[6465, 16958, 21688, 32768, 0],
[25199, 31514, 32360, 32768, 0],
[14774, 27149, 30607, 32768, 0],
[9257, 21438, 26972, 32768, 0],
[5723, 15183, 21882, 32768, 0],
[3150, 8879, 13731, 32768, 0],
[26989, 32262, 32682, 32768, 0],
[17396, 29937, 32085, 32768, 0],
[11387, 24901, 29784, 32768, 0],
[7289, 18821, 25548, 32768, 0],
[3734, 10577, 16086, 32768, 0],
[29728, 32501, 32695, 32768, 0],
[17431, 29701, 31903, 32768, 0],
[9921, 22826, 28300, 32768, 0],
[5896, 15434, 22068, 32768, 0],
[3430, 9646, 14757, 32768, 0],
[28614, 32511, 32705, 32768, 0],
[19364, 30638, 32263, 32768, 0],
[13129, 26254, 30402, 32768, 0],
[8754, 20484, 26440, 32768, 0],
[4378, 11607, 17110, 32768, 0],
[30292, 32671, 32744, 32768, 0],
[21780, 31603, 32501, 32768, 0],
[14314, 27829, 31291, 32768, 0],
[9611, 22327, 28263, 32768, 0],
[4890, 13087, 19065, 32768, 0],
[25862, 32567, 32733, 32768, 0],
[20794, 32050, 32567, 32768, 0],
[17243, 30625, 32254, 32768, 0],
[13283, 27628, 31474, 32768, 0],
[9669, 22532, 28918, 32768, 0],
[27435, 32697, 32748, 32768, 0],
[24922, 32390, 32714, 32768, 0],
[21449, 31504, 32536, 32768, 0],
[16392, 29729, 31832, 32768, 0],
[11692, 24884, 29076, 32768, 0],
[24193, 32290, 32735, 32768, 0],
[18909, 31104, 32563, 32768, 0],
[12236, 26841, 31403, 32768, 0],
[8171, 21840, 29082, 32768, 0],
[7224, 17280, 25275, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[3078, 6839, 9890, 32768, 0],
[13837, 20450, 24479, 32768, 0],
[5914, 14222, 19328, 32768, 0],
[3866, 10267, 14762, 32768, 0],
[2612, 7208, 11042, 32768, 0],
[1067, 2991, 4776, 32768, 0],
[25817, 31646, 32529, 32768, 0],
[13708, 26338, 30385, 32768, 0],
[7328, 18585, 24870, 32768, 0],
[4691, 13080, 19276, 32768, 0],
[1825, 5253, 8352, 32768, 0],
[29386, 32315, 32624, 32768, 0],
[17160, 29001, 31360, 32768, 0],
[9602, 21862, 27396, 32768, 0],
[5915, 15772, 22148, 32768, 0],
[2786, 7779, 12047, 32768, 0],
[29246, 32450, 32663, 32768, 0],
[18696, 29929, 31818, 32768, 0],
[10510, 23369, 28560, 32768, 0],
[6229, 16499, 23125, 32768, 0],
[2608, 7448, 11705, 32768, 0],
[30753, 32710, 32748, 32768, 0],
[21638, 31487, 32503, 32768, 0],
[12937, 26854, 30870, 32768, 0],
[8182, 20596, 26970, 32768, 0],
[3637, 10269, 15497, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[5244, 12150, 16906, 32768, 0],
[20486, 26858, 29701, 32768, 0],
[7756, 18317, 23735, 32768, 0],
[3452, 9256, 13146, 32768, 0],
[2020, 5206, 8229, 32768, 0],
[1801, 4993, 7903, 32768, 0],
[27051, 31858, 32531, 32768, 0],
[15988, 27531, 30619, 32768, 0],
[9188, 21484, 26719, 32768, 0],
[6273, 17186, 23800, 32768, 0],
[3108, 9355, 14764, 32768, 0],
[31076, 32520, 32680, 32768, 0],
[18119, 30037, 31850, 32768, 0],
[10244, 22969, 27472, 32768, 0],
[4692, 14077, 19273, 32768, 0],
[3694, 11677, 17556, 32768, 0],
[30060, 32581, 32720, 32768, 0],
[21011, 30775, 32120, 32768, 0],
[11931, 24820, 29289, 32768, 0],
[7119, 17662, 24356, 32768, 0],
[3833, 10706, 16304, 32768, 0],
[31954, 32731, 32748, 32768, 0],
[23913, 31724, 32489, 32768, 0],
[15520, 28060, 31286, 32768, 0],
[11517, 23008, 28571, 32768, 0],
[6193, 14508, 20629, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[1035, 2807, 4156, 32768, 0],
[13162, 18138, 20939, 32768, 0],
[2696, 6633, 8755, 32768, 0],
[1373, 4161, 6853, 32768, 0],
[1099, 2746, 4716, 32768, 0],
[340, 1021, 1599, 32768, 0],
[22826, 30419, 32135, 32768, 0],
[10395, 21762, 26942, 32768, 0],
[4726, 12407, 17361, 32768, 0],
[2447, 7080, 10593, 32768, 0],
[1227, 3717, 6011, 32768, 0],
[28156, 31424, 31934, 32768, 0],
[16915, 27754, 30373, 32768, 0],
[9148, 20990, 26431, 32768, 0],
[5950, 15515, 21148, 32768, 0],
[2492, 7327, 11526, 32768, 0],
[30602, 32477, 32670, 32768, 0],
[20026, 29955, 31568, 32768, 0],
[11220, 23628, 28105, 32768, 0],
[6652, 17019, 22973, 32768, 0],
[3064, 8536, 13043, 32768, 0],
[31769, 32724, 32748, 32768, 0],
[22230, 30887, 32373, 32768, 0],
[12234, 25079, 29731, 32768, 0],
[7326, 18816, 25353, 32768, 0],
[3933, 10907, 16616, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
[
[
[
[8896, 16227, 20630, 32768, 0],
[23629, 31782, 32527, 32768, 0],
[15173, 27755, 31321, 32768, 0],
[10158, 21233, 27382, 32768, 0],
[6420, 14857, 21558, 32768, 0],
[3269, 8155, 12646, 32768, 0],
[24835, 32009, 32496, 32768, 0],
[16509, 28421, 31579, 32768, 0],
[10957, 21514, 27418, 32768, 0],
[7881, 15930, 22096, 32768, 0],
[5388, 10960, 15918, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[20745, 30773, 32093, 32768, 0],
[15200, 27221, 30861, 32768, 0],
[13032, 20873, 25667, 32768, 0],
[12285, 18663, 23494, 32768, 0],
[11563, 17481, 21489, 32768, 0],
[26260, 31982, 32320, 32768, 0],
[15397, 28083, 31100, 32768, 0],
[9742, 19217, 24824, 32768, 0],
[3261, 9629, 15362, 32768, 0],
[1480, 4322, 7499, 32768, 0],
[27599, 32256, 32460, 32768, 0],
[16857, 27659, 30774, 32768, 0],
[9551, 18290, 23748, 32768, 0],
[3052, 8933, 14103, 32768, 0],
[2021, 5910, 9787, 32768, 0],
[29005, 32015, 32392, 32768, 0],
[17677, 27694, 30863, 32768, 0],
[9204, 17356, 23219, 32768, 0],
[2403, 7516, 12814, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[10808, 22056, 26896, 32768, 0],
[25739, 32313, 32676, 32768, 0],
[17288, 30203, 32221, 32768, 0],
[11359, 24878, 29896, 32768, 0],
[6949, 17767, 24893, 32768, 0],
[4287, 11796, 18071, 32768, 0],
[27880, 32521, 32705, 32768, 0],
[19038, 31004, 32414, 32768, 0],
[12564, 26345, 30768, 32768, 0],
[8269, 19947, 26779, 32768, 0],
[5674, 14657, 21674, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[25742, 32319, 32671, 32768, 0],
[19557, 31164, 32454, 32768, 0],
[13381, 26381, 30755, 32768, 0],
[10101, 21466, 26722, 32768, 0],
[9209, 19650, 26825, 32768, 0],
[27107, 31917, 32432, 32768, 0],
[18056, 28893, 31203, 32768, 0],
[10200, 21434, 26764, 32768, 0],
[4660, 12913, 19502, 32768, 0],
[2368, 6930, 12504, 32768, 0],
[26960, 32158, 32613, 32768, 0],
[18628, 30005, 32031, 32768, 0],
[10233, 22442, 28232, 32768, 0],
[5471, 14630, 21516, 32768, 0],
[3235, 10767, 17109, 32768, 0],
[27696, 32440, 32692, 32768, 0],
[20032, 31167, 32438, 32768, 0],
[8700, 21341, 28442, 32768, 0],
[5662, 14831, 21795, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[9704, 17294, 21132, 32768, 0],
[26762, 32278, 32633, 32768, 0],
[18382, 29620, 31819, 32768, 0],
[10891, 23475, 28723, 32768, 0],
[6358, 16583, 23309, 32768, 0],
[3248, 9118, 14141, 32768, 0],
[27204, 32573, 32699, 32768, 0],
[19818, 30824, 32329, 32768, 0],
[11772, 25120, 30041, 32768, 0],
[6995, 18033, 25039, 32768, 0],
[3752, 10442, 16098, 32768, 0],
[27222, 32256, 32559, 32768, 0],
[15356, 28399, 31475, 32768, 0],
[8821, 20635, 27057, 32768, 0],
[5511, 14404, 21239, 32768, 0],
[2935, 8222, 13051, 32768, 0],
[24875, 32120, 32529, 32768, 0],
[15233, 28265, 31445, 32768, 0],
[8605, 20570, 26932, 32768, 0],
[5431, 14413, 21196, 32768, 0],
[2994, 8341, 13223, 32768, 0],
[28201, 32604, 32700, 32768, 0],
[21041, 31446, 32456, 32768, 0],
[13221, 26213, 30475, 32768, 0],
[8255, 19385, 26037, 32768, 0],
[4930, 12585, 18830, 32768, 0],
[28768, 32448, 32627, 32768, 0],
[19705, 30561, 32021, 32768, 0],
[11572, 23589, 28220, 32768, 0],
[5532, 15034, 21446, 32768, 0],
[2460, 7150, 11456, 32768, 0],
[29874, 32619, 32699, 32768, 0],
[21621, 31071, 32201, 32768, 0],
[12511, 24747, 28992, 32768, 0],
[6281, 16395, 22748, 32768, 0],
[3246, 9278, 14497, 32768, 0],
[29715, 32625, 32712, 32768, 0],
[20958, 31011, 32283, 32768, 0],
[11233, 23671, 28806, 32768, 0],
[6012, 16128, 22868, 32768, 0],
[3427, 9851, 15414, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[11016, 22111, 26794, 32768, 0],
[25946, 32357, 32677, 32768, 0],
[17890, 30452, 32252, 32768, 0],
[11678, 25142, 29816, 32768, 0],
[6720, 17534, 24584, 32768, 0],
[4230, 11665, 17820, 32768, 0],
[28400, 32623, 32747, 32768, 0],
[21164, 31668, 32575, 32768, 0],
[13572, 27388, 31182, 32768, 0],
[8234, 20750, 27358, 32768, 0],
[5065, 14055, 20897, 32768, 0],
[28981, 32547, 32705, 32768, 0],
[18681, 30543, 32239, 32768, 0],
[10919, 24075, 29286, 32768, 0],
[6431, 17199, 24077, 32768, 0],
[3819, 10464, 16618, 32768, 0],
[26870, 32467, 32693, 32768, 0],
[19041, 30831, 32347, 32768, 0],
[11794, 25211, 30016, 32768, 0],
[6888, 18019, 24970, 32768, 0],
[4370, 12363, 18992, 32768, 0],
[29578, 32670, 32744, 32768, 0],
[23159, 32007, 32613, 32768, 0],
[15315, 28669, 31676, 32768, 0],
[9298, 22607, 28782, 32768, 0],
[6144, 15913, 22968, 32768, 0],
[28110, 32499, 32669, 32768, 0],
[21574, 30937, 32015, 32768, 0],
[12759, 24818, 28727, 32768, 0],
[6545, 16761, 23042, 32768, 0],
[3649, 10597, 16833, 32768, 0],
[28163, 32552, 32728, 32768, 0],
[22101, 31469, 32464, 32768, 0],
[13160, 25472, 30143, 32768, 0],
[7303, 18684, 25468, 32768, 0],
[5241, 13975, 20955, 32768, 0],
[28400, 32631, 32744, 32768, 0],
[22104, 31793, 32603, 32768, 0],
[13557, 26571, 30846, 32768, 0],
[7749, 19861, 26675, 32768, 0],
[4873, 14030, 21234, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[9800, 17635, 21073, 32768, 0],
[26153, 31885, 32527, 32768, 0],
[15038, 27852, 31006, 32768, 0],
[8718, 20564, 26486, 32768, 0],
[5128, 14076, 20514, 32768, 0],
[2636, 7566, 11925, 32768, 0],
[27551, 32504, 32701, 32768, 0],
[18310, 30054, 32100, 32768, 0],
[10211, 23420, 29082, 32768, 0],
[6222, 16876, 23916, 32768, 0],
[3462, 9954, 15498, 32768, 0],
[29991, 32633, 32721, 32768, 0],
[19883, 30751, 32201, 32768, 0],
[11141, 24184, 29285, 32768, 0],
[6420, 16940, 23774, 32768, 0],
[3392, 9753, 15118, 32768, 0],
[28465, 32616, 32712, 32768, 0],
[19850, 30702, 32244, 32768, 0],
[10983, 24024, 29223, 32768, 0],
[6294, 16770, 23582, 32768, 0],
[3244, 9283, 14509, 32768, 0],
[30023, 32717, 32748, 32768, 0],
[22940, 32032, 32626, 32768, 0],
[14282, 27928, 31473, 32768, 0],
[8562, 21327, 27914, 32768, 0],
[4846, 13393, 19919, 32768, 0],
[29981, 32590, 32695, 32768, 0],
[20465, 30963, 32166, 32768, 0],
[11479, 23579, 28195, 32768, 0],
[5916, 15648, 22073, 32768, 0],
[3031, 8605, 13398, 32768, 0],
[31146, 32691, 32739, 32768, 0],
[23106, 31724, 32444, 32768, 0],
[13783, 26738, 30439, 32768, 0],
[7852, 19468, 25807, 32768, 0],
[3860, 11124, 16853, 32768, 0],
[31014, 32724, 32748, 32768, 0],
[23629, 32109, 32628, 32768, 0],
[14747, 28115, 31403, 32768, 0],
[8545, 21242, 27478, 32768, 0],
[4574, 12781, 19067, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[9185, 19694, 24688, 32768, 0],
[26081, 31985, 32621, 32768, 0],
[16015, 29000, 31787, 32768, 0],
[10542, 23690, 29206, 32768, 0],
[6732, 17945, 24677, 32768, 0],
[3916, 11039, 16722, 32768, 0],
[28224, 32566, 32744, 32768, 0],
[19100, 31138, 32485, 32768, 0],
[12528, 26620, 30879, 32768, 0],
[7741, 20277, 26885, 32768, 0],
[4566, 12845, 18990, 32768, 0],
[29933, 32593, 32718, 32768, 0],
[17670, 30333, 32155, 32768, 0],
[10385, 23600, 28909, 32768, 0],
[6243, 16236, 22407, 32768, 0],
[3976, 10389, 16017, 32768, 0],
[28377, 32561, 32738, 32768, 0],
[19366, 31175, 32482, 32768, 0],
[13327, 27175, 31094, 32768, 0],
[8258, 20769, 27143, 32768, 0],
[4703, 13198, 19527, 32768, 0],
[31086, 32706, 32748, 32768, 0],
[22853, 31902, 32583, 32768, 0],
[14759, 28186, 31419, 32768, 0],
[9284, 22382, 28348, 32768, 0],
[5585, 15192, 21868, 32768, 0],
[28291, 32652, 32746, 32768, 0],
[19849, 32107, 32571, 32768, 0],
[14834, 26818, 29214, 32768, 0],
[10306, 22594, 28672, 32768, 0],
[6615, 17384, 23384, 32768, 0],
[28947, 32604, 32745, 32768, 0],
[25625, 32289, 32646, 32768, 0],
[18758, 28672, 31403, 32768, 0],
[10017, 23430, 28523, 32768, 0],
[6862, 15269, 22131, 32768, 0],
[23933, 32509, 32739, 32768, 0],
[19927, 31495, 32631, 32768, 0],
[11903, 26023, 30621, 32768, 0],
[7026, 20094, 27252, 32768, 0],
[5998, 18106, 24437, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[4456, 11274, 15533, 32768, 0],
[21219, 29079, 31616, 32768, 0],
[11173, 23774, 28567, 32768, 0],
[7282, 18293, 24263, 32768, 0],
[4890, 13286, 19115, 32768, 0],
[1890, 5508, 8659, 32768, 0],
[26651, 32136, 32647, 32768, 0],
[14630, 28254, 31455, 32768, 0],
[8716, 21287, 27395, 32768, 0],
[5615, 15331, 22008, 32768, 0],
[2675, 7700, 12150, 32768, 0],
[29954, 32526, 32690, 32768, 0],
[16126, 28982, 31633, 32768, 0],
[9030, 21361, 27352, 32768, 0],
[5411, 14793, 21271, 32768, 0],
[2943, 8422, 13163, 32768, 0],
[29539, 32601, 32730, 32768, 0],
[18125, 30385, 32201, 32768, 0],
[10422, 24090, 29468, 32768, 0],
[6468, 17487, 24438, 32768, 0],
[2970, 8653, 13531, 32768, 0],
[30912, 32715, 32748, 32768, 0],
[20666, 31373, 32497, 32768, 0],
[12509, 26640, 30917, 32768, 0],
[8058, 20629, 27290, 32768, 0],
[4231, 12006, 18052, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[10202, 20633, 25484, 32768, 0],
[27336, 31445, 32352, 32768, 0],
[12420, 24384, 28552, 32768, 0],
[7648, 18115, 23856, 32768, 0],
[5662, 14341, 19902, 32768, 0],
[3611, 10328, 15390, 32768, 0],
[30945, 32616, 32736, 32768, 0],
[18682, 30505, 32253, 32768, 0],
[11513, 25336, 30203, 32768, 0],
[7449, 19452, 26148, 32768, 0],
[4482, 13051, 18886, 32768, 0],
[32022, 32690, 32747, 32768, 0],
[18578, 30501, 32146, 32768, 0],
[11249, 23368, 28631, 32768, 0],
[5645, 16958, 22158, 32768, 0],
[5009, 11444, 16637, 32768, 0],
[31357, 32710, 32748, 32768, 0],
[21552, 31494, 32504, 32768, 0],
[13891, 27677, 31340, 32768, 0],
[9051, 22098, 28172, 32768, 0],
[5190, 13377, 19486, 32768, 0],
[32364, 32740, 32748, 32768, 0],
[24839, 31907, 32551, 32768, 0],
[17160, 28779, 31696, 32768, 0],
[12452, 24137, 29602, 32768, 0],
[6165, 15389, 22477, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[2575, 7281, 11077, 32768, 0],
[14002, 20866, 25402, 32768, 0],
[6343, 15056, 19658, 32768, 0],
[4474, 11858, 17041, 32768, 0],
[2865, 8299, 12534, 32768, 0],
[1344, 3949, 6391, 32768, 0],
[24720, 31239, 32459, 32768, 0],
[12585, 25356, 29968, 32768, 0],
[7181, 18246, 24444, 32768, 0],
[5025, 13667, 19885, 32768, 0],
[2521, 7304, 11605, 32768, 0],
[29908, 32252, 32584, 32768, 0],
[17421, 29156, 31575, 32768, 0],
[9889, 22188, 27782, 32768, 0],
[5878, 15647, 22123, 32768, 0],
[2814, 8665, 13323, 32768, 0],
[30183, 32568, 32713, 32768, 0],
[18528, 30195, 32049, 32768, 0],
[10982, 24606, 29657, 32768, 0],
[6957, 18165, 25231, 32768, 0],
[3508, 10118, 15468, 32768, 0],
[31761, 32736, 32748, 32768, 0],
[21041, 31328, 32546, 32768, 0],
[12568, 26732, 31166, 32768, 0],
[8052, 20720, 27733, 32768, 0],
[4336, 12192, 18396, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
[
[
[
[7062, 16472, 22319, 32768, 0],
[24538, 32261, 32674, 32768, 0],
[13675, 28041, 31779, 32768, 0],
[8590, 20674, 27631, 32768, 0],
[5685, 14675, 22013, 32768, 0],
[3655, 9898, 15731, 32768, 0],
[26493, 32418, 32658, 32768, 0],
[16376, 29342, 32090, 32768, 0],
[10594, 22649, 28970, 32768, 0],
[8176, 17170, 24303, 32768, 0],
[5605, 12694, 19139, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[23888, 31902, 32542, 32768, 0],
[18612, 29687, 31987, 32768, 0],
[16245, 24852, 29249, 32768, 0],
[15765, 22608, 27559, 32768, 0],
[19895, 24699, 27510, 32768, 0],
[28401, 32212, 32457, 32768, 0],
[15274, 27825, 30980, 32768, 0],
[9364, 18128, 24332, 32768, 0],
[2283, 8193, 15082, 32768, 0],
[1228, 3972, 7881, 32768, 0],
[29455, 32469, 32620, 32768, 0],
[17981, 28245, 31388, 32768, 0],
[10921, 20098, 26240, 32768, 0],
[3743, 11829, 18657, 32768, 0],
[2374, 9593, 15715, 32768, 0],
[31068, 32466, 32635, 32768, 0],
[20321, 29572, 31971, 32768, 0],
[10771, 20255, 27119, 32768, 0],
[2795, 10410, 17361, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[9320, 22102, 27840, 32768, 0],
[27057, 32464, 32724, 32768, 0],
[16331, 30268, 32309, 32768, 0],
[10319, 23935, 29720, 32768, 0],
[6189, 16448, 24106, 32768, 0],
[3589, 10884, 18808, 32768, 0],
[29026, 32624, 32748, 32768, 0],
[19226, 31507, 32587, 32768, 0],
[12692, 26921, 31203, 32768, 0],
[7049, 19532, 27635, 32768, 0],
[7727, 15669, 23252, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[28056, 32625, 32748, 32768, 0],
[22383, 32075, 32669, 32768, 0],
[15417, 27098, 31749, 32768, 0],
[18127, 26493, 27190, 32768, 0],
[5461, 16384, 21845, 32768, 0],
[27982, 32091, 32584, 32768, 0],
[19045, 29868, 31972, 32768, 0],
[10397, 22266, 27932, 32768, 0],
[5990, 13697, 21500, 32768, 0],
[1792, 6912, 15104, 32768, 0],
[28198, 32501, 32718, 32768, 0],
[21534, 31521, 32569, 32768, 0],
[11109, 25217, 30017, 32768, 0],
[5671, 15124, 26151, 32768, 0],
[4681, 14043, 18725, 32768, 0],
[28688, 32580, 32741, 32768, 0],
[22576, 32079, 32661, 32768, 0],
[10627, 22141, 28340, 32768, 0],
[9362, 14043, 28087, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[7754, 16948, 22142, 32768, 0],
[25670, 32330, 32691, 32768, 0],
[15663, 29225, 31994, 32768, 0],
[9878, 23288, 29158, 32768, 0],
[6419, 17088, 24336, 32768, 0],
[3859, 11003, 17039, 32768, 0],
[27562, 32595, 32725, 32768, 0],
[17575, 30588, 32399, 32768, 0],
[10819, 24838, 30309, 32768, 0],
[7124, 18686, 25916, 32768, 0],
[4479, 12688, 19340, 32768, 0],
[28385, 32476, 32673, 32768, 0],
[15306, 29005, 31938, 32768, 0],
[8937, 21615, 28322, 32768, 0],
[5982, 15603, 22786, 32768, 0],
[3620, 10267, 16136, 32768, 0],
[27280, 32464, 32667, 32768, 0],
[15607, 29160, 32004, 32768, 0],
[9091, 22135, 28740, 32768, 0],
[6232, 16632, 24020, 32768, 0],
[4047, 11377, 17672, 32768, 0],
[29220, 32630, 32718, 32768, 0],
[19650, 31220, 32462, 32768, 0],
[13050, 26312, 30827, 32768, 0],
[9228, 20870, 27468, 32768, 0],
[6146, 15149, 21971, 32768, 0],
[30169, 32481, 32623, 32768, 0],
[17212, 29311, 31554, 32768, 0],
[9911, 21311, 26882, 32768, 0],
[4487, 13314, 20372, 32768, 0],
[2570, 7772, 12889, 32768, 0],
[30924, 32613, 32708, 32768, 0],
[19490, 30206, 32107, 32768, 0],
[11232, 23998, 29276, 32768, 0],
[6769, 17955, 25035, 32768, 0],
[4398, 12623, 19214, 32768, 0],
[30609, 32627, 32722, 32768, 0],
[19370, 30582, 32287, 32768, 0],
[10457, 23619, 29409, 32768, 0],
[6443, 17637, 24834, 32768, 0],
[4645, 13236, 20106, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8626, 20271, 26216, 32768, 0],
[26707, 32406, 32711, 32768, 0],
[16999, 30329, 32286, 32768, 0],
[11445, 25123, 30286, 32768, 0],
[6411, 18828, 25601, 32768, 0],
[6801, 12458, 20248, 32768, 0],
[29918, 32682, 32748, 32768, 0],
[20649, 31739, 32618, 32768, 0],
[12879, 27773, 31581, 32768, 0],
[7896, 21751, 28244, 32768, 0],
[5260, 14870, 23698, 32768, 0],
[29252, 32593, 32731, 32768, 0],
[17072, 30460, 32294, 32768, 0],
[10653, 24143, 29365, 32768, 0],
[6536, 17490, 23983, 32768, 0],
[4929, 13170, 20085, 32768, 0],
[28137, 32518, 32715, 32768, 0],
[18171, 30784, 32407, 32768, 0],
[11437, 25436, 30459, 32768, 0],
[7252, 18534, 26176, 32768, 0],
[4126, 13353, 20978, 32768, 0],
[31162, 32726, 32748, 32768, 0],
[23017, 32222, 32701, 32768, 0],
[15629, 29233, 32046, 32768, 0],
[9387, 22621, 29480, 32768, 0],
[6922, 17616, 25010, 32768, 0],
[28838, 32265, 32614, 32768, 0],
[19701, 30206, 31920, 32768, 0],
[11214, 22410, 27933, 32768, 0],
[5320, 14177, 23034, 32768, 0],
[5049, 12881, 17827, 32768, 0],
[27484, 32471, 32734, 32768, 0],
[21076, 31526, 32561, 32768, 0],
[12707, 26303, 31211, 32768, 0],
[8169, 21722, 28219, 32768, 0],
[6045, 19406, 27042, 32768, 0],
[27753, 32572, 32745, 32768, 0],
[20832, 31878, 32653, 32768, 0],
[13250, 27356, 31674, 32768, 0],
[7718, 21508, 29858, 32768, 0],
[7209, 18350, 25559, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[7876, 16901, 21741, 32768, 0],
[24001, 31898, 32625, 32768, 0],
[14529, 27959, 31451, 32768, 0],
[8273, 20818, 27258, 32768, 0],
[5278, 14673, 21510, 32768, 0],
[2983, 8843, 14039, 32768, 0],
[28016, 32574, 32732, 32768, 0],
[17471, 30306, 32301, 32768, 0],
[10224, 24063, 29728, 32768, 0],
[6602, 17954, 25052, 32768, 0],
[4002, 11585, 17759, 32768, 0],
[30190, 32634, 32739, 32768, 0],
[17497, 30282, 32270, 32768, 0],
[10229, 23729, 29538, 32768, 0],
[6344, 17211, 24440, 32768, 0],
[3849, 11189, 17108, 32768, 0],
[28570, 32583, 32726, 32768, 0],
[17521, 30161, 32238, 32768, 0],
[10153, 23565, 29378, 32768, 0],
[6455, 17341, 24443, 32768, 0],
[3907, 11042, 17024, 32768, 0],
[30689, 32715, 32748, 32768, 0],
[21546, 31840, 32610, 32768, 0],
[13547, 27581, 31459, 32768, 0],
[8912, 21757, 28309, 32768, 0],
[5548, 15080, 22046, 32768, 0],
[30783, 32540, 32685, 32768, 0],
[17540, 29528, 31668, 32768, 0],
[10160, 21468, 26783, 32768, 0],
[4724, 13393, 20054, 32768, 0],
[2702, 8174, 13102, 32768, 0],
[31648, 32686, 32742, 32768, 0],
[20954, 31094, 32337, 32768, 0],
[12420, 25698, 30179, 32768, 0],
[7304, 19320, 26248, 32768, 0],
[4366, 12261, 18864, 32768, 0],
[31581, 32723, 32748, 32768, 0],
[21373, 31586, 32525, 32768, 0],
[12744, 26625, 30885, 32768, 0],
[7431, 20322, 26950, 32768, 0],
[4692, 13323, 20111, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[7833, 18369, 24095, 32768, 0],
[26650, 32273, 32702, 32768, 0],
[16371, 29961, 32191, 32768, 0],
[11055, 24082, 29629, 32768, 0],
[6892, 18644, 25400, 32768, 0],
[5006, 13057, 19240, 32768, 0],
[29834, 32666, 32748, 32768, 0],
[19577, 31335, 32570, 32768, 0],
[12253, 26509, 31122, 32768, 0],
[7991, 20772, 27711, 32768, 0],
[5677, 15910, 23059, 32768, 0],
[30109, 32532, 32720, 32768, 0],
[16747, 30166, 32252, 32768, 0],
[10134, 23542, 29184, 32768, 0],
[5791, 16176, 23556, 32768, 0],
[4362, 10414, 17284, 32768, 0],
[29492, 32626, 32748, 32768, 0],
[19894, 31402, 32525, 32768, 0],
[12942, 27071, 30869, 32768, 0],
[8346, 21216, 27405, 32768, 0],
[6572, 17087, 23859, 32768, 0],
[32035, 32735, 32748, 32768, 0],
[22957, 31838, 32618, 32768, 0],
[14724, 28572, 31772, 32768, 0],
[10364, 23999, 29553, 32768, 0],
[7004, 18433, 25655, 32768, 0],
[27528, 32277, 32681, 32768, 0],
[16959, 31171, 32096, 32768, 0],
[10486, 23593, 27962, 32768, 0],
[8192, 16384, 23211, 32768, 0],
[8937, 17873, 20852, 32768, 0],
[27715, 32002, 32615, 32768, 0],
[15073, 29491, 31676, 32768, 0],
[11264, 24576, 28672, 32768, 0],
[2341, 18725, 23406, 32768, 0],
[7282, 18204, 25486, 32768, 0],
[28547, 32213, 32657, 32768, 0],
[20788, 29773, 32239, 32768, 0],
[6780, 21469, 30508, 32768, 0],
[5958, 14895, 23831, 32768, 0],
[16384, 21845, 27307, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[5992, 14304, 19765, 32768, 0],
[22612, 31238, 32456, 32768, 0],
[13456, 27162, 31087, 32768, 0],
[8001, 20062, 26504, 32768, 0],
[5168, 14105, 20764, 32768, 0],
[2632, 7771, 12385, 32768, 0],
[27034, 32344, 32709, 32768, 0],
[15850, 29415, 31997, 32768, 0],
[9494, 22776, 28841, 32768, 0],
[6151, 16830, 23969, 32768, 0],
[3461, 10039, 15722, 32768, 0],
[30134, 32569, 32731, 32768, 0],
[15638, 29422, 31945, 32768, 0],
[9150, 21865, 28218, 32768, 0],
[5647, 15719, 22676, 32768, 0],
[3402, 9772, 15477, 32768, 0],
[28530, 32586, 32735, 32768, 0],
[17139, 30298, 32292, 32768, 0],
[10200, 24039, 29685, 32768, 0],
[6419, 17674, 24786, 32768, 0],
[3544, 10225, 15824, 32768, 0],
[31333, 32726, 32748, 32768, 0],
[20618, 31487, 32544, 32768, 0],
[12901, 27217, 31232, 32768, 0],
[8624, 21734, 28171, 32768, 0],
[5104, 14191, 20748, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[11206, 21090, 26561, 32768, 0],
[28759, 32279, 32671, 32768, 0],
[14171, 27952, 31569, 32768, 0],
[9743, 22907, 29141, 32768, 0],
[6871, 17886, 24868, 32768, 0],
[4960, 13152, 19315, 32768, 0],
[31077, 32661, 32748, 32768, 0],
[19400, 31195, 32515, 32768, 0],
[12752, 26858, 31040, 32768, 0],
[8370, 22098, 28591, 32768, 0],
[5457, 15373, 22298, 32768, 0],
[31697, 32706, 32748, 32768, 0],
[17860, 30657, 32333, 32768, 0],
[12510, 24812, 29261, 32768, 0],
[6180, 19124, 24722, 32768, 0],
[5041, 13548, 17959, 32768, 0],
[31552, 32716, 32748, 32768, 0],
[21908, 31769, 32623, 32768, 0],
[14470, 28201, 31565, 32768, 0],
[9493, 22982, 28608, 32768, 0],
[6858, 17240, 24137, 32768, 0],
[32543, 32752, 32756, 32768, 0],
[24286, 32097, 32666, 32768, 0],
[15958, 29217, 32024, 32768, 0],
[10207, 24234, 29958, 32768, 0],
[6929, 18305, 25652, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
[
[
[4137, 10847, 15682, 32768, 0],
[17824, 27001, 30058, 32768, 0],
[10204, 22796, 28291, 32768, 0],
[6076, 15935, 22125, 32768, 0],
[3852, 10937, 16816, 32768, 0],
[2252, 6324, 10131, 32768, 0],
[25840, 32016, 32662, 32768, 0],
[15109, 28268, 31531, 32768, 0],
[9385, 22231, 28340, 32768, 0],
[6082, 16672, 23479, 32768, 0],
[3318, 9427, 14681, 32768, 0],
[30594, 32574, 32718, 32768, 0],
[16836, 29552, 31859, 32768, 0],
[9556, 22542, 28356, 32768, 0],
[6305, 16725, 23540, 32768, 0],
[3376, 9895, 15184, 32768, 0],
[29383, 32617, 32745, 32768, 0],
[18891, 30809, 32401, 32768, 0],
[11688, 25942, 30687, 32768, 0],
[7468, 19469, 26651, 32768, 0],
[3909, 11358, 17012, 32768, 0],
[31564, 32736, 32748, 32768, 0],
[20906, 31611, 32600, 32768, 0],
[13191, 27621, 31537, 32768, 0],
[8768, 22029, 28676, 32768, 0],
[5079, 14109, 20906, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
];
// ---------------------------------------------------------------------
// Round 140 — coefficient `coeff_br` sub-group default CDF (§9.4), the
// LAST of the `coeff_base` / `coeff_base_eob` / `coeff_br` braid reset
// by the §8.3.1 `init_coeff_cdfs` function. `coeff_br` codes the
// per-coefficient base-range increment used to push a level above
// `NUM_BASE_LEVELS`: each read codes a value in `0..BR_CDF_SIZE` (the
// row is `[c0, c1, c2, 32768, 0]` — three cumulative frequencies plus
// the §8.3 adaptation counter), and §5.11.39 stacks
// `COEFF_BASE_RANGE / (BR_CDF_SIZE - 1) == 4` such reads per
// coefficient. Transcribed verbatim from the §9.4 source; the outer
// `COEFF_CDF_Q_CTXS` axis is selected by `base_q_idx` at
// `init_coeff_cdfs`. With this table all three coeff-CDF braids are
// landed; the next gate is the §8.3.2 `get_coeff_base_ctx()` /
// `get_br_ctx()` neighbour-derivation helpers (deferred to a future
// round — they need tile-content walker state).
// ---------------------------------------------------------------------
/// `Default_Coeff_Br_Cdf[ COEFF_CDF_Q_CTXS ][ TX_SIZES ][ PLANE_TYPES ][ LEVEL_CONTEXTS ][ BR_CDF_SIZE + 1 ]`
/// (§9.4). Codes `coeff_br`. Selected at `init_coeff_cdfs` by `idx`
/// (the [`coeff_cdf_q_ctx`] value), then by `Min(txSzCtx, TX_32X32)`,
/// `ptype`, and the `coeff_br` context (the §8.3.2 `get_br_ctx(...)`
/// result, in `0..LEVEL_CONTEXTS`).
///
/// Declared `static` rather than `const` because the table is large
/// (840 5-entry rows = 8400 bytes) and `clippy::large_const_arrays`
/// flags const arrays of this size as a per-use copy hazard. The
/// shape and read semantics are otherwise identical to the other
/// `Default_*_Cdf` arrays in this module.
pub static DEFAULT_COEFF_BR_CDF: [[[[[u16; BR_CDF_SIZE + 1]; LEVEL_CONTEXTS]; PLANE_TYPES];
TX_SIZES]; COEFF_CDF_Q_CTXS] = [
[
[
[
[14298, 20718, 24174, 32768, 0],
[12536, 19601, 23789, 32768, 0],
[8712, 15051, 19503, 32768, 0],
[6170, 11327, 15434, 32768, 0],
[4742, 8926, 12538, 32768, 0],
[3803, 7317, 10546, 32768, 0],
[1696, 3317, 4871, 32768, 0],
[14392, 19951, 22756, 32768, 0],
[15978, 23218, 26818, 32768, 0],
[12187, 19474, 23889, 32768, 0],
[9176, 15640, 20259, 32768, 0],
[7068, 12655, 17028, 32768, 0],
[5656, 10442, 14472, 32768, 0],
[2580, 4992, 7244, 32768, 0],
[12136, 18049, 21426, 32768, 0],
[13784, 20721, 24481, 32768, 0],
[10836, 17621, 21900, 32768, 0],
[8372, 14444, 18847, 32768, 0],
[6523, 11779, 16000, 32768, 0],
[5337, 9898, 13760, 32768, 0],
[3034, 5860, 8462, 32768, 0],
],
[
[15967, 22905, 26286, 32768, 0],
[13534, 20654, 24579, 32768, 0],
[9504, 16092, 20535, 32768, 0],
[6975, 12568, 16903, 32768, 0],
[5364, 10091, 14020, 32768, 0],
[4357, 8370, 11857, 32768, 0],
[2506, 4934, 7218, 32768, 0],
[23032, 28815, 30936, 32768, 0],
[19540, 26704, 29719, 32768, 0],
[15158, 22969, 27097, 32768, 0],
[11408, 18865, 23650, 32768, 0],
[8885, 15448, 20250, 32768, 0],
[7108, 12853, 17416, 32768, 0],
[4231, 8041, 11480, 32768, 0],
[19823, 26490, 29156, 32768, 0],
[18890, 25929, 28932, 32768, 0],
[15660, 23491, 27433, 32768, 0],
[12147, 19776, 24488, 32768, 0],
[9728, 16774, 21649, 32768, 0],
[7919, 14277, 19066, 32768, 0],
[5440, 10170, 14185, 32768, 0],
],
],
[
[
[14406, 20862, 24414, 32768, 0],
[11824, 18907, 23109, 32768, 0],
[8257, 14393, 18803, 32768, 0],
[5860, 10747, 14778, 32768, 0],
[4475, 8486, 11984, 32768, 0],
[3606, 6954, 10043, 32768, 0],
[1736, 3410, 5048, 32768, 0],
[14430, 20046, 22882, 32768, 0],
[15593, 22899, 26709, 32768, 0],
[12102, 19368, 23811, 32768, 0],
[9059, 15584, 20262, 32768, 0],
[6999, 12603, 17048, 32768, 0],
[5684, 10497, 14553, 32768, 0],
[2822, 5438, 7862, 32768, 0],
[15785, 21585, 24359, 32768, 0],
[18347, 25229, 28266, 32768, 0],
[14974, 22487, 26389, 32768, 0],
[11423, 18681, 23271, 32768, 0],
[8863, 15350, 20008, 32768, 0],
[7153, 12852, 17278, 32768, 0],
[3707, 7036, 9982, 32768, 0],
],
[
[15460, 21696, 25469, 32768, 0],
[12170, 19249, 23191, 32768, 0],
[8723, 15027, 19332, 32768, 0],
[6428, 11704, 15874, 32768, 0],
[4922, 9292, 13052, 32768, 0],
[4139, 7695, 11010, 32768, 0],
[2291, 4508, 6598, 32768, 0],
[19856, 26920, 29828, 32768, 0],
[17923, 25289, 28792, 32768, 0],
[14278, 21968, 26297, 32768, 0],
[10910, 18136, 22950, 32768, 0],
[8423, 14815, 19627, 32768, 0],
[6771, 12283, 16774, 32768, 0],
[4074, 7750, 11081, 32768, 0],
[19852, 26074, 28672, 32768, 0],
[19371, 26110, 28989, 32768, 0],
[16265, 23873, 27663, 32768, 0],
[12758, 20378, 24952, 32768, 0],
[10095, 17098, 21961, 32768, 0],
[8250, 14628, 19451, 32768, 0],
[5205, 9745, 13622, 32768, 0],
],
],
[
[
[10563, 16233, 19763, 32768, 0],
[9794, 16022, 19804, 32768, 0],
[6750, 11945, 15759, 32768, 0],
[4963, 9186, 12752, 32768, 0],
[3845, 7435, 10627, 32768, 0],
[3051, 6085, 8834, 32768, 0],
[1311, 2596, 3830, 32768, 0],
[11246, 16404, 19689, 32768, 0],
[12315, 18911, 22731, 32768, 0],
[10557, 17095, 21289, 32768, 0],
[8136, 14006, 18249, 32768, 0],
[6348, 11474, 15565, 32768, 0],
[5196, 9655, 13400, 32768, 0],
[2349, 4526, 6587, 32768, 0],
[13337, 18730, 21569, 32768, 0],
[19306, 26071, 28882, 32768, 0],
[15952, 23540, 27254, 32768, 0],
[12409, 19934, 24430, 32768, 0],
[9760, 16706, 21389, 32768, 0],
[8004, 14220, 18818, 32768, 0],
[4138, 7794, 10961, 32768, 0],
],
[
[10870, 16684, 20949, 32768, 0],
[9664, 15230, 18680, 32768, 0],
[6886, 12109, 15408, 32768, 0],
[4825, 8900, 12305, 32768, 0],
[3630, 7162, 10314, 32768, 0],
[3036, 6429, 9387, 32768, 0],
[1671, 3296, 4940, 32768, 0],
[13819, 19159, 23026, 32768, 0],
[11984, 19108, 23120, 32768, 0],
[10690, 17210, 21663, 32768, 0],
[7984, 14154, 18333, 32768, 0],
[6868, 12294, 16124, 32768, 0],
[5274, 8994, 12868, 32768, 0],
[2988, 5771, 8424, 32768, 0],
[19736, 26647, 29141, 32768, 0],
[18933, 26070, 28984, 32768, 0],
[15779, 23048, 27200, 32768, 0],
[12638, 20061, 24532, 32768, 0],
[10692, 17545, 22220, 32768, 0],
[9217, 15251, 20054, 32768, 0],
[5078, 9284, 12594, 32768, 0],
],
],
[
[
[2331, 3662, 5244, 32768, 0],
[2891, 4771, 6145, 32768, 0],
[4598, 7623, 9729, 32768, 0],
[3520, 6845, 9199, 32768, 0],
[3417, 6119, 9324, 32768, 0],
[2601, 5412, 7385, 32768, 0],
[600, 1173, 1744, 32768, 0],
[7672, 13286, 17469, 32768, 0],
[4232, 7792, 10793, 32768, 0],
[2915, 5317, 7397, 32768, 0],
[2318, 4356, 6152, 32768, 0],
[2127, 4000, 5554, 32768, 0],
[1850, 3478, 5275, 32768, 0],
[977, 1933, 2843, 32768, 0],
[18280, 24387, 27989, 32768, 0],
[15852, 22671, 26185, 32768, 0],
[13845, 20951, 24789, 32768, 0],
[11055, 17966, 22129, 32768, 0],
[9138, 15422, 19801, 32768, 0],
[7454, 13145, 17456, 32768, 0],
[3370, 6393, 9013, 32768, 0],
],
[
[5842, 9229, 10838, 32768, 0],
[2313, 3491, 4276, 32768, 0],
[2998, 6104, 7496, 32768, 0],
[2420, 7447, 9868, 32768, 0],
[3034, 8495, 10923, 32768, 0],
[4076, 8937, 10975, 32768, 0],
[1086, 2370, 3299, 32768, 0],
[9714, 17254, 20444, 32768, 0],
[8543, 13698, 17123, 32768, 0],
[4918, 9007, 11910, 32768, 0],
[4129, 7532, 10553, 32768, 0],
[2364, 5533, 8058, 32768, 0],
[1834, 3546, 5563, 32768, 0],
[1473, 2908, 4133, 32768, 0],
[15405, 21193, 25619, 32768, 0],
[15691, 21952, 26561, 32768, 0],
[12962, 19194, 24165, 32768, 0],
[10272, 17855, 22129, 32768, 0],
[8588, 15270, 20718, 32768, 0],
[8682, 14669, 19500, 32768, 0],
[4870, 9636, 13205, 32768, 0],
],
],
[
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
[
[
[
[14995, 21341, 24749, 32768, 0],
[13158, 20289, 24601, 32768, 0],
[8941, 15326, 19876, 32768, 0],
[6297, 11541, 15807, 32768, 0],
[4817, 9029, 12776, 32768, 0],
[3731, 7273, 10627, 32768, 0],
[1847, 3617, 5354, 32768, 0],
[14472, 19659, 22343, 32768, 0],
[16806, 24162, 27533, 32768, 0],
[12900, 20404, 24713, 32768, 0],
[9411, 16112, 20797, 32768, 0],
[7056, 12697, 17148, 32768, 0],
[5544, 10339, 14460, 32768, 0],
[2954, 5704, 8319, 32768, 0],
[12464, 18071, 21354, 32768, 0],
[15482, 22528, 26034, 32768, 0],
[12070, 19269, 23624, 32768, 0],
[8953, 15406, 20106, 32768, 0],
[7027, 12730, 17220, 32768, 0],
[5887, 10913, 15140, 32768, 0],
[3793, 7278, 10447, 32768, 0],
],
[
[15571, 22232, 25749, 32768, 0],
[14506, 21575, 25374, 32768, 0],
[10189, 17089, 21569, 32768, 0],
[7316, 13301, 17915, 32768, 0],
[5783, 10912, 15190, 32768, 0],
[4760, 9155, 13088, 32768, 0],
[2993, 5966, 8774, 32768, 0],
[23424, 28903, 30778, 32768, 0],
[20775, 27666, 30290, 32768, 0],
[16474, 24410, 28299, 32768, 0],
[12471, 20180, 24987, 32768, 0],
[9410, 16487, 21439, 32768, 0],
[7536, 13614, 18529, 32768, 0],
[5048, 9586, 13549, 32768, 0],
[21090, 27290, 29756, 32768, 0],
[20796, 27402, 30026, 32768, 0],
[17819, 25485, 28969, 32768, 0],
[13860, 21909, 26462, 32768, 0],
[11002, 18494, 23529, 32768, 0],
[8953, 15929, 20897, 32768, 0],
[6448, 11918, 16454, 32768, 0],
],
],
[
[
[15999, 22208, 25449, 32768, 0],
[13050, 19988, 24122, 32768, 0],
[8594, 14864, 19378, 32768, 0],
[6033, 11079, 15238, 32768, 0],
[4554, 8683, 12347, 32768, 0],
[3672, 7139, 10337, 32768, 0],
[1900, 3771, 5576, 32768, 0],
[15788, 21340, 23949, 32768, 0],
[16825, 24235, 27758, 32768, 0],
[12873, 20402, 24810, 32768, 0],
[9590, 16363, 21094, 32768, 0],
[7352, 13209, 17733, 32768, 0],
[5960, 10989, 15184, 32768, 0],
[3232, 6234, 9007, 32768, 0],
[15761, 20716, 23224, 32768, 0],
[19318, 25989, 28759, 32768, 0],
[15529, 23094, 26929, 32768, 0],
[11662, 18989, 23641, 32768, 0],
[8955, 15568, 20366, 32768, 0],
[7281, 13106, 17708, 32768, 0],
[4248, 8059, 11440, 32768, 0],
],
[
[14899, 21217, 24503, 32768, 0],
[13519, 20283, 24047, 32768, 0],
[9429, 15966, 20365, 32768, 0],
[6700, 12355, 16652, 32768, 0],
[5088, 9704, 13716, 32768, 0],
[4243, 8154, 11731, 32768, 0],
[2702, 5364, 7861, 32768, 0],
[22745, 28388, 30454, 32768, 0],
[20235, 27146, 29922, 32768, 0],
[15896, 23715, 27637, 32768, 0],
[11840, 19350, 24131, 32768, 0],
[9122, 15932, 20880, 32768, 0],
[7488, 13581, 18362, 32768, 0],
[5114, 9568, 13370, 32768, 0],
[20845, 26553, 28932, 32768, 0],
[20981, 27372, 29884, 32768, 0],
[17781, 25335, 28785, 32768, 0],
[13760, 21708, 26297, 32768, 0],
[10975, 18415, 23365, 32768, 0],
[9045, 15789, 20686, 32768, 0],
[6130, 11199, 15423, 32768, 0],
],
],
[
[
[13549, 19724, 23158, 32768, 0],
[11844, 18382, 22246, 32768, 0],
[7919, 13619, 17773, 32768, 0],
[5486, 10143, 13946, 32768, 0],
[4166, 7983, 11324, 32768, 0],
[3364, 6506, 9427, 32768, 0],
[1598, 3160, 4674, 32768, 0],
[15281, 20979, 23781, 32768, 0],
[14939, 22119, 25952, 32768, 0],
[11363, 18407, 22812, 32768, 0],
[8609, 14857, 19370, 32768, 0],
[6737, 12184, 16480, 32768, 0],
[5506, 10263, 14262, 32768, 0],
[2990, 5786, 8380, 32768, 0],
[20249, 25253, 27417, 32768, 0],
[21070, 27518, 30001, 32768, 0],
[16854, 24469, 28074, 32768, 0],
[12864, 20486, 25000, 32768, 0],
[9962, 16978, 21778, 32768, 0],
[8074, 14338, 19048, 32768, 0],
[4494, 8479, 11906, 32768, 0],
],
[
[13960, 19617, 22829, 32768, 0],
[11150, 17341, 21228, 32768, 0],
[7150, 12964, 17190, 32768, 0],
[5331, 10002, 13867, 32768, 0],
[4167, 7744, 11057, 32768, 0],
[3480, 6629, 9646, 32768, 0],
[1883, 3784, 5686, 32768, 0],
[18752, 25660, 28912, 32768, 0],
[16968, 24586, 28030, 32768, 0],
[13520, 21055, 25313, 32768, 0],
[10453, 17626, 22280, 32768, 0],
[8386, 14505, 19116, 32768, 0],
[6742, 12595, 17008, 32768, 0],
[4273, 8140, 11499, 32768, 0],
[22120, 27827, 30233, 32768, 0],
[20563, 27358, 29895, 32768, 0],
[17076, 24644, 28153, 32768, 0],
[13362, 20942, 25309, 32768, 0],
[10794, 17965, 22695, 32768, 0],
[9014, 15652, 20319, 32768, 0],
[5708, 10512, 14497, 32768, 0],
],
],
[
[
[5705, 10930, 15725, 32768, 0],
[7946, 12765, 16115, 32768, 0],
[6801, 12123, 16226, 32768, 0],
[5462, 10135, 14200, 32768, 0],
[4189, 8011, 11507, 32768, 0],
[3191, 6229, 9408, 32768, 0],
[1057, 2137, 3212, 32768, 0],
[10018, 17067, 21491, 32768, 0],
[7380, 12582, 16453, 32768, 0],
[6068, 10845, 14339, 32768, 0],
[5098, 9198, 12555, 32768, 0],
[4312, 8010, 11119, 32768, 0],
[3700, 6966, 9781, 32768, 0],
[1693, 3326, 4887, 32768, 0],
[18757, 24930, 27774, 32768, 0],
[17648, 24596, 27817, 32768, 0],
[14707, 22052, 26026, 32768, 0],
[11720, 18852, 23292, 32768, 0],
[9357, 15952, 20525, 32768, 0],
[7810, 13753, 18210, 32768, 0],
[3879, 7333, 10328, 32768, 0],
],
[
[8278, 13242, 15922, 32768, 0],
[10547, 15867, 18919, 32768, 0],
[9106, 15842, 20609, 32768, 0],
[6833, 13007, 17218, 32768, 0],
[4811, 9712, 13923, 32768, 0],
[3985, 7352, 11128, 32768, 0],
[1688, 3458, 5262, 32768, 0],
[12951, 21861, 26510, 32768, 0],
[9788, 16044, 20276, 32768, 0],
[6309, 11244, 14870, 32768, 0],
[5183, 9349, 12566, 32768, 0],
[4389, 8229, 11492, 32768, 0],
[3633, 6945, 10620, 32768, 0],
[3600, 6847, 9907, 32768, 0],
[21748, 28137, 30255, 32768, 0],
[19436, 26581, 29560, 32768, 0],
[16359, 24201, 27953, 32768, 0],
[13961, 21693, 25871, 32768, 0],
[11544, 18686, 23322, 32768, 0],
[9372, 16462, 20952, 32768, 0],
[6138, 11210, 15390, 32768, 0],
],
],
[
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
[
[
[
[16138, 22223, 25509, 32768, 0],
[15347, 22430, 26332, 32768, 0],
[9614, 16736, 21332, 32768, 0],
[6600, 12275, 16907, 32768, 0],
[4811, 9424, 13547, 32768, 0],
[3748, 7809, 11420, 32768, 0],
[2254, 4587, 6890, 32768, 0],
[15196, 20284, 23177, 32768, 0],
[18317, 25469, 28451, 32768, 0],
[13918, 21651, 25842, 32768, 0],
[10052, 17150, 21995, 32768, 0],
[7499, 13630, 18587, 32768, 0],
[6158, 11417, 16003, 32768, 0],
[4014, 7785, 11252, 32768, 0],
[15048, 21067, 24384, 32768, 0],
[18202, 25346, 28553, 32768, 0],
[14302, 22019, 26356, 32768, 0],
[10839, 18139, 23166, 32768, 0],
[8715, 15744, 20806, 32768, 0],
[7536, 13576, 18544, 32768, 0],
[5413, 10335, 14498, 32768, 0],
],
[
[17394, 24501, 27895, 32768, 0],
[15889, 23420, 27185, 32768, 0],
[11561, 19133, 23870, 32768, 0],
[8285, 14812, 19844, 32768, 0],
[6496, 12043, 16550, 32768, 0],
[4771, 9574, 13677, 32768, 0],
[3603, 6830, 10144, 32768, 0],
[21656, 27704, 30200, 32768, 0],
[21324, 27915, 30511, 32768, 0],
[17327, 25336, 28997, 32768, 0],
[13417, 21381, 26033, 32768, 0],
[10132, 17425, 22338, 32768, 0],
[8580, 15016, 19633, 32768, 0],
[5694, 11477, 16411, 32768, 0],
[24116, 29780, 31450, 32768, 0],
[23853, 29695, 31591, 32768, 0],
[20085, 27614, 30428, 32768, 0],
[15326, 24335, 28575, 32768, 0],
[11814, 19472, 24810, 32768, 0],
[10221, 18611, 24767, 32768, 0],
[7689, 14558, 20321, 32768, 0],
],
],
[
[
[16214, 22380, 25770, 32768, 0],
[14213, 21304, 25295, 32768, 0],
[9213, 15823, 20455, 32768, 0],
[6395, 11758, 16139, 32768, 0],
[4779, 9187, 13066, 32768, 0],
[3821, 7501, 10953, 32768, 0],
[2293, 4567, 6795, 32768, 0],
[15859, 21283, 23820, 32768, 0],
[18404, 25602, 28726, 32768, 0],
[14325, 21980, 26206, 32768, 0],
[10669, 17937, 22720, 32768, 0],
[8297, 14642, 19447, 32768, 0],
[6746, 12389, 16893, 32768, 0],
[4324, 8251, 11770, 32768, 0],
[16532, 21631, 24475, 32768, 0],
[20667, 27150, 29668, 32768, 0],
[16728, 24510, 28175, 32768, 0],
[12861, 20645, 25332, 32768, 0],
[10076, 17361, 22417, 32768, 0],
[8395, 14940, 19963, 32768, 0],
[5731, 10683, 14912, 32768, 0],
],
[
[14433, 21155, 24938, 32768, 0],
[14658, 21716, 25545, 32768, 0],
[9923, 16824, 21557, 32768, 0],
[6982, 13052, 17721, 32768, 0],
[5419, 10503, 15050, 32768, 0],
[4852, 9162, 13014, 32768, 0],
[3271, 6395, 9630, 32768, 0],
[22210, 27833, 30109, 32768, 0],
[20750, 27368, 29821, 32768, 0],
[16894, 24828, 28573, 32768, 0],
[13247, 21276, 25757, 32768, 0],
[10038, 17265, 22563, 32768, 0],
[8587, 14947, 20327, 32768, 0],
[5645, 11371, 15252, 32768, 0],
[22027, 27526, 29714, 32768, 0],
[23098, 29146, 31221, 32768, 0],
[19886, 27341, 30272, 32768, 0],
[15609, 23747, 28046, 32768, 0],
[11993, 20065, 24939, 32768, 0],
[9637, 18267, 23671, 32768, 0],
[7625, 13801, 19144, 32768, 0],
],
],
[
[
[14438, 20798, 24089, 32768, 0],
[12621, 19203, 23097, 32768, 0],
[8177, 14125, 18402, 32768, 0],
[5674, 10501, 14456, 32768, 0],
[4236, 8239, 11733, 32768, 0],
[3447, 6750, 9806, 32768, 0],
[1986, 3950, 5864, 32768, 0],
[16208, 22099, 24930, 32768, 0],
[16537, 24025, 27585, 32768, 0],
[12780, 20381, 24867, 32768, 0],
[9767, 16612, 21416, 32768, 0],
[7686, 13738, 18398, 32768, 0],
[6333, 11614, 15964, 32768, 0],
[3941, 7571, 10836, 32768, 0],
[22819, 27422, 29202, 32768, 0],
[22224, 28514, 30721, 32768, 0],
[17660, 25433, 28913, 32768, 0],
[13574, 21482, 26002, 32768, 0],
[10629, 17977, 22938, 32768, 0],
[8612, 15298, 20265, 32768, 0],
[5607, 10491, 14596, 32768, 0],
],
[
[13569, 19800, 23206, 32768, 0],
[13128, 19924, 23869, 32768, 0],
[8329, 14841, 19403, 32768, 0],
[6130, 10976, 15057, 32768, 0],
[4682, 8839, 12518, 32768, 0],
[3656, 7409, 10588, 32768, 0],
[2577, 5099, 7412, 32768, 0],
[22427, 28684, 30585, 32768, 0],
[20913, 27750, 30139, 32768, 0],
[15840, 24109, 27834, 32768, 0],
[12308, 20029, 24569, 32768, 0],
[10216, 16785, 21458, 32768, 0],
[8309, 14203, 19113, 32768, 0],
[6043, 11168, 15307, 32768, 0],
[23166, 28901, 30998, 32768, 0],
[21899, 28405, 30751, 32768, 0],
[18413, 26091, 29443, 32768, 0],
[15233, 23114, 27352, 32768, 0],
[12683, 20472, 25288, 32768, 0],
[10702, 18259, 23409, 32768, 0],
[8125, 14464, 19226, 32768, 0],
],
],
[
[
[9040, 14786, 18360, 32768, 0],
[9979, 15718, 19415, 32768, 0],
[7913, 13918, 18311, 32768, 0],
[5859, 10889, 15184, 32768, 0],
[4593, 8677, 12510, 32768, 0],
[3820, 7396, 10791, 32768, 0],
[1730, 3471, 5192, 32768, 0],
[11803, 18365, 22709, 32768, 0],
[11419, 18058, 22225, 32768, 0],
[9418, 15774, 20243, 32768, 0],
[7539, 13325, 17657, 32768, 0],
[6233, 11317, 15384, 32768, 0],
[5137, 9656, 13545, 32768, 0],
[2977, 5774, 8349, 32768, 0],
[21207, 27246, 29640, 32768, 0],
[19547, 26578, 29497, 32768, 0],
[16169, 23871, 27690, 32768, 0],
[12820, 20458, 25018, 32768, 0],
[10224, 17332, 22214, 32768, 0],
[8526, 15048, 19884, 32768, 0],
[5037, 9410, 13118, 32768, 0],
],
[
[12339, 17329, 20140, 32768, 0],
[13505, 19895, 23225, 32768, 0],
[9847, 16944, 21564, 32768, 0],
[7280, 13256, 18348, 32768, 0],
[4712, 10009, 14454, 32768, 0],
[4361, 7914, 12477, 32768, 0],
[2870, 5628, 7995, 32768, 0],
[20061, 25504, 28526, 32768, 0],
[15235, 22878, 26145, 32768, 0],
[12985, 19958, 24155, 32768, 0],
[9782, 16641, 21403, 32768, 0],
[9456, 16360, 20760, 32768, 0],
[6855, 12940, 18557, 32768, 0],
[5661, 10564, 15002, 32768, 0],
[25656, 30602, 31894, 32768, 0],
[22570, 29107, 31092, 32768, 0],
[18917, 26423, 29541, 32768, 0],
[15940, 23649, 27754, 32768, 0],
[12803, 20581, 25219, 32768, 0],
[11082, 18695, 23376, 32768, 0],
[7939, 14373, 19005, 32768, 0],
],
],
[
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
[
[
[
[18315, 24289, 27551, 32768, 0],
[16854, 24068, 27835, 32768, 0],
[10140, 17927, 23173, 32768, 0],
[6722, 12982, 18267, 32768, 0],
[4661, 9826, 14706, 32768, 0],
[3832, 8165, 12294, 32768, 0],
[2795, 6098, 9245, 32768, 0],
[17145, 23326, 26672, 32768, 0],
[20733, 27680, 30308, 32768, 0],
[16032, 24461, 28546, 32768, 0],
[11653, 20093, 25081, 32768, 0],
[9290, 16429, 22086, 32768, 0],
[7796, 14598, 19982, 32768, 0],
[6502, 12378, 17441, 32768, 0],
[21681, 27732, 30320, 32768, 0],
[22389, 29044, 31261, 32768, 0],
[19027, 26731, 30087, 32768, 0],
[14739, 23755, 28624, 32768, 0],
[11358, 20778, 25511, 32768, 0],
[10995, 18073, 24190, 32768, 0],
[9162, 14990, 20617, 32768, 0],
],
[
[21425, 27952, 30388, 32768, 0],
[18062, 25838, 29034, 32768, 0],
[11956, 19881, 24808, 32768, 0],
[7718, 15000, 20980, 32768, 0],
[5702, 11254, 16143, 32768, 0],
[4898, 9088, 16864, 32768, 0],
[3679, 6776, 11907, 32768, 0],
[23294, 30160, 31663, 32768, 0],
[24397, 29896, 31836, 32768, 0],
[19245, 27128, 30593, 32768, 0],
[13202, 19825, 26404, 32768, 0],
[11578, 19297, 23957, 32768, 0],
[8073, 13297, 21370, 32768, 0],
[5461, 10923, 19745, 32768, 0],
[27367, 30521, 31934, 32768, 0],
[24904, 30671, 31940, 32768, 0],
[23075, 28460, 31299, 32768, 0],
[14400, 23658, 30417, 32768, 0],
[13885, 23882, 28325, 32768, 0],
[14746, 22938, 27853, 32768, 0],
[5461, 16384, 27307, 32768, 0],
],
],
[
[
[18274, 24813, 27890, 32768, 0],
[15537, 23149, 27003, 32768, 0],
[9449, 16740, 21827, 32768, 0],
[6700, 12498, 17261, 32768, 0],
[4988, 9866, 14198, 32768, 0],
[4236, 8147, 11902, 32768, 0],
[2867, 5860, 8654, 32768, 0],
[17124, 23171, 26101, 32768, 0],
[20396, 27477, 30148, 32768, 0],
[16573, 24629, 28492, 32768, 0],
[12749, 20846, 25674, 32768, 0],
[10233, 17878, 22818, 32768, 0],
[8525, 15332, 20363, 32768, 0],
[6283, 11632, 16255, 32768, 0],
[20466, 26511, 29286, 32768, 0],
[23059, 29174, 31191, 32768, 0],
[19481, 27263, 30241, 32768, 0],
[15458, 23631, 28137, 32768, 0],
[12416, 20608, 25693, 32768, 0],
[10261, 18011, 23261, 32768, 0],
[8016, 14655, 19666, 32768, 0],
],
[
[17616, 24586, 28112, 32768, 0],
[15809, 23299, 27155, 32768, 0],
[10767, 18890, 23793, 32768, 0],
[7727, 14255, 18865, 32768, 0],
[6129, 11926, 16882, 32768, 0],
[4482, 9704, 14861, 32768, 0],
[3277, 7452, 11522, 32768, 0],
[22956, 28551, 30730, 32768, 0],
[22724, 28937, 30961, 32768, 0],
[18467, 26324, 29580, 32768, 0],
[13234, 20713, 25649, 32768, 0],
[11181, 17592, 22481, 32768, 0],
[8291, 18358, 24576, 32768, 0],
[7568, 11881, 14984, 32768, 0],
[24948, 29001, 31147, 32768, 0],
[25674, 30619, 32151, 32768, 0],
[20841, 26793, 29603, 32768, 0],
[14669, 24356, 28666, 32768, 0],
[11334, 23593, 28219, 32768, 0],
[8922, 14762, 22873, 32768, 0],
[8301, 13544, 20535, 32768, 0],
],
],
[
[
[17113, 23733, 27081, 32768, 0],
[14139, 21406, 25452, 32768, 0],
[8552, 15002, 19776, 32768, 0],
[5871, 11120, 15378, 32768, 0],
[4455, 8616, 12253, 32768, 0],
[3469, 6910, 10386, 32768, 0],
[2255, 4553, 6782, 32768, 0],
[18224, 24376, 27053, 32768, 0],
[19290, 26710, 29614, 32768, 0],
[14936, 22991, 27184, 32768, 0],
[11238, 18951, 23762, 32768, 0],
[8786, 15617, 20588, 32768, 0],
[7317, 13228, 18003, 32768, 0],
[5101, 9512, 13493, 32768, 0],
[22639, 28222, 30210, 32768, 0],
[23216, 29331, 31307, 32768, 0],
[19075, 26762, 29895, 32768, 0],
[15014, 23113, 27457, 32768, 0],
[11938, 19857, 24752, 32768, 0],
[9942, 17280, 22282, 32768, 0],
[7167, 13144, 17752, 32768, 0],
],
[
[15820, 22738, 26488, 32768, 0],
[13530, 20885, 25216, 32768, 0],
[8395, 15530, 20452, 32768, 0],
[6574, 12321, 16380, 32768, 0],
[5353, 10419, 14568, 32768, 0],
[4613, 8446, 12381, 32768, 0],
[3440, 7158, 9903, 32768, 0],
[24247, 29051, 31224, 32768, 0],
[22118, 28058, 30369, 32768, 0],
[16498, 24768, 28389, 32768, 0],
[12920, 21175, 26137, 32768, 0],
[10730, 18619, 25352, 32768, 0],
[10187, 16279, 22791, 32768, 0],
[9310, 14631, 22127, 32768, 0],
[24970, 30558, 32057, 32768, 0],
[24801, 29942, 31698, 32768, 0],
[22432, 28453, 30855, 32768, 0],
[19054, 25680, 29580, 32768, 0],
[14392, 23036, 28109, 32768, 0],
[12495, 20947, 26650, 32768, 0],
[12442, 20326, 26214, 32768, 0],
],
],
[
[
[12162, 18785, 22648, 32768, 0],
[12749, 19697, 23806, 32768, 0],
[8580, 15297, 20346, 32768, 0],
[6169, 11749, 16543, 32768, 0],
[4836, 9391, 13448, 32768, 0],
[3821, 7711, 11613, 32768, 0],
[2228, 4601, 7070, 32768, 0],
[16319, 24725, 28280, 32768, 0],
[15698, 23277, 27168, 32768, 0],
[12726, 20368, 25047, 32768, 0],
[9912, 17015, 21976, 32768, 0],
[7888, 14220, 19179, 32768, 0],
[6777, 12284, 17018, 32768, 0],
[4492, 8590, 12252, 32768, 0],
[23249, 28904, 30947, 32768, 0],
[21050, 27908, 30512, 32768, 0],
[17440, 25340, 28949, 32768, 0],
[14059, 22018, 26541, 32768, 0],
[11288, 18903, 23898, 32768, 0],
[9411, 16342, 21428, 32768, 0],
[6278, 11588, 15944, 32768, 0],
],
[
[13981, 20067, 23226, 32768, 0],
[16922, 23580, 26783, 32768, 0],
[11005, 19039, 24487, 32768, 0],
[7389, 14218, 19798, 32768, 0],
[5598, 11505, 17206, 32768, 0],
[6090, 11213, 15659, 32768, 0],
[3820, 7371, 10119, 32768, 0],
[21082, 26925, 29675, 32768, 0],
[21262, 28627, 31128, 32768, 0],
[18392, 26454, 30437, 32768, 0],
[14870, 22910, 27096, 32768, 0],
[12620, 19484, 24908, 32768, 0],
[9290, 16553, 22802, 32768, 0],
[6668, 14288, 20004, 32768, 0],
[27704, 31055, 31949, 32768, 0],
[24709, 29978, 31788, 32768, 0],
[21668, 29264, 31657, 32768, 0],
[18295, 26968, 30074, 32768, 0],
[16399, 24422, 29313, 32768, 0],
[14347, 23026, 28104, 32768, 0],
[12370, 19806, 24477, 32768, 0],
],
],
[
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
[
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
[8192, 16384, 24576, 32768, 0],
],
],
],
];
// ---------------------------------------------------------------------
// §8.3.1 init-from-defaults: the per-tile working CDF set.
// ---------------------------------------------------------------------
/// The per-tile working set of CDF arrays for the intra-frame mode /
/// partition subset, as set up by §8.3.1 ("each `Tile*Cdf` array is set
/// equal to a copy of `Default_*_Cdf`").
///
/// Field names mirror the spec's `Tile*Cdf` arrays with the `Tile`
/// prefix dropped (the prefix only distinguishes the per-tile copy from
/// the immutable `Default_*` source). Each array is mutated in place by
/// [`crate::symbol_decoder::SymbolDecoder::read_symbol`] as it adapts.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TileCdfContext {
/// `TileIntraFrameYModeCdf` (§8.3.1).
pub intra_frame_y_mode: [[[u16; INTRA_MODES + 1]; INTRA_MODE_CONTEXTS]; INTRA_MODE_CONTEXTS],
/// `TilePartitionW8Cdf` (§8.3.1).
pub partition_w8: [[u16; 5]; PARTITION_CONTEXTS],
/// `TilePartitionW16Cdf` (§8.3.1).
pub partition_w16: [[u16; 11]; PARTITION_CONTEXTS],
/// `TilePartitionW32Cdf` (§8.3.1).
pub partition_w32: [[u16; 11]; PARTITION_CONTEXTS],
/// `TilePartitionW64Cdf` (§8.3.1).
pub partition_w64: [[u16; 11]; PARTITION_CONTEXTS],
/// `TilePartitionW128Cdf` (§8.3.1).
pub partition_w128: [[u16; 9]; PARTITION_CONTEXTS],
/// `TileSkipCdf` (§8.3.1).
pub skip: [[u16; 3]; SKIP_CONTEXTS],
/// `TileSegmentIdCdf` (§8.3.1).
pub segment_id: [[u16; MAX_SEGMENTS + 1]; SEGMENT_ID_CONTEXTS],
/// `TileSegmentIdPredictedCdf` (§8.3.1 — av1-spec p.155). Codes the
/// binary `seg_id_predicted` symbol per §5.11.19 (the
/// `segmentation_temporal_update == 1` arm of `inter_segment_id`).
pub segment_id_predicted: [[u16; 3]; SEGMENT_ID_PREDICTED_CONTEXTS],
// -----------------------------------------------------------------
// Round 17 — motion-vector working CDFs. §8.3.1 enumerates each as
// "`Mv*Cdf[ i ]` is set equal to a copy of `Default_Mv_*_Cdf` for
// `i = 0..MV_CONTEXTS - 1`" (with the inner `comp = 0..1` axis
// either replicated or carried by the source default).
// -----------------------------------------------------------------
/// `TileMvJointCdf[ MV_CONTEXTS ]` (§8.3.1).
pub mv_joint: [[u16; MV_JOINTS + 1]; MV_CONTEXTS],
/// `TileMvSignCdf[ MV_CONTEXTS ][ 2 ]` (§8.3.1). The `2` is
/// `comp = 0..1`.
pub mv_sign: [[[u16; 3]; MV_COMPS]; MV_CONTEXTS],
/// `TileMvClassCdf[ MV_CONTEXTS ][ 2 ]` (§8.3.1).
pub mv_class: [[[u16; MV_CLASSES + 1]; MV_COMPS]; MV_CONTEXTS],
/// `TileMvClass0BitCdf[ MV_CONTEXTS ][ 2 ]` (§8.3.1).
pub mv_class0_bit: [[[u16; 3]; MV_COMPS]; MV_CONTEXTS],
/// `TileMvClass0FrCdf[ MV_CONTEXTS ][ 2 ][ CLASS0_SIZE ]` (§8.3.1).
/// The §5.11.32 selection indexes by `[ MvCtx ][ comp ][ mv_class0_bit ]`.
pub mv_class0_fr: [[[[u16; MV_JOINTS + 1]; CLASS0_SIZE]; MV_COMPS]; MV_CONTEXTS],
/// `TileMvClass0HpCdf[ MV_CONTEXTS ][ 2 ]` (§8.3.1).
pub mv_class0_hp: [[[u16; 3]; MV_COMPS]; MV_CONTEXTS],
/// `TileMvBitCdf[ MV_CONTEXTS ][ 2 ][ MV_OFFSET_BITS ]` (§8.3.1).
/// Selection: `[ MvCtx ][ comp ][ i ]`.
pub mv_bit: [[[[u16; 3]; MV_OFFSET_BITS]; MV_COMPS]; MV_CONTEXTS],
/// `TileMvFrCdf[ MV_CONTEXTS ][ 2 ]` (§8.3.1).
pub mv_fr: [[[u16; MV_JOINTS + 1]; MV_COMPS]; MV_CONTEXTS],
/// `TileMvHpCdf[ MV_CONTEXTS ][ 2 ]` (§8.3.1).
pub mv_hp: [[[u16; 3]; MV_COMPS]; MV_CONTEXTS],
// -----------------------------------------------------------------
// Round 18 — inter-mode / reference-frame working CDFs. §8.3.1
// enumerates each as "`*Cdf` is set to a copy of `Default_*_Cdf`".
// -----------------------------------------------------------------
/// `TileNewMvCdf[ NEW_MV_CONTEXTS ]` (§8.3.1).
pub new_mv: [[u16; 3]; NEW_MV_CONTEXTS],
/// `TileZeroMvCdf[ ZERO_MV_CONTEXTS ]` (§8.3.1).
pub zero_mv: [[u16; 3]; ZERO_MV_CONTEXTS],
/// `TileRefMvCdf[ REF_MV_CONTEXTS ]` (§8.3.1).
pub ref_mv: [[u16; 3]; REF_MV_CONTEXTS],
/// `TileDrlModeCdf[ DRL_MODE_CONTEXTS ]` (§8.3.1).
pub drl_mode: [[u16; 3]; DRL_MODE_CONTEXTS],
/// `TileIsInterCdf[ IS_INTER_CONTEXTS ]` (§8.3.1).
pub is_inter: [[u16; 3]; IS_INTER_CONTEXTS],
/// `TileCompModeCdf[ COMP_INTER_CONTEXTS ]` (§8.3.1).
pub comp_mode: [[u16; 3]; COMP_INTER_CONTEXTS],
/// `TileSkipModeCdf[ SKIP_MODE_CONTEXTS ]` (§8.3.1).
pub skip_mode: [[u16; 3]; SKIP_MODE_CONTEXTS],
/// `TileIntrabcCdf` (§8.3.1, av1-spec p.354). Codes `use_intrabc`
/// per §5.11.7 / §8.3.2; single CDF row (no context index — see
/// [`DEFAULT_INTRABC_CDF`]).
pub intrabc: [u16; 3],
/// `TileDeltaQCdf` (§8.3.1, av1-spec p.382). Codes `delta_q_abs`
/// per §5.11.12 / §8.3.2; single CDF row (no context index).
pub delta_q: [u16; DELTA_Q_SMALL + 2],
/// `TileDeltaLFCdf` (§8.3.1, av1-spec p.382). Codes
/// `delta_lf_abs` per §5.11.13 / §8.3.2 in the
/// `delta_lf_multi == 0` branch; single CDF row (no context
/// index).
pub delta_lf: [u16; DELTA_LF_SMALL + 2],
/// `TileDeltaLFMultiCdf[ FRAME_LF_COUNT ]` (§8.3.1, av1-spec
/// p.382). Codes `delta_lf_abs` per §5.11.13 / §8.3.2 in the
/// `delta_lf_multi == 1` branch — one CDF row per loop-filter
/// strength index `i ∈ 0..FRAME_LF_COUNT`. The §8.3.1 init step
/// initialises every row from [`DEFAULT_DELTA_LF_CDF`].
pub delta_lf_multi: [[u16; DELTA_LF_SMALL + 2]; FRAME_LF_COUNT],
/// `TileCompRefCdf[ REF_CONTEXTS ][ FWD_REFS - 1 ]` (§8.3.1).
pub comp_ref: [[[u16; 3]; FWD_REFS - 1]; REF_CONTEXTS],
/// `TileCompBwdRefCdf[ REF_CONTEXTS ][ BWD_REFS - 1 ]` (§8.3.1).
pub comp_bwd_ref: [[[u16; 3]; BWD_REFS - 1]; REF_CONTEXTS],
/// `TileSingleRefCdf[ REF_CONTEXTS ][ SINGLE_REFS - 1 ]` (§8.3.1).
pub single_ref: [[[u16; 3]; SINGLE_REFS - 1]; REF_CONTEXTS],
/// `TileCompoundModeCdf[ COMPOUND_MODE_CONTEXTS ]` (§8.3.1).
pub compound_mode: [[u16; COMPOUND_MODES + 1]; COMPOUND_MODE_CONTEXTS],
/// `TileCompRefTypeCdf[ COMP_REF_TYPE_CONTEXTS ]` (§8.3.1).
pub comp_ref_type: [[u16; 3]; COMP_REF_TYPE_CONTEXTS],
/// `TileUniCompRefCdf[ REF_CONTEXTS ][ UNIDIR_COMP_REFS - 1 ]`
/// (§8.3.1).
pub uni_comp_ref: [[[u16; 3]; UNIDIR_COMP_REFS - 1]; REF_CONTEXTS],
// -----------------------------------------------------------------
// Round 19 — palette / filter-intra / CFL working CDFs. §8.3.1
// enumerates each as "`*Cdf` is set to a copy of `Default_*_Cdf`"
// (no per-context broadcast for this group).
// -----------------------------------------------------------------
/// `TileFilterIntraModeCdf` (§8.3.1).
pub filter_intra_mode: [u16; INTRA_FILTER_MODES + 1],
/// `TileFilterIntraCdf[ BLOCK_SIZES ]` (§8.3.1).
pub filter_intra: [[u16; 3]; BLOCK_SIZES],
/// `TilePaletteYModeCdf[ PALETTE_BLOCK_SIZE_CONTEXTS ][ PALETTE_Y_MODE_CONTEXTS ]`
/// (§8.3.1).
pub palette_y_mode: [[[u16; 3]; PALETTE_Y_MODE_CONTEXTS]; PALETTE_BLOCK_SIZE_CONTEXTS],
/// `TilePaletteUVModeCdf[ PALETTE_UV_MODE_CONTEXTS ]` (§8.3.1).
pub palette_uv_mode: [[u16; 3]; PALETTE_UV_MODE_CONTEXTS],
/// `TilePaletteYSizeCdf[ PALETTE_BLOCK_SIZE_CONTEXTS ]` (§8.3.1).
pub palette_y_size: [[u16; PALETTE_SIZES + 1]; PALETTE_BLOCK_SIZE_CONTEXTS],
/// `TilePaletteUVSizeCdf[ PALETTE_BLOCK_SIZE_CONTEXTS ]` (§8.3.1).
pub palette_uv_size: [[u16; PALETTE_SIZES + 1]; PALETTE_BLOCK_SIZE_CONTEXTS],
/// `TilePaletteSize2YColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_2_y_color: [[u16; 3]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize3YColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_3_y_color: [[u16; 4]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize4YColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_4_y_color: [[u16; 5]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize5YColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_5_y_color: [[u16; 6]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize6YColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_6_y_color: [[u16; 7]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize7YColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_7_y_color: [[u16; 8]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize8YColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_8_y_color: [[u16; 9]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize2UVColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_2_uv_color: [[u16; 3]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize3UVColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_3_uv_color: [[u16; 4]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize4UVColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_4_uv_color: [[u16; 5]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize5UVColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_5_uv_color: [[u16; 6]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize6UVColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_6_uv_color: [[u16; 7]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize7UVColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_7_uv_color: [[u16; 8]; PALETTE_COLOR_CONTEXTS],
/// `TilePaletteSize8UVColorCdf[ PALETTE_COLOR_CONTEXTS ]` (§8.3.1).
pub palette_size_8_uv_color: [[u16; 9]; PALETTE_COLOR_CONTEXTS],
/// `TileCflSignCdf` (§8.3.1).
pub cfl_sign: [u16; CFL_JOINT_SIGNS + 1],
/// `TileCflAlphaCdf[ CFL_ALPHA_CONTEXTS ]` (§8.3.1).
pub cfl_alpha: [[u16; CFL_ALPHABET_SIZE + 1]; CFL_ALPHA_CONTEXTS],
// -----------------------------------------------------------------
// Round 20 — transform-size working CDFs. §8.3.1 lists each as
// "`Tx*Cdf` is set equal to a copy of `Default_Tx_*_Cdf`" and
// "`TxfmSplitCdf` is set equal to a copy of
// `Default_Txfm_Split_Cdf`".
// -----------------------------------------------------------------
/// `TileTx8x8Cdf[ TX_SIZE_CONTEXTS ]` (§8.3.1). Selected for
/// `tx_depth` when `maxTxDepth == 1`.
pub tx_8x8: [[u16; MAX_TX_DEPTH + 1]; TX_SIZE_CONTEXTS],
/// `TileTx16x16Cdf[ TX_SIZE_CONTEXTS ]` (§8.3.1). Selected for
/// `tx_depth` when `maxTxDepth == 2`.
pub tx_16x16: [[u16; MAX_TX_DEPTH + 2]; TX_SIZE_CONTEXTS],
/// `TileTx32x32Cdf[ TX_SIZE_CONTEXTS ]` (§8.3.1). Selected for
/// `tx_depth` when `maxTxDepth == 3`.
pub tx_32x32: [[u16; MAX_TX_DEPTH + 2]; TX_SIZE_CONTEXTS],
/// `TileTx64x64Cdf[ TX_SIZE_CONTEXTS ]` (§8.3.1). Selected for
/// `tx_depth` when `maxTxDepth == 4`.
pub tx_64x64: [[u16; MAX_TX_DEPTH + 2]; TX_SIZE_CONTEXTS],
/// `TileTxfmSplitCdf[ TXFM_PARTITION_CONTEXTS ]` (§8.3.1). Codes
/// `txfm_split`.
pub txfm_split: [[u16; 3]; TXFM_PARTITION_CONTEXTS],
// -----------------------------------------------------------------
// Round 21 — inter-frame transform-type group. §8.3.1 enumerates
// each as "`InterTxTypeSet{n}Cdf` is set equal to a copy of
// `Default_Inter_Tx_Type_Set{n}_Cdf`".
// -----------------------------------------------------------------
/// `TileInterTxTypeSet1Cdf[ INTER_TX_TYPE_SET1_SIZES ]` (§8.3.1).
/// Selected for `inter_tx_type` when `set == TX_SET_INTER_1`.
pub inter_tx_type_set1: [[u16; TX_TYPES + 1]; INTER_TX_TYPE_SET1_SIZES],
/// `TileInterTxTypeSet2Cdf` (§8.3.1). Selected for `inter_tx_type`
/// when `set == TX_SET_INTER_2`. Single row — no per-`Tx_Size_Sqr`
/// axis (only `TX_16X16` reaches this set).
pub inter_tx_type_set2: [u16; TX_TYPES_SET2 + 1],
/// `TileInterTxTypeSet3Cdf[ INTER_TX_TYPE_SET3_SIZES ]` (§8.3.1).
/// Selected for `inter_tx_type` when `set == TX_SET_INTER_3`.
pub inter_tx_type_set3: [[u16; TX_TYPES_SET3 + 1]; INTER_TX_TYPE_SET3_SIZES],
// -----------------------------------------------------------------
// Round 137 — intra-frame transform-type group. §8.3.1 enumerates
// each as "`IntraTxTypeSet{n}Cdf` is set equal to a copy of
// `Default_Intra_Tx_Type_Set{n}_Cdf`". Both tables carry an extra
// `intraDir` axis (`INTRA_MODES`) on top of the `Tx_Size_Sqr` axis
// already seen in the inter counterparts.
// -----------------------------------------------------------------
/// `TileIntraTxTypeSet1Cdf[ INTRA_TX_TYPE_SET1_SIZES ][ INTRA_MODES ]`
/// (§8.3.1). Selected for `intra_tx_type` when `set == TX_SET_INTRA_1`.
pub intra_tx_type_set1:
[[[u16; TX_TYPES_INTRA_SET1 + 1]; INTRA_MODES]; INTRA_TX_TYPE_SET1_SIZES],
/// `TileIntraTxTypeSet2Cdf[ INTRA_TX_TYPE_SET2_SIZES ][ INTRA_MODES ]`
/// (§8.3.1). Selected for `intra_tx_type` when `set == TX_SET_INTRA_2`.
pub intra_tx_type_set2:
[[[u16; TX_TYPES_INTRA_SET2 + 1]; INTRA_MODES]; INTRA_TX_TYPE_SET2_SIZES],
// -----------------------------------------------------------------
// Round 22 — inter-frame interpolation-filter group. §8.3.1
// enumerates as "`InterpFilterCdf` is set equal to a copy of
// `Default_Interp_Filter_Cdf`".
// -----------------------------------------------------------------
/// `TileInterpFilterCdf[ INTERP_FILTER_CONTEXTS ]` (§8.3.1). Codes
/// `interp_filter` (the switchable-filter selection).
pub interp_filter: [[u16; INTERP_FILTERS + 1]; INTERP_FILTER_CONTEXTS],
// -----------------------------------------------------------------
// Round 23 — motion-mode group. §8.3.1 enumerates as
// "`MotionModeCdf` is set to a copy of `Default_Motion_Mode_Cdf`".
// -----------------------------------------------------------------
/// `TileMotionModeCdf[ BLOCK_SIZES ]` (§8.3.1). Codes `motion_mode`
/// (§5.11.x `read_motion_mode`), selected by `MiSize`.
pub motion_mode: [[u16; MOTION_MODES + 1]; BLOCK_SIZES],
/// `TileUseObmcCdf[ BLOCK_SIZES ]` (§8.3.1). Codes the binary
/// `use_obmc` (§5.11.27 — the `force_integer_mv || NumSamples == 0
/// || !allow_warped_motion || is_scaled(RefFrame[0])` arm), selected
/// by `MiSize`.
pub use_obmc: [[u16; 3]; BLOCK_SIZES],
// -----------------------------------------------------------------
// Round 24 — compound-prediction group. §8.3.1 enumerates these as
// "`CompGroupIdxCdf` is set to a copy of `Default_Comp_Group_Idx_Cdf`",
// "`CompoundIdxCdf` is set to a copy of `Default_Compound_Idx_Cdf`",
// and "`CompoundTypeCdf` is set to a copy of
// `Default_Compound_Type_Cdf`".
// -----------------------------------------------------------------
/// `TileCompGroupIdxCdf[ COMP_GROUP_IDX_CONTEXTS ]` (§8.3.1). Codes
/// the binary `comp_group_idx` (§5.11.x `read_compound_type`),
/// selected by a precomputed `ctx`.
pub comp_group_idx: [[u16; 3]; COMP_GROUP_IDX_CONTEXTS],
/// `TileCompoundIdxCdf[ COMPOUND_IDX_CONTEXTS ]` (§8.3.1). Codes the
/// binary `compound_idx` (§5.11.x `read_compound_type`), selected by
/// a precomputed `ctx`.
pub compound_idx: [[u16; 3]; COMPOUND_IDX_CONTEXTS],
/// `TileCompoundTypeCdf[ BLOCK_SIZES ]` (§8.3.1). Codes the binary
/// `compound_type` (§5.11.x `read_compound_type`), selected by
/// `MiSize`.
pub compound_type: [[u16; COMPOUND_TYPES + 1]; BLOCK_SIZES],
// -----------------------------------------------------------------
// Round 134 — inter-frame intra-mode group. §8.3.1 enumerates these
// as "`YModeCdf` is set to a copy of `Default_Y_Mode_Cdf`",
// "`UVModeCflNotAllowedCdf` is set to a copy of
// `Default_Uv_Mode_Cfl_Not_Allowed_Cdf`", and
// "`UVModeCflAllowedCdf` is set to a copy of
// `Default_Uv_Mode_Cfl_Allowed_Cdf`".
// -----------------------------------------------------------------
/// `TileYModeCdf[ BLOCK_SIZE_GROUPS ]` (§8.3.1). Codes the inter-frame
/// `y_mode`, selected by `ctx = Size_Group[ MiSize ]`.
pub y_mode: [[u16; INTRA_MODES + 1]; BLOCK_SIZE_GROUPS],
/// `TileUVModeCflNotAllowedCdf[ INTRA_MODES ]` (§8.3.1). Codes
/// `uv_mode` when chroma-from-luma is not allowed, selected by `YMode`.
pub uv_mode_cfl_not_allowed: [[u16; UV_INTRA_MODES_CFL_NOT_ALLOWED + 1]; INTRA_MODES],
/// `TileUVModeCflAllowedCdf[ INTRA_MODES ]` (§8.3.1). Codes `uv_mode`
/// when chroma-from-luma is allowed, selected by `YMode`.
pub uv_mode_cfl_allowed: [[u16; UV_INTRA_MODES_CFL_ALLOWED + 1]; INTRA_MODES],
// -----------------------------------------------------------------
// Round 135 — angle-delta group. §8.3.1 enumerates this as
// "`AngleDeltaCdf` is set to a copy of `Default_Angle_Delta_Cdf`".
// -----------------------------------------------------------------
/// `TileAngleDeltaCdf[ DIRECTIONAL_MODES ]` (§8.3.1). Codes the
/// `angle_delta_y` / `angle_delta_uv` directional-angle offset,
/// selected by `YMode - V_PRED` / `UVMode - V_PRED`.
pub angle_delta: [[u16; (2 * MAX_ANGLE_DELTA + 1) + 1]; DIRECTIONAL_MODES],
// -----------------------------------------------------------------
// Round 136 — coefficient-token entry sub-group. §8.3.1
// `init_coeff_cdfs` first derives `idx` from `base_q_idx`, then sets
// each of these working arrays to a copy of `Default_*_Cdf[ idx ]`.
// The working copy therefore *drops* the `COEFF_CDF_Q_CTXS` axis;
// [`TileCdfContext::init_coeff_cdfs`] performs the per-`idx` copy.
// `new_from_defaults` seeds them from `idx == 0` so the value is
// always well-formed before `init_coeff_cdfs` runs.
// -----------------------------------------------------------------
/// `TileTxbSkipCdf[ TX_SIZES ][ TXB_SKIP_CONTEXTS ]` (§8.3.1). Codes
/// the `all_zero` transform-block skip flag.
pub txb_skip: [[[u16; 3]; TXB_SKIP_CONTEXTS]; TX_SIZES],
/// `TileEobPt16Cdf[ PLANE_TYPES ][ 2 ]` (§8.3.1). Codes `eob_pt_16`.
pub eob_pt_16: [[[u16; 6]; 2]; PLANE_TYPES],
/// `TileEobPt32Cdf[ PLANE_TYPES ][ 2 ]` (§8.3.1). Codes `eob_pt_32`.
pub eob_pt_32: [[[u16; 7]; 2]; PLANE_TYPES],
/// `TileEobPt64Cdf[ PLANE_TYPES ][ 2 ]` (§8.3.1). Codes `eob_pt_64`.
pub eob_pt_64: [[[u16; 8]; 2]; PLANE_TYPES],
/// `TileEobPt128Cdf[ PLANE_TYPES ][ 2 ]` (§8.3.1). Codes `eob_pt_128`.
pub eob_pt_128: [[[u16; 9]; 2]; PLANE_TYPES],
/// `TileEobPt256Cdf[ PLANE_TYPES ][ 2 ]` (§8.3.1). Codes `eob_pt_256`.
pub eob_pt_256: [[[u16; 10]; 2]; PLANE_TYPES],
/// `TileEobPt512Cdf[ PLANE_TYPES ]` (§8.3.1). Codes `eob_pt_512`.
pub eob_pt_512: [[u16; 11]; PLANE_TYPES],
/// `TileEobPt1024Cdf[ PLANE_TYPES ]` (§8.3.1). Codes `eob_pt_1024`.
pub eob_pt_1024: [[u16; 12]; PLANE_TYPES],
/// `TileEobExtraCdf[ TX_SIZES ][ PLANE_TYPES ][ EOB_COEF_CONTEXTS ]`
/// (§8.3.1). Codes the binary `eob_extra` flag.
pub eob_extra: [[[[u16; 3]; EOB_COEF_CONTEXTS]; PLANE_TYPES]; TX_SIZES],
/// `TileDcSignCdf[ PLANE_TYPES ][ DC_SIGN_CONTEXTS ]` (§8.3.1). Codes
/// the binary `dc_sign`.
pub dc_sign: [[[u16; 3]; DC_SIGN_CONTEXTS]; PLANE_TYPES],
// -----------------------------------------------------------------
// Round 138 — `coeff_base_eob` sub-group. §8.3.1 enumerates this as
// "`CoeffBaseEobCdf` is set to a copy of `Default_Coeff_Base_Eob_Cdf
// [ idx ]`". The working copy drops the `COEFF_CDF_Q_CTXS` axis;
// [`TileCdfContext::init_coeff_cdfs`] performs the per-`idx` copy.
// -----------------------------------------------------------------
/// `TileCoeffBaseEobCdf[ TX_SIZES ][ PLANE_TYPES ][ SIG_COEF_CONTEXTS_EOB ]`
/// (§8.3.1). Codes `coeff_base_eob`.
pub coeff_base_eob: [[[[u16; 4]; SIG_COEF_CONTEXTS_EOB]; PLANE_TYPES]; TX_SIZES],
// -----------------------------------------------------------------
// Round 139 — `coeff_base` sub-group. §8.3.1 enumerates this as
// "`CoeffBaseCdf` is set to a copy of `Default_Coeff_Base_Cdf
// [ idx ]`". The working copy drops the `COEFF_CDF_Q_CTXS` axis;
// [`TileCdfContext::init_coeff_cdfs`] performs the per-`idx` copy.
// -----------------------------------------------------------------
/// `TileCoeffBaseCdf[ TX_SIZES ][ PLANE_TYPES ][ SIG_COEF_CONTEXTS ]`
/// (§8.3.1). Codes `coeff_base` (the base level of each non-EOB
/// coefficient, a 4-symbol alphabet).
pub coeff_base: [[[[u16; 5]; SIG_COEF_CONTEXTS]; PLANE_TYPES]; TX_SIZES],
// -----------------------------------------------------------------
// Round 140 — `coeff_br` sub-group. §8.3.1 enumerates this as
// "`CoeffBrCdf` is set to a copy of `Default_Coeff_Br_Cdf
// [ idx ]`". The working copy drops the `COEFF_CDF_Q_CTXS` axis;
// [`TileCdfContext::init_coeff_cdfs`] performs the per-`idx` copy.
// The §8.3.2 selector clamps `txSzCtx` to `TX_32X32` before
// indexing into this working copy, but the working copy itself
// retains all `TX_SIZES` slices (matching the §9.4 source's
// outer-after-`idx` shape).
// -----------------------------------------------------------------
/// `TileCoeffBrCdf[ TX_SIZES ][ PLANE_TYPES ][ LEVEL_CONTEXTS ]`
/// (§8.3.1). Codes `coeff_br` (the per-coefficient base-range
/// increment used to push a level above `NUM_BASE_LEVELS`).
pub coeff_br: [[[[u16; BR_CDF_SIZE + 1]; LEVEL_CONTEXTS]; PLANE_TYPES]; TX_SIZES],
// -----------------------------------------------------------------
// Round 143 — inter-intra group. §8.3.1 enumerates these as
// "`InterIntraCdf` is set to a copy of `Default_Inter_Intra_Cdf`",
// "`InterIntraModeCdf` is set to a copy of
// `Default_Inter_Intra_Mode_Cdf`", and "`WedgeInterIntraCdf` is set
// to a copy of `Default_Wedge_Inter_Intra_Cdf`".
// -----------------------------------------------------------------
/// `TileInterIntraCdf[ BLOCK_SIZE_GROUPS - 1 ]` (§8.3.1). Codes the
/// binary `interintra` flag (§5.11.28 `read_interintra_mode`),
/// selected by `ctx = Size_Group[ MiSize ] - 1`.
pub inter_intra: [[u16; 3]; BLOCK_SIZE_GROUPS - 1],
/// `TileInterIntraModeCdf[ BLOCK_SIZE_GROUPS - 1 ]` (§8.3.1). Codes
/// `interintra_mode` (§5.11.28 `read_interintra_mode`), selected by
/// `ctx = Size_Group[ MiSize ] - 1`.
pub inter_intra_mode: [[u16; INTERINTRA_MODES + 1]; BLOCK_SIZE_GROUPS - 1],
/// `TileWedgeInterIntraCdf[ BLOCK_SIZES ]` (§8.3.1). Codes the binary
/// `wedge_interintra` flag (§5.11.28 `read_interintra_mode`),
/// selected by `MiSize` per the §8.3.2 selection.
pub wedge_inter_intra: [[u16; 3]; BLOCK_SIZES],
// -----------------------------------------------------------------
// Round 144 — wedge-index CDF. §8.3.1 enumerates this as
// "`WedgeIndexCdf` is set to a copy of `Default_Wedge_Index_Cdf`".
// -----------------------------------------------------------------
/// `TileWedgeIndexCdf[ BLOCK_SIZES ]` (§8.3.1). Codes the
/// `wedge_index ∈ 0..WEDGE_TYPES` element read by §5.11.28
/// `read_interintra_mode` (the inter-intra wedge sub-branch, when
/// `wedge_interintra == 1`) and §5.11.29 `read_compound_type` (the
/// inter-inter `COMPOUND_WEDGE` branch). Selected by `MiSize` per
/// the §8.3.2 selection (`TileWedgeIndexCdf[ MiSize ]`).
pub wedge_index: [[u16; WEDGE_TYPES + 1]; BLOCK_SIZES],
// -----------------------------------------------------------------
// Round 287 — loop-restoration unit CDFs. §8.3.1 enumerates each as
// "`UseWienerCdf` is set to a copy of `Default_Use_Wiener_Cdf`"
// (and likewise for `UseSgrprojCdf` / `RestorationTypeCdf`). None of
// the three is context-indexed — a single CDF row per symbol.
// -----------------------------------------------------------------
/// `TileUseWienerCdf` (§8.3.1 — av1-spec p.156). Binary `use_wiener`
/// (§5.11.58 `read_lr_unit`).
pub use_wiener: [u16; 3],
/// `TileUseSgrprojCdf` (§8.3.1 — av1-spec p.156). Binary
/// `use_sgrproj` (§5.11.58 `read_lr_unit`).
pub use_sgrproj: [u16; 3],
/// `TileRestorationTypeCdf` (§8.3.1 — av1-spec p.156). 3-symbol
/// `restoration_type` (§5.11.58 `read_lr_unit`, switchable arm).
pub restoration_type: [u16; 4],
}
impl TileCdfContext {
/// §6.8.21 `load_cdfs( ctx )` counter reset — "Once the CDF arrays
/// have been loaded, the last entry in each array, representing
/// the symbol count for that context, is set to 0." The §8.3
/// per-symbol update rate depends on that count (`4 + (count >
/// 15) + (count > 31) + ...`), so a loaded context must restart
/// its adaptation-rate schedule even though the probabilities
/// carry over.
pub fn zero_counts(&mut self) {
fn zero1<const N: usize>(row: &mut [u16; N]) {
row[N - 1] = 0;
}
fn zero2<const N: usize, const M: usize>(rows: &mut [[u16; N]; M]) {
for row in rows.iter_mut() {
zero1(row);
}
}
fn zero3<const N: usize, const M: usize, const L: usize>(rows: &mut [[[u16; N]; M]; L]) {
for row in rows.iter_mut() {
zero2(row);
}
}
fn zero4<const N: usize, const M: usize, const L: usize, const K: usize>(
rows: &mut [[[[u16; N]; M]; L]; K],
) {
for row in rows.iter_mut() {
zero3(row);
}
}
zero3(&mut self.intra_frame_y_mode);
zero2(&mut self.partition_w8);
zero2(&mut self.partition_w16);
zero2(&mut self.partition_w32);
zero2(&mut self.partition_w64);
zero2(&mut self.partition_w128);
zero2(&mut self.skip);
zero2(&mut self.segment_id);
zero2(&mut self.segment_id_predicted);
zero2(&mut self.mv_joint);
zero3(&mut self.mv_sign);
zero3(&mut self.mv_class);
zero3(&mut self.mv_class0_bit);
zero4(&mut self.mv_class0_fr);
zero3(&mut self.mv_class0_hp);
zero4(&mut self.mv_bit);
zero3(&mut self.mv_fr);
zero3(&mut self.mv_hp);
zero2(&mut self.new_mv);
zero2(&mut self.zero_mv);
zero2(&mut self.ref_mv);
zero2(&mut self.drl_mode);
zero2(&mut self.is_inter);
zero2(&mut self.comp_mode);
zero2(&mut self.skip_mode);
zero1(&mut self.intrabc);
zero1(&mut self.delta_q);
zero1(&mut self.delta_lf);
zero2(&mut self.delta_lf_multi);
zero3(&mut self.comp_ref);
zero3(&mut self.comp_bwd_ref);
zero3(&mut self.single_ref);
zero2(&mut self.compound_mode);
zero2(&mut self.comp_ref_type);
zero3(&mut self.uni_comp_ref);
zero1(&mut self.filter_intra_mode);
zero2(&mut self.filter_intra);
zero3(&mut self.palette_y_mode);
zero2(&mut self.palette_uv_mode);
zero2(&mut self.palette_y_size);
zero2(&mut self.palette_uv_size);
zero2(&mut self.palette_size_2_y_color);
zero2(&mut self.palette_size_3_y_color);
zero2(&mut self.palette_size_4_y_color);
zero2(&mut self.palette_size_5_y_color);
zero2(&mut self.palette_size_6_y_color);
zero2(&mut self.palette_size_7_y_color);
zero2(&mut self.palette_size_8_y_color);
zero2(&mut self.palette_size_2_uv_color);
zero2(&mut self.palette_size_3_uv_color);
zero2(&mut self.palette_size_4_uv_color);
zero2(&mut self.palette_size_5_uv_color);
zero2(&mut self.palette_size_6_uv_color);
zero2(&mut self.palette_size_7_uv_color);
zero2(&mut self.palette_size_8_uv_color);
zero1(&mut self.cfl_sign);
zero2(&mut self.cfl_alpha);
zero2(&mut self.tx_8x8);
zero2(&mut self.tx_16x16);
zero2(&mut self.tx_32x32);
zero2(&mut self.tx_64x64);
zero2(&mut self.txfm_split);
zero2(&mut self.inter_tx_type_set1);
zero1(&mut self.inter_tx_type_set2);
zero2(&mut self.inter_tx_type_set3);
zero3(&mut self.intra_tx_type_set1);
zero3(&mut self.intra_tx_type_set2);
zero2(&mut self.interp_filter);
zero2(&mut self.motion_mode);
zero2(&mut self.use_obmc);
zero2(&mut self.comp_group_idx);
zero2(&mut self.compound_idx);
zero2(&mut self.compound_type);
zero2(&mut self.y_mode);
zero2(&mut self.uv_mode_cfl_not_allowed);
zero2(&mut self.uv_mode_cfl_allowed);
zero2(&mut self.angle_delta);
zero3(&mut self.txb_skip);
zero3(&mut self.eob_pt_16);
zero3(&mut self.eob_pt_32);
zero3(&mut self.eob_pt_64);
zero3(&mut self.eob_pt_128);
zero3(&mut self.eob_pt_256);
zero2(&mut self.eob_pt_512);
zero2(&mut self.eob_pt_1024);
zero4(&mut self.eob_extra);
zero3(&mut self.dc_sign);
zero4(&mut self.coeff_base_eob);
zero4(&mut self.coeff_base);
zero4(&mut self.coeff_br);
zero2(&mut self.inter_intra);
zero2(&mut self.inter_intra_mode);
zero2(&mut self.wedge_inter_intra);
zero2(&mut self.wedge_index);
zero1(&mut self.use_wiener);
zero1(&mut self.use_sgrproj);
zero1(&mut self.restoration_type);
}
/// §8.3.1: initialise every `Tile*Cdf` array from its `Default_*`
/// table. Called at the start of tile parsing (and again when
/// `init_non_coeff_cdfs()` is invoked per §7.4 / §5.11.4).
///
/// The returned context is independent of [`DEFAULT_INTRA_FRAME_Y_MODE_CDF`]
/// et al. (it is a value copy), so adapting it leaves the defaults
/// untouched for the next tile's `new_from_defaults`.
pub fn new_from_defaults() -> Self {
// Per §8.3.1 the flat (per-comp, per-bit) defaults are
// broadcast into a [MV_CONTEXTS][..] / [MV_CONTEXTS][2][..]
// working set; expand them once here.
let mv_sign_row: [[u16; 3]; MV_COMPS] = [DEFAULT_MV_SIGN_CDF, DEFAULT_MV_SIGN_CDF];
let mv_class0_bit_row: [[u16; 3]; MV_COMPS] =
[DEFAULT_MV_CLASS0_BIT_CDF, DEFAULT_MV_CLASS0_BIT_CDF];
let mv_class0_hp_row: [[u16; 3]; MV_COMPS] =
[DEFAULT_MV_CLASS0_HP_CDF, DEFAULT_MV_CLASS0_HP_CDF];
let mv_hp_row: [[u16; 3]; MV_COMPS] = [DEFAULT_MV_HP_CDF, DEFAULT_MV_HP_CDF];
let mv_bit_row: [[[u16; 3]; MV_OFFSET_BITS]; MV_COMPS] =
[DEFAULT_MV_BIT_CDF, DEFAULT_MV_BIT_CDF];
Self {
intra_frame_y_mode: DEFAULT_INTRA_FRAME_Y_MODE_CDF,
partition_w8: DEFAULT_PARTITION_W8_CDF,
partition_w16: DEFAULT_PARTITION_W16_CDF,
partition_w32: DEFAULT_PARTITION_W32_CDF,
partition_w64: DEFAULT_PARTITION_W64_CDF,
partition_w128: DEFAULT_PARTITION_W128_CDF,
skip: DEFAULT_SKIP_CDF,
segment_id: DEFAULT_SEGMENT_ID_CDF,
segment_id_predicted: DEFAULT_SEGMENT_ID_PREDICTED_CDF,
mv_joint: [DEFAULT_MV_JOINT_CDF; MV_CONTEXTS],
mv_sign: [mv_sign_row; MV_CONTEXTS],
mv_class: [DEFAULT_MV_CLASS_CDF; MV_CONTEXTS],
mv_class0_bit: [mv_class0_bit_row; MV_CONTEXTS],
mv_class0_fr: [DEFAULT_MV_CLASS0_FR_CDF; MV_CONTEXTS],
mv_class0_hp: [mv_class0_hp_row; MV_CONTEXTS],
mv_bit: [mv_bit_row; MV_CONTEXTS],
mv_fr: [DEFAULT_MV_FR_CDF; MV_CONTEXTS],
mv_hp: [mv_hp_row; MV_CONTEXTS],
// Round 18 — inter-mode / reference-frame group.
new_mv: DEFAULT_NEW_MV_CDF,
zero_mv: DEFAULT_ZERO_MV_CDF,
ref_mv: DEFAULT_REF_MV_CDF,
drl_mode: DEFAULT_DRL_MODE_CDF,
is_inter: DEFAULT_IS_INTER_CDF,
comp_mode: DEFAULT_COMP_MODE_CDF,
skip_mode: DEFAULT_SKIP_MODE_CDF,
intrabc: DEFAULT_INTRABC_CDF,
delta_q: DEFAULT_DELTA_Q_CDF,
delta_lf: DEFAULT_DELTA_LF_CDF,
delta_lf_multi: [DEFAULT_DELTA_LF_CDF; FRAME_LF_COUNT],
comp_ref: DEFAULT_COMP_REF_CDF,
comp_bwd_ref: DEFAULT_COMP_BWD_REF_CDF,
single_ref: DEFAULT_SINGLE_REF_CDF,
compound_mode: DEFAULT_COMPOUND_MODE_CDF,
comp_ref_type: DEFAULT_COMP_REF_TYPE_CDF,
uni_comp_ref: DEFAULT_UNI_COMP_REF_CDF,
// Round 19 — palette / filter-intra / CFL group.
filter_intra_mode: DEFAULT_FILTER_INTRA_MODE_CDF,
filter_intra: DEFAULT_FILTER_INTRA_CDF,
palette_y_mode: DEFAULT_PALETTE_Y_MODE_CDF,
palette_uv_mode: DEFAULT_PALETTE_UV_MODE_CDF,
palette_y_size: DEFAULT_PALETTE_Y_SIZE_CDF,
palette_uv_size: DEFAULT_PALETTE_UV_SIZE_CDF,
palette_size_2_y_color: DEFAULT_PALETTE_SIZE_2_Y_COLOR_CDF,
palette_size_3_y_color: DEFAULT_PALETTE_SIZE_3_Y_COLOR_CDF,
palette_size_4_y_color: DEFAULT_PALETTE_SIZE_4_Y_COLOR_CDF,
palette_size_5_y_color: DEFAULT_PALETTE_SIZE_5_Y_COLOR_CDF,
palette_size_6_y_color: DEFAULT_PALETTE_SIZE_6_Y_COLOR_CDF,
palette_size_7_y_color: DEFAULT_PALETTE_SIZE_7_Y_COLOR_CDF,
palette_size_8_y_color: DEFAULT_PALETTE_SIZE_8_Y_COLOR_CDF,
palette_size_2_uv_color: DEFAULT_PALETTE_SIZE_2_UV_COLOR_CDF,
palette_size_3_uv_color: DEFAULT_PALETTE_SIZE_3_UV_COLOR_CDF,
palette_size_4_uv_color: DEFAULT_PALETTE_SIZE_4_UV_COLOR_CDF,
palette_size_5_uv_color: DEFAULT_PALETTE_SIZE_5_UV_COLOR_CDF,
palette_size_6_uv_color: DEFAULT_PALETTE_SIZE_6_UV_COLOR_CDF,
palette_size_7_uv_color: DEFAULT_PALETTE_SIZE_7_UV_COLOR_CDF,
palette_size_8_uv_color: DEFAULT_PALETTE_SIZE_8_UV_COLOR_CDF,
cfl_sign: DEFAULT_CFL_SIGN_CDF,
cfl_alpha: DEFAULT_CFL_ALPHA_CDF,
// Round 20 — transform-size group.
tx_8x8: DEFAULT_TX_8X8_CDF,
tx_16x16: DEFAULT_TX_16X16_CDF,
tx_32x32: DEFAULT_TX_32X32_CDF,
tx_64x64: DEFAULT_TX_64X64_CDF,
txfm_split: DEFAULT_TXFM_SPLIT_CDF,
// Round 21 — inter-frame transform-type group.
inter_tx_type_set1: DEFAULT_INTER_TX_TYPE_SET1_CDF,
inter_tx_type_set2: DEFAULT_INTER_TX_TYPE_SET2_CDF,
inter_tx_type_set3: DEFAULT_INTER_TX_TYPE_SET3_CDF,
// Round 137 — intra-frame transform-type group.
intra_tx_type_set1: DEFAULT_INTRA_TX_TYPE_SET1_CDF,
intra_tx_type_set2: DEFAULT_INTRA_TX_TYPE_SET2_CDF,
// Round 22 — inter-frame interpolation-filter group.
interp_filter: DEFAULT_INTERP_FILTER_CDF,
// Round 23 — motion-mode group.
motion_mode: DEFAULT_MOTION_MODE_CDF,
use_obmc: DEFAULT_USE_OBMC_CDF,
// Round 24 — compound-prediction group.
comp_group_idx: DEFAULT_COMP_GROUP_IDX_CDF,
compound_idx: DEFAULT_COMPOUND_IDX_CDF,
compound_type: DEFAULT_COMPOUND_TYPE_CDF,
// Round 134 — inter-frame intra-mode group.
y_mode: DEFAULT_Y_MODE_CDF,
uv_mode_cfl_not_allowed: DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF,
uv_mode_cfl_allowed: DEFAULT_UV_MODE_CFL_ALLOWED_CDF,
// Round 135 — angle-delta group.
angle_delta: DEFAULT_ANGLE_DELTA_CDF,
// Round 136 — coefficient-token entry sub-group. Seeded from
// the `idx == 0` q-context slice; `init_coeff_cdfs` re-selects
// the slice for the actual `base_q_idx` before tile content.
txb_skip: DEFAULT_TXB_SKIP_CDF[0],
eob_pt_16: DEFAULT_EOB_PT_16_CDF[0],
eob_pt_32: DEFAULT_EOB_PT_32_CDF[0],
eob_pt_64: DEFAULT_EOB_PT_64_CDF[0],
eob_pt_128: DEFAULT_EOB_PT_128_CDF[0],
eob_pt_256: DEFAULT_EOB_PT_256_CDF[0],
eob_pt_512: DEFAULT_EOB_PT_512_CDF[0],
eob_pt_1024: DEFAULT_EOB_PT_1024_CDF[0],
eob_extra: DEFAULT_EOB_EXTRA_CDF[0],
dc_sign: DEFAULT_DC_SIGN_CDF[0],
// Round 138 — `coeff_base_eob` sub-group. Seeded from the
// `idx == 0` q-context slice; `init_coeff_cdfs` re-selects
// the slice for the actual `base_q_idx` before tile content.
coeff_base_eob: DEFAULT_COEFF_BASE_EOB_CDF[0],
// Round 139 — `coeff_base` sub-group. Seeded from the
// `idx == 0` q-context slice; `init_coeff_cdfs` re-selects
// the slice for the actual `base_q_idx` before tile content.
coeff_base: DEFAULT_COEFF_BASE_CDF[0],
// Round 140 — `coeff_br` sub-group. Seeded from the
// `idx == 0` q-context slice; `init_coeff_cdfs` re-selects
// the slice for the actual `base_q_idx` before tile content.
coeff_br: DEFAULT_COEFF_BR_CDF[0],
// Round 143 — inter-intra group.
inter_intra: DEFAULT_INTER_INTRA_CDF,
inter_intra_mode: DEFAULT_INTER_INTRA_MODE_CDF,
wedge_inter_intra: DEFAULT_WEDGE_INTER_INTRA_CDF,
// Round 144 — wedge-index CDF.
wedge_index: DEFAULT_WEDGE_INDEX_CDF,
use_wiener: DEFAULT_USE_WIENER_CDF,
use_sgrproj: DEFAULT_USE_SGRPROJ_CDF,
restoration_type: DEFAULT_RESTORATION_TYPE_CDF,
}
}
// -----------------------------------------------------------------
// §8.3.2 selection: a syntax-element name + its block context maps
// to a mutable reference to the right CDF row. The caller passes the
// returned `&mut [u16]` straight to `SymbolDecoder::read_symbol`.
// -----------------------------------------------------------------
/// §8.3.2 `intra_frame_y_mode`: the cdf is
/// `TileIntraFrameYModeCdf[ abovemode ][ leftmode ]`, where
/// `abovemode` / `leftmode` are the [`INTRA_MODE_CONTEXT`]-mapped
/// intra modes of the blocks immediately above / to the left.
///
/// The caller supplies the already-mapped context indices (each in
/// `0..INTRA_MODE_CONTEXTS`), since the neighbour-availability +
/// `YModes[]` lookup belongs to the (not-yet-implemented) tile walk.
/// [`intra_mode_ctx`] is provided for the mapping step.
pub fn intra_frame_y_mode_cdf(&mut self, abovemode: usize, leftmode: usize) -> &mut [u16] {
&mut self.intra_frame_y_mode[abovemode][leftmode]
}
/// §8.3.2 `partition`: select the `TilePartitionW{8,16,32,64,128}Cdf`
/// array by `bsl` (= `Mi_Width_Log2[ bSize ]`, in `1..=5`) and index
/// it by `ctx` (= `left * 2 + above`, in `0..PARTITION_CONTEXTS`).
///
/// Returns `None` for a `bsl` outside `1..=5` (a caller bug — the
/// partition syntax is never reached for other block sizes).
pub fn partition_cdf(&mut self, bsl: u32, ctx: usize) -> Option<&mut [u16]> {
match bsl {
1 => Some(&mut self.partition_w8[ctx]),
2 => Some(&mut self.partition_w16[ctx]),
3 => Some(&mut self.partition_w32[ctx]),
4 => Some(&mut self.partition_w64[ctx]),
5 => Some(&mut self.partition_w128[ctx]),
_ => None,
}
}
/// §8.3.2 `skip`: the cdf is `TileSkipCdf[ ctx ]` where `ctx` is the
/// sum of the above and left blocks' `Skips[]` (in
/// `0..SKIP_CONTEXTS`).
pub fn skip_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.skip[ctx]
}
/// §8.3.2 `use_wiener`: the cdf is `TileUseWienerCdf` (not
/// context-indexed). §5.11.58 `read_lr_unit`.
pub fn use_wiener_cdf(&mut self) -> &mut [u16] {
&mut self.use_wiener
}
/// §8.3.2 `use_sgrproj`: the cdf is `TileUseSgrprojCdf` (not
/// context-indexed). §5.11.58 `read_lr_unit`.
pub fn use_sgrproj_cdf(&mut self) -> &mut [u16] {
&mut self.use_sgrproj
}
/// §8.3.2 `restoration_type`: the cdf is `TileRestorationTypeCdf`
/// (not context-indexed). §5.11.58 `read_lr_unit`, switchable arm.
pub fn restoration_type_cdf(&mut self) -> &mut [u16] {
&mut self.restoration_type
}
/// §8.3.2 `segment_id`: the cdf is `TileSegmentIdCdf[ ctx ]` where
/// `ctx` is computed from the neighbouring segment ids (in
/// `0..SEGMENT_ID_CONTEXTS`); see [`segment_id_ctx`].
pub fn segment_id_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.segment_id[ctx]
}
/// §8.3.2 `seg_id_predicted`: the cdf is
/// `TileSegmentIdPredictedCdf[ ctx ]` where `ctx =
/// LeftSegPredContext[ MiRow ] + AboveSegPredContext[ MiCol ]`
/// (each neighbour contributing `0` or `1`; sum in
/// `0..SEGMENT_ID_PREDICTED_CONTEXTS = 0..3`). Read by the
/// §5.11.19 `inter_segment_id` `segmentation_temporal_update == 1`
/// arm; see [`PartitionWalker::decode_inter_segment_id`].
pub fn segment_id_predicted_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.segment_id_predicted[ctx]
}
// -----------------------------------------------------------------
// Round 17 — motion-vector §8.3.2 selectors. The shared `MvCtx`
// input is derived from §5.11.31 `read_mv()`:
//
// MvCtx = use_intrabc ? MV_INTRABC_CONTEXT : 0
//
// See [`mv_ctx`].
// -----------------------------------------------------------------
/// §8.3.2 `mv_joint`: the cdf is `TileMvJointCdf[ MvCtx ]`.
pub fn mv_joint_cdf(&mut self, mv_ctx: usize) -> &mut [u16] {
&mut self.mv_joint[mv_ctx]
}
/// §8.3.2 `mv_sign`: the cdf is `TileMvSignCdf[ MvCtx ][ comp ]`,
/// with `comp = 0` for the horizontal component and `comp = 1` for
/// the vertical.
pub fn mv_sign_cdf(&mut self, mv_ctx: usize, comp: usize) -> &mut [u16] {
&mut self.mv_sign[mv_ctx][comp]
}
/// §8.3.2 `mv_class`: the cdf is `TileMvClassCdf[ MvCtx ][ comp ]`.
pub fn mv_class_cdf(&mut self, mv_ctx: usize, comp: usize) -> &mut [u16] {
&mut self.mv_class[mv_ctx][comp]
}
/// §8.3.2 `mv_class0_bit`: the cdf is
/// `TileMvClass0BitCdf[ MvCtx ][ comp ]`. Only reached when
/// §5.11.32 `read_mv_component()` saw `mv_class == MV_CLASS_0`.
pub fn mv_class0_bit_cdf(&mut self, mv_ctx: usize, comp: usize) -> &mut [u16] {
&mut self.mv_class0_bit[mv_ctx][comp]
}
/// §8.3.2 `mv_class0_fr`: the cdf is
/// `TileMvClass0FrCdf[ MvCtx ][ comp ][ mv_class0_bit ]`. The
/// caller supplies the already-decoded `mv_class0_bit` (in
/// `0..CLASS0_SIZE`).
pub fn mv_class0_fr_cdf(
&mut self,
mv_ctx: usize,
comp: usize,
mv_class0_bit: usize,
) -> &mut [u16] {
&mut self.mv_class0_fr[mv_ctx][comp][mv_class0_bit]
}
/// §8.3.2 `mv_class0_hp`: the cdf is
/// `TileMvClass0HpCdf[ MvCtx ][ comp ]`. Only reached when
/// `allow_high_precision_mv == 1`.
pub fn mv_class0_hp_cdf(&mut self, mv_ctx: usize, comp: usize) -> &mut [u16] {
&mut self.mv_class0_hp[mv_ctx][comp]
}
/// §8.3.2 `mv_bit`: the cdf is `TileMvBitCdf[ MvCtx ][ comp ][ i ]`
/// where `i` is the bit position currently being read by §5.11.32
/// (`i = 0..mv_class - 1`, bounded above by `MV_OFFSET_BITS`).
pub fn mv_bit_cdf(&mut self, mv_ctx: usize, comp: usize, i: usize) -> &mut [u16] {
&mut self.mv_bit[mv_ctx][comp][i]
}
/// §8.3.2 `mv_fr`: the cdf is `TileMvFrCdf[ MvCtx ][ comp ]`. Only
/// reached when `force_integer_mv == 0` and `mv_class != MV_CLASS_0`.
pub fn mv_fr_cdf(&mut self, mv_ctx: usize, comp: usize) -> &mut [u16] {
&mut self.mv_fr[mv_ctx][comp]
}
/// §8.3.2 `mv_hp`: the cdf is `TileMvHpCdf[ MvCtx ][ comp ]`. Only
/// reached when `allow_high_precision_mv == 1` and
/// `mv_class != MV_CLASS_0`.
pub fn mv_hp_cdf(&mut self, mv_ctx: usize, comp: usize) -> &mut [u16] {
&mut self.mv_hp[mv_ctx][comp]
}
// -----------------------------------------------------------------
// Round 18 — inter-mode / reference-frame §8.3.2 selectors. The
// caller pre-computes the §8.3.2 `ctx` (the spec has explicit
// formulas for each — see [`is_inter_ctx`], [`skip_mode_ctx`],
// [`ref_count_ctx`], [`compound_mode_ctx`]; the `comp_mode` and
// `comp_ref_type` formulas need full tile-walk neighbour state and
// are deferred) and passes the index straight to the array lookup.
// -----------------------------------------------------------------
/// §8.3.2 `new_mv`: the cdf is `TileNewMvCdf[ NewMvContext ]`. The
/// `NewMvContext` is supplied by `find_mv_stack()` (in `0..NEW_MV_CONTEXTS`).
pub fn new_mv_cdf(&mut self, new_mv_context: usize) -> &mut [u16] {
&mut self.new_mv[new_mv_context]
}
/// §8.3.2 `zero_mv`: the cdf is `TileZeroMvCdf[ ZeroMvContext ]`.
/// `ZeroMvContext` in `0..ZERO_MV_CONTEXTS`.
pub fn zero_mv_cdf(&mut self, zero_mv_context: usize) -> &mut [u16] {
&mut self.zero_mv[zero_mv_context]
}
/// §8.3.2 `ref_mv`: the cdf is `TileRefMvCdf[ RefMvContext ]`.
/// `RefMvContext` in `0..REF_MV_CONTEXTS`.
pub fn ref_mv_cdf(&mut self, ref_mv_context: usize) -> &mut [u16] {
&mut self.ref_mv[ref_mv_context]
}
/// §8.3.2 `drl_mode`: the cdf is `TileDrlModeCdf[ DrlCtxStack[ idx ] ]`.
/// The caller supplies the `DrlCtxStack[ idx ]` value in
/// `0..DRL_MODE_CONTEXTS`.
pub fn drl_mode_cdf(&mut self, drl_ctx: usize) -> &mut [u16] {
&mut self.drl_mode[drl_ctx]
}
/// §8.3.2 `is_inter`: the cdf is `TileIsInterCdf[ ctx ]`, with `ctx`
/// computed by [`is_inter_ctx`] (in `0..IS_INTER_CONTEXTS`).
pub fn is_inter_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.is_inter[ctx]
}
/// §8.3.2 `comp_mode`: the cdf is `TileCompModeCdf[ ctx ]`. `ctx` is
/// supplied in `0..COMP_INTER_CONTEXTS`; its §8.3.2 derivation needs
/// `AvailU` / `AvailL` / `AboveSingle` / `LeftSingle` /
/// `AboveRefFrame[0]` / `LeftRefFrame[0]` / `AboveIntra` /
/// `LeftIntra` from the tile walk plus the spec's `check_backward(ref)`
/// `(ref >= BWDREF_FRAME) && (ref <= ALTREF_FRAME)` predicate, so the
/// branch ladder lives in the (future) tile-walk crate rather than as
/// a standalone helper here.
pub fn comp_mode_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.comp_mode[ctx]
}
/// §8.3.2 `skip_mode`: the cdf is `TileSkipModeCdf[ ctx ]`, with
/// `ctx` computed by [`skip_mode_ctx`] (in `0..SKIP_MODE_CONTEXTS`).
pub fn skip_mode_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.skip_mode[ctx]
}
/// §8.3.2 `use_intrabc`: the cdf is `TileIntrabcCdf` (no context
/// index — the §8.3.2 selection text reads "use_intrabc: The cdf
/// for use_intrabc is given by TileIntrabcCdf" with no `[ctx]`
/// subscript). Length is 3 (`use_intrabc ∈ { 0, 1 }` plus the
/// §8.2.6 counter slot). Used only by the §5.11.7 `if
/// ( allow_intrabc ) { use_intrabc S() }` arm — frames with
/// `allow_intrabc == 0` never reach the selector (the §5.11.7
/// fall-through `use_intrabc = 0` consumes no bit).
pub fn intrabc_cdf(&mut self) -> &mut [u16] {
&mut self.intrabc
}
/// §8.3.2 `delta_q_abs`: the cdf is `TileDeltaQCdf` (no context
/// index). Length is `DELTA_Q_SMALL + 2 = 5`, encoding the four
/// literal values `0..=DELTA_Q_SMALL` plus the §8.2.6 counter
/// slot. A decoded value of `DELTA_Q_SMALL` is the escape into
/// the §5.11.12 `delta_q_rem_bits` / `delta_q_abs_bits` ladder.
pub fn delta_q_cdf(&mut self) -> &mut [u16] {
&mut self.delta_q
}
/// §8.3.2 `delta_lf_abs` (single-LF branch, `delta_lf_multi ==
/// 0`): the cdf is `TileDeltaLFCdf` (no context index). Length is
/// `DELTA_LF_SMALL + 2 = 5`, encoding the four literal values
/// `0..=DELTA_LF_SMALL` plus the §8.2.6 counter slot. A decoded
/// value of `DELTA_LF_SMALL` is the escape into the §5.11.13
/// `delta_lf_rem_bits` / `delta_lf_abs_bits` ladder.
pub fn delta_lf_cdf(&mut self) -> &mut [u16] {
&mut self.delta_lf
}
/// §8.3.2 `delta_lf_abs` (multi-LF branch, `delta_lf_multi ==
/// 1`): the cdf is `TileDeltaLFMultiCdf[ i ]`, with the index
/// `i` selecting the loop-filter-strength row in
/// `0..FRAME_LF_COUNT`. Same length / encoding as
/// [`Self::delta_lf_cdf`].
pub fn delta_lf_multi_cdf(&mut self, i: usize) -> &mut [u16] {
&mut self.delta_lf_multi[i]
}
/// §8.3.2 `comp_ref`: the cdf is `TileCompRefCdf[ ctx ][ p ]`, with
/// `p ∈ {0, 1, 2}` selecting `comp_ref` / `comp_ref_p1` /
/// `comp_ref_p2` (§8.3.2). `ctx` is `ref_count_ctx(..)` per the
/// matching §8.3.2 paragraph.
pub fn comp_ref_cdf(&mut self, ctx: usize, p: usize) -> &mut [u16] {
&mut self.comp_ref[ctx][p]
}
/// §8.3.2 `comp_bwdref`: the cdf is `TileCompBwdRefCdf[ ctx ][ p ]`,
/// with `p ∈ {0, 1}` selecting `comp_bwdref` / `comp_bwdref_p1`.
pub fn comp_bwd_ref_cdf(&mut self, ctx: usize, p: usize) -> &mut [u16] {
&mut self.comp_bwd_ref[ctx][p]
}
/// §8.3.2 `single_ref_p{1..6}`: the cdf is
/// `TileSingleRefCdf[ ctx ][ p ]`, with `p ∈ {0..5}` selecting
/// `single_ref_p1` .. `single_ref_p6` (the §8.3.2 list maps each
/// to the `comp_*` paragraph that defines its `ctx`).
pub fn single_ref_cdf(&mut self, ctx: usize, p: usize) -> &mut [u16] {
&mut self.single_ref[ctx][p]
}
/// §8.3.2 `compound_mode`: the cdf is `TileCompoundModeCdf[ ctx ]`,
/// with `ctx = Compound_Mode_Ctx_Map[ RefMvContext >> 1 ]
/// [ Min(NewMvContext, COMP_NEWMV_CTXS - 1) ]`. See
/// [`compound_mode_ctx`].
pub fn compound_mode_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.compound_mode[ctx]
}
/// §8.3.2 `comp_ref_type`: the cdf is `TileCompRefTypeCdf[ ctx ]`,
/// with `ctx` computed by the multi-branch paragraph in §8.3.2 (in
/// `0..COMP_REF_TYPE_CONTEXTS`). The branch evaluator belongs in
/// the (future) tile walk; this selector takes the already-computed
/// index.
pub fn comp_ref_type_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.comp_ref_type[ctx]
}
/// §8.3.2 `uni_comp_ref{,_p1,_p2}`: the cdf is
/// `TileUniCompRefCdf[ ctx ][ p ]`, with `p ∈ {0, 1, 2}` selecting
/// `uni_comp_ref` / `uni_comp_ref_p1` / `uni_comp_ref_p2`.
pub fn uni_comp_ref_cdf(&mut self, ctx: usize, p: usize) -> &mut [u16] {
&mut self.uni_comp_ref[ctx][p]
}
// -----------------------------------------------------------------
// Round 19 — palette / filter-intra / CFL §8.3.2 selectors. The
// caller pre-computes each `ctx` from the §5.11.x neighbour state
// (helpers [`palette_y_mode_ctx`], [`palette_uv_mode_ctx`],
// [`palette_color_ctx`], [`cfl_alpha_u_ctx`], [`cfl_alpha_v_ctx`]
// cover the parts that need only scalar inputs; `bsizeCtx` is the
// §5.11.46 block-size mapping the tile walk supplies).
// -----------------------------------------------------------------
/// §8.3.2 `use_filter_intra`: the cdf is `TileFilterIntraCdf[ MiSize ]`.
pub fn filter_intra_cdf(&mut self, mi_size: usize) -> &mut [u16] {
&mut self.filter_intra[mi_size]
}
/// §8.3.2 `filter_intra_mode`: the cdf is `TileFilterIntraModeCdf`
/// (a single context-free row).
pub fn filter_intra_mode_cdf(&mut self) -> &mut [u16] {
&mut self.filter_intra_mode
}
/// §8.3.2 `has_palette_y`: the cdf is
/// `TilePaletteYModeCdf[ bsizeCtx ][ ctx ]`. `bsizeCtx` is the
/// §5.11.46 block-size class (in `0..PALETTE_BLOCK_SIZE_CONTEXTS`);
/// `ctx` is the neighbour-palette count (see [`palette_y_mode_ctx`],
/// in `0..PALETTE_Y_MODE_CONTEXTS`).
pub fn palette_y_mode_cdf(&mut self, bsize_ctx: usize, ctx: usize) -> &mut [u16] {
&mut self.palette_y_mode[bsize_ctx][ctx]
}
/// §8.3.2 `has_palette_uv`: the cdf is `TilePaletteUVModeCdf[ ctx ]`,
/// with `ctx = (PaletteSizeY > 0) ? 1 : 0` (see [`palette_uv_mode_ctx`]).
pub fn palette_uv_mode_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.palette_uv_mode[ctx]
}
/// §8.3.2 `palette_size_y_minus_2`: the cdf is
/// `TilePaletteYSizeCdf[ bsizeCtx ]`.
pub fn palette_y_size_cdf(&mut self, bsize_ctx: usize) -> &mut [u16] {
&mut self.palette_y_size[bsize_ctx]
}
/// §8.3.2 `palette_size_uv_minus_2`: the cdf is
/// `TilePaletteUVSizeCdf[ bsizeCtx ]`.
pub fn palette_uv_size_cdf(&mut self, bsize_ctx: usize) -> &mut [u16] {
&mut self.palette_uv_size[bsize_ctx]
}
/// §8.3.2 `palette_color_idx_y`: the cdf is
/// `TilePaletteSize{2..8}YColorCdf[ ctx ]`, selected by `PaletteSizeY`
/// (in `2..=PALETTE_COLORS`). `ctx = Palette_Color_Context[ ColorContextHash ]`
/// (see [`palette_color_ctx`], in `0..PALETTE_COLOR_CONTEXTS`).
///
/// Returns `None` for a `palette_size_y` outside `2..=PALETTE_COLORS`
/// (a caller bug — the colour-index syntax is never reached otherwise).
pub fn palette_y_color_cdf(&mut self, palette_size_y: usize, ctx: usize) -> Option<&mut [u16]> {
match palette_size_y {
2 => Some(&mut self.palette_size_2_y_color[ctx]),
3 => Some(&mut self.palette_size_3_y_color[ctx]),
4 => Some(&mut self.palette_size_4_y_color[ctx]),
5 => Some(&mut self.palette_size_5_y_color[ctx]),
6 => Some(&mut self.palette_size_6_y_color[ctx]),
7 => Some(&mut self.palette_size_7_y_color[ctx]),
8 => Some(&mut self.palette_size_8_y_color[ctx]),
_ => None,
}
}
/// §8.3.2 `palette_color_idx_uv`: the cdf is
/// `TilePaletteSize{2..8}UVColorCdf[ ctx ]`, selected by `PaletteSizeUV`
/// (in `2..=PALETTE_COLORS`). `ctx = Palette_Color_Context[ ColorContextHash ]`.
///
/// Returns `None` for a `palette_size_uv` outside `2..=PALETTE_COLORS`.
pub fn palette_uv_color_cdf(
&mut self,
palette_size_uv: usize,
ctx: usize,
) -> Option<&mut [u16]> {
match palette_size_uv {
2 => Some(&mut self.palette_size_2_uv_color[ctx]),
3 => Some(&mut self.palette_size_3_uv_color[ctx]),
4 => Some(&mut self.palette_size_4_uv_color[ctx]),
5 => Some(&mut self.palette_size_5_uv_color[ctx]),
6 => Some(&mut self.palette_size_6_uv_color[ctx]),
7 => Some(&mut self.palette_size_7_uv_color[ctx]),
8 => Some(&mut self.palette_size_8_uv_color[ctx]),
_ => None,
}
}
/// §8.3.2 `cfl_alpha_signs`: the cdf is `TileCflSignCdf` (a single
/// context-free row).
pub fn cfl_sign_cdf(&mut self) -> &mut [u16] {
&mut self.cfl_sign
}
/// §8.3.2 `cfl_alpha_u` / `cfl_alpha_v`: the cdf is
/// `TileCflAlphaCdf[ ctx ]`, with `ctx` from [`cfl_alpha_u_ctx`] /
/// [`cfl_alpha_v_ctx`] (in `0..CFL_ALPHA_CONTEXTS`).
pub fn cfl_alpha_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.cfl_alpha[ctx]
}
/// §8.3.2 `tx_depth`: the cdf depends on the value of `maxTxDepth`
/// and `ctx`. Per the §8.3.2 paragraph:
///
/// * `TileTx64x64Cdf[ ctx ]` if `maxTxDepth == 4`
/// * `TileTx32x32Cdf[ ctx ]` if `maxTxDepth == 3`
/// * `TileTx16x16Cdf[ ctx ]` if `maxTxDepth == 2`
/// * `TileTx8x8Cdf[ ctx ]` otherwise (`maxTxDepth == 1`)
///
/// `ctx` is the [`tx_depth_ctx`] result (in `0..TX_SIZE_CONTEXTS`);
/// `max_tx_depth` is the `Max_Tx_Depth[ MiSize ]` value from §5.11.15.
/// Returns `None` when `max_tx_depth == 0` (no `tx_depth` is read in
/// that case — §5.11.15 forces `TxSize = maxRectTxSize`).
pub fn tx_depth_cdf(&mut self, max_tx_depth: u32, ctx: usize) -> Option<&mut [u16]> {
match max_tx_depth {
1 => Some(&mut self.tx_8x8[ctx]),
2 => Some(&mut self.tx_16x16[ctx]),
3 => Some(&mut self.tx_32x32[ctx]),
4 => Some(&mut self.tx_64x64[ctx]),
_ => None,
}
}
/// §8.3.2 `txfm_split`: the cdf is `TileTxfmSplitCdf[ ctx ]`, where
/// `ctx` is the [`txfm_split_ctx`] result (in
/// `0..TXFM_PARTITION_CONTEXTS`).
pub fn txfm_split_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.txfm_split[ctx]
}
/// §8.3.2 `inter_tx_type`: the cdf depends on the §5.11.48
/// `get_tx_set()` value as follows (spec §8.3.2, "inter_tx_type"):
///
/// * `TX_SET_INTER_1` ⇒ `TileInterTxTypeSet1Cdf[ Tx_Size_Sqr[ txSz ] ]`
/// * `TX_SET_INTER_2` ⇒ `TileInterTxTypeSet2Cdf` (no `txSz` axis)
/// * `TX_SET_INTER_3` ⇒ `TileInterTxTypeSet3Cdf[ Tx_Size_Sqr[ txSz ] ]`
///
/// `set` is the [`inter_tx_type_set`] result; `tx_size_sqr` is the
/// `Tx_Size_Sqr[ txSz ]` value supplied by §5.11.47 (in
/// `0..TX_SIZES`, but only the first 2 / 4 entries of Set1 / Set3
/// are addressable per the §5.11.48 routing). Returns `None` for
/// the §5.11.47 `set == TX_SET_DCTONLY` path (where `inter_tx_type`
/// is not read — `TxType = DCT_DCT` is forced), and for any
/// out-of-range `set` / `tx_size_sqr` combination that §5.11.48
/// would not actually produce.
pub fn inter_tx_type_cdf(&mut self, set: u32, tx_size_sqr: u32) -> Option<&mut [u16]> {
let sqr = tx_size_sqr as usize;
match set {
TX_SET_INTER_1 if sqr < INTER_TX_TYPE_SET1_SIZES => {
Some(&mut self.inter_tx_type_set1[sqr])
}
TX_SET_INTER_2 => Some(&mut self.inter_tx_type_set2),
TX_SET_INTER_3 if sqr < INTER_TX_TYPE_SET3_SIZES => {
Some(&mut self.inter_tx_type_set3[sqr])
}
_ => None,
}
}
/// §8.3.2 `intra_tx_type`: the cdf depends on the §5.11.48
/// `get_tx_set()` value as follows (spec §8.3.2, "intra_tx_type"):
///
/// * `TX_SET_INTRA_1` ⇒
/// `TileIntraTxTypeSet1Cdf[ Tx_Size_Sqr[ txSz ] ][ intraDir ]`
/// * `TX_SET_INTRA_2` ⇒
/// `TileIntraTxTypeSet2Cdf[ Tx_Size_Sqr[ txSz ] ][ intraDir ]`
///
/// `set` is the §5.11.48 `get_tx_set()` result on the
/// `is_inter == 0` branch (one of `TX_SET_DCTONLY`, `TX_SET_INTRA_1`,
/// `TX_SET_INTRA_2`); `tx_size_sqr` is `Tx_Size_Sqr[ txSz ]`
/// (`0..TX_SIZES`, but only the first 2 entries of Set1 / 3 entries
/// of Set2 are addressable per the §5.11.48 routing); `intra_dir` is
/// the §8.3.2 `intraDir` axis — `YMode` when `use_filter_intra == 0`
/// and `Filter_Intra_Mode_To_Intra_Dir[ filter_intra_mode ]`
/// otherwise (see [`FILTER_INTRA_MODE_TO_INTRA_DIR`]).
///
/// Returns `None` for:
/// * the §5.11.47 `set == TX_SET_DCTONLY` path (where `intra_tx_type`
/// is not read — `TxType = DCT_DCT` is forced),
/// * any out-of-range `set` / `tx_size_sqr` combination that
/// §5.11.48 would not actually produce, and
/// * `intra_dir >= INTRA_MODES` (a caller bug — the §3 enumeration
/// bounds `YMode` to `0..INTRA_MODES`).
pub fn intra_tx_type_cdf(
&mut self,
set: u32,
tx_size_sqr: u32,
intra_dir: usize,
) -> Option<&mut [u16]> {
if intra_dir >= INTRA_MODES {
return None;
}
let sqr = tx_size_sqr as usize;
match set {
TX_SET_INTRA_1 if sqr < INTRA_TX_TYPE_SET1_SIZES => {
Some(&mut self.intra_tx_type_set1[sqr][intra_dir])
}
TX_SET_INTRA_2 if sqr < INTRA_TX_TYPE_SET2_SIZES => {
Some(&mut self.intra_tx_type_set2[sqr][intra_dir])
}
_ => None,
}
}
/// §8.3.2 `interp_filter`: the cdf is `TileInterpFilterCdf[ ctx ]`,
/// with `ctx` in `0..INTERP_FILTER_CONTEXTS` (see
/// [`interp_filter_ctx`]). Returns `None` if `ctx` is out of range
/// (a caller bug — the §8.3.2 formula bounds the result).
pub fn interp_filter_cdf(&mut self, ctx: usize) -> Option<&mut [u16]> {
if ctx < INTERP_FILTER_CONTEXTS {
Some(&mut self.interp_filter[ctx])
} else {
None
}
}
/// §8.3.2 `motion_mode`: the cdf is `TileMotionModeCdf[ MiSize ]`,
/// a straight `0..BLOCK_SIZES` index — no neighbour-context
/// arithmetic. Returns `None` if `mi_size >= BLOCK_SIZES` (a caller
/// bug — the §3 enumeration bounds `MiSize`).
///
/// Per the §9.4 note on `Default_Motion_Mode_Cdf`, `MiSize` indices
/// `0..=2` and `16..=17` are never reached by the §5.11.x
/// `read_motion_mode` selection (the §5.11.x gate
/// `Block_Width >= 8 && Block_Height >= 8` excludes those block
/// sizes), but the selector still returns the in-table row so the
/// behaviour is purely indexical.
pub fn motion_mode_cdf(&mut self, mi_size: usize) -> Option<&mut [u16]> {
if mi_size < BLOCK_SIZES {
Some(&mut self.motion_mode[mi_size])
} else {
None
}
}
/// §8.3.2 `use_obmc`: the cdf is `TileUseObmcCdf[ MiSize ]`, a
/// straight `0..BLOCK_SIZES` index — no neighbour-context
/// arithmetic. Returns `None` if `mi_size >= BLOCK_SIZES` (a caller
/// bug — the §3 enumeration bounds `MiSize`).
///
/// Per the §9.4 note on `Default_Use_Obmc_Cdf`, `MiSize` indices
/// `0..=2` and `16..=17` are never reached by the §5.11.27
/// selection (the §5.11.27 gate `Min(Block_Width, Block_Height) >=
/// 8` excludes those block sizes), but the selector still returns
/// the in-table row so the behaviour is purely indexical.
pub fn use_obmc_cdf(&mut self, mi_size: usize) -> Option<&mut [u16]> {
if mi_size < BLOCK_SIZES {
Some(&mut self.use_obmc[mi_size])
} else {
None
}
}
/// §8.3.2 `comp_group_idx`: the cdf is `TileCompGroupIdxCdf[ ctx ]`,
/// with `ctx` computed by the §8.3.2 `comp_group_idx` paragraph
/// (which combines `CompGroupIdxs` neighbour values, `AvailU` /
/// `AvailL`, `AboveSingle` / `LeftSingle` and the `ALTREF_FRAME`
/// reference test, ending with `ctx = Min(5, ctx)` — in
/// `0..COMP_GROUP_IDX_CONTEXTS`). The neighbour arithmetic belongs
/// in the (future) tile walk; this selector takes the already-
/// computed index.
pub fn comp_group_idx_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.comp_group_idx[ctx]
}
/// §8.3.2 `compound_idx`: the cdf is `TileCompoundIdxCdf[ ctx ]`,
/// with `ctx` computed by the §8.3.2 `compound_idx` paragraph
/// (which seeds `ctx` from the `get_relative_dist` forward/backward
/// equality test, then adds the `CompoundIdxs` neighbour values /
/// `ALTREF_FRAME` test — in `0..COMPOUND_IDX_CONTEXTS`). The
/// neighbour arithmetic belongs in the (future) tile walk; this
/// selector takes the already-computed index.
pub fn compound_idx_cdf(&mut self, ctx: usize) -> &mut [u16] {
&mut self.compound_idx[ctx]
}
/// §8.3.2 `compound_type`: the cdf is
/// `TileCompoundTypeCdf[ MiSize ]`, a straight `0..BLOCK_SIZES`
/// index — no neighbour-context arithmetic. Returns `None` if
/// `mi_size >= BLOCK_SIZES` (a caller bug — the §3 enumeration
/// bounds `MiSize`).
///
/// Per the §9.4 note on `Default_Compound_Type_Cdf`, `MiSize`
/// indices `0..=2`, `10..=17` and `20..=21` are never reached by the
/// §5.11.x `read_compound_type` selection (those block sizes do not
/// satisfy the masked-compound gate), but the selector still returns
/// the in-table row so the behaviour is purely indexical.
pub fn compound_type_cdf(&mut self, mi_size: usize) -> Option<&mut [u16]> {
if mi_size < BLOCK_SIZES {
Some(&mut self.compound_type[mi_size])
} else {
None
}
}
/// §8.3.2 `y_mode`: the cdf is `TileYModeCdf[ ctx ]`, where the §8.3.2
/// `y_mode` paragraph computes `ctx = Size_Group[ MiSize ]` (a value
/// in `0..BLOCK_SIZE_GROUPS`). [`size_group`] performs that mapping;
/// this selector takes the already-computed context. Returns `None`
/// if `ctx >= BLOCK_SIZE_GROUPS` (a caller bug).
///
/// Distinct from [`Self::intra_frame_y_mode_cdf`], which is the
/// keyframe / intra-frame variant keyed by `[abovemode][leftmode]`.
pub fn y_mode_cdf(&mut self, ctx: usize) -> Option<&mut [u16]> {
if ctx < BLOCK_SIZE_GROUPS {
Some(&mut self.y_mode[ctx])
} else {
None
}
}
/// §8.3.2 `uv_mode`: selects between the two chroma intra-mode CDFs by
/// the `cfl_allowed` flag derived in the §8.3.2 `uv_mode` paragraph,
/// then indexes the chosen array by `YMode`:
///
/// * `cfl_allowed == true` → `TileUVModeCflAllowedCdf[ YMode ]`
/// (so `UV_CFL_PRED` is a coded value);
/// * `cfl_allowed == false` → `TileUVModeCflNotAllowedCdf[ YMode ]`.
///
/// The §8.3.2 derivation of `cfl_allowed` (the `Lossless` /
/// `get_plane_residual_size` / `Max(Block_Width, Block_Height) <= 32`
/// tests) belongs in the tile walk; this selector takes the resolved
/// flag plus `YMode`. Returns `None` if `y_mode >= INTRA_MODES`
/// (a caller bug — the §3 enumeration bounds `YMode`).
pub fn uv_mode_cdf(&mut self, cfl_allowed: bool, y_mode: usize) -> Option<&mut [u16]> {
if y_mode >= INTRA_MODES {
return None;
}
if cfl_allowed {
Some(&mut self.uv_mode_cfl_allowed[y_mode])
} else {
Some(&mut self.uv_mode_cfl_not_allowed[y_mode])
}
}
/// §8.3.2 `angle_delta_y` / `angle_delta_uv`: the cdf is
/// `TileAngleDeltaCdf[ YMode - V_PRED ]` for the luma element and
/// `TileAngleDeltaCdf[ UVMode - V_PRED ]` for the chroma element. Both
/// rebase the directional intra mode onto `0..DIRECTIONAL_MODES` by
/// subtracting `V_PRED`; this selector takes the directional `mode`
/// (`YMode` for `angle_delta_y`, `UVMode` for `angle_delta_uv`) and
/// performs the subtraction.
///
/// `angle_delta_y` / `angle_delta_uv` are only read when the mode is
/// directional (`V_PRED <= mode <= D67_PRED`, i.e. the `is_directional_mode`
/// gate in the §5.11 mode-info read). Returns `None` if `mode < V_PRED`
/// or `mode - V_PRED >= DIRECTIONAL_MODES` (a non-directional mode — a
/// caller bug, since the syntax wouldn't have coded an `angle_delta`).
pub fn angle_delta_cdf(&mut self, mode: usize) -> Option<&mut [u16]> {
if mode < V_PRED {
return None;
}
let idx = mode - V_PRED;
if idx < DIRECTIONAL_MODES {
Some(&mut self.angle_delta[idx])
} else {
None
}
}
// -----------------------------------------------------------------
// Round 136 — §8.3.1 `init_coeff_cdfs` + §8.3.2 coefficient-entry
// selectors.
// -----------------------------------------------------------------
/// §8.3.1 `init_coeff_cdfs`: select each coefficient-token working CDF
/// from its `Default_*_Cdf[ idx ]` slice, where `idx` is derived from
/// `base_q_idx` by [`coeff_cdf_q_ctx`].
///
/// This is the coefficient counterpart to [`Self::new_from_defaults`]
/// (which performs `init_non_coeff_cdfs`). It is invoked at the start
/// of every tile's `coeff( )` parsing.
pub fn init_coeff_cdfs(&mut self, base_q_idx: u8) {
let idx = coeff_cdf_q_ctx(base_q_idx);
self.txb_skip = DEFAULT_TXB_SKIP_CDF[idx];
self.eob_pt_16 = DEFAULT_EOB_PT_16_CDF[idx];
self.eob_pt_32 = DEFAULT_EOB_PT_32_CDF[idx];
self.eob_pt_64 = DEFAULT_EOB_PT_64_CDF[idx];
self.eob_pt_128 = DEFAULT_EOB_PT_128_CDF[idx];
self.eob_pt_256 = DEFAULT_EOB_PT_256_CDF[idx];
self.eob_pt_512 = DEFAULT_EOB_PT_512_CDF[idx];
self.eob_pt_1024 = DEFAULT_EOB_PT_1024_CDF[idx];
self.eob_extra = DEFAULT_EOB_EXTRA_CDF[idx];
self.dc_sign = DEFAULT_DC_SIGN_CDF[idx];
self.coeff_base_eob = DEFAULT_COEFF_BASE_EOB_CDF[idx];
self.coeff_base = DEFAULT_COEFF_BASE_CDF[idx];
self.coeff_br = DEFAULT_COEFF_BR_CDF[idx];
}
/// §8.3.2 `all_zero`: the cdf is `TileTxbSkipCdf[ txSzCtx ][ ctx ]`,
/// where `txSzCtx` is the transform-size context (`0..TX_SIZES`) and
/// `ctx` is the `all_zero` context (`0..TXB_SKIP_CONTEXTS`). Returns
/// `None` for out-of-range indices (a caller bug).
pub fn txb_skip_cdf(&mut self, tx_sz_ctx: usize, ctx: usize) -> Option<&mut [u16]> {
if tx_sz_ctx < TX_SIZES && ctx < TXB_SKIP_CONTEXTS {
Some(&mut self.txb_skip[tx_sz_ctx][ctx])
} else {
None
}
}
/// §8.3.2 `eob_pt_16` .. `eob_pt_256`: the cdf is
/// `TileEobPt{N}Cdf[ ptype ][ isInter ]`. `eobMultisize` selects which
/// of the seven EOB-position tables applies (the `8`-class blocks reach
/// `eob_pt_512` / `eob_pt_1024`, which have no `isInter` axis — see
/// [`Self::eob_pt_512_cdf`] / [`Self::eob_pt_1024_cdf`]). `ptype` is the
/// plane type (`0..PLANE_TYPES`); `is_inter` is `0` or `1`.
pub fn eob_pt_16_cdf(&mut self, ptype: usize, is_inter: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES && is_inter < 2 {
Some(&mut self.eob_pt_16[ptype][is_inter])
} else {
None
}
}
/// §8.3.2 `eob_pt_32`: `TileEobPt32Cdf[ ptype ][ isInter ]`.
pub fn eob_pt_32_cdf(&mut self, ptype: usize, is_inter: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES && is_inter < 2 {
Some(&mut self.eob_pt_32[ptype][is_inter])
} else {
None
}
}
/// §8.3.2 `eob_pt_64`: `TileEobPt64Cdf[ ptype ][ isInter ]`.
pub fn eob_pt_64_cdf(&mut self, ptype: usize, is_inter: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES && is_inter < 2 {
Some(&mut self.eob_pt_64[ptype][is_inter])
} else {
None
}
}
/// §8.3.2 `eob_pt_128`: `TileEobPt128Cdf[ ptype ][ isInter ]`.
pub fn eob_pt_128_cdf(&mut self, ptype: usize, is_inter: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES && is_inter < 2 {
Some(&mut self.eob_pt_128[ptype][is_inter])
} else {
None
}
}
/// §8.3.2 `eob_pt_256`: `TileEobPt256Cdf[ ptype ][ isInter ]`.
pub fn eob_pt_256_cdf(&mut self, ptype: usize, is_inter: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES && is_inter < 2 {
Some(&mut self.eob_pt_256[ptype][is_inter])
} else {
None
}
}
/// §8.3.2 `eob_pt_512`: `TileEobPt512Cdf[ ptype ]` (no `isInter` axis).
pub fn eob_pt_512_cdf(&mut self, ptype: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES {
Some(&mut self.eob_pt_512[ptype])
} else {
None
}
}
/// §8.3.2 `eob_pt_1024`: `TileEobPt1024Cdf[ ptype ]` (no `isInter`
/// axis).
pub fn eob_pt_1024_cdf(&mut self, ptype: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES {
Some(&mut self.eob_pt_1024[ptype])
} else {
None
}
}
/// §8.3.2 `eob_extra`: the cdf is
/// `TileEobExtraCdf[ txSzCtx ][ ptype ][ ctx ]`, where `ctx` is the
/// `eob_extra` context (`0..EOB_COEF_CONTEXTS`). Returns `None` for an
/// out-of-range index.
pub fn eob_extra_cdf(
&mut self,
tx_sz_ctx: usize,
ptype: usize,
ctx: usize,
) -> Option<&mut [u16]> {
if tx_sz_ctx < TX_SIZES && ptype < PLANE_TYPES && ctx < EOB_COEF_CONTEXTS {
Some(&mut self.eob_extra[tx_sz_ctx][ptype][ctx])
} else {
None
}
}
/// §8.3.2 `dc_sign`: the cdf is `TileDcSignCdf[ ptype ][ ctx ]`, where
/// `ctx` is the `dc_sign` context (`0..DC_SIGN_CONTEXTS`). Returns
/// `None` for an out-of-range index.
pub fn dc_sign_cdf(&mut self, ptype: usize, ctx: usize) -> Option<&mut [u16]> {
if ptype < PLANE_TYPES && ctx < DC_SIGN_CONTEXTS {
Some(&mut self.dc_sign[ptype][ctx])
} else {
None
}
}
/// §8.3.2 `coeff_base_eob`: the cdf is
/// `TileCoeffBaseEobCdf[ txSzCtx ][ ptype ][ ctx ]`, where `ctx` is
/// the `coeff_base_eob` context derived per §8.3.2 from
/// `get_coeff_base_ctx(...) - SIG_COEF_CONTEXTS + SIG_COEF_CONTEXTS_EOB`
/// (the `get_coeff_base_ctx()` lookup itself belongs to the
/// not-yet-implemented tile-content walk). Returns `None` for an
/// out-of-range index.
pub fn coeff_base_eob_cdf(
&mut self,
tx_sz_ctx: usize,
ptype: usize,
ctx: usize,
) -> Option<&mut [u16]> {
if tx_sz_ctx < TX_SIZES && ptype < PLANE_TYPES && ctx < SIG_COEF_CONTEXTS_EOB {
Some(&mut self.coeff_base_eob[tx_sz_ctx][ptype][ctx])
} else {
None
}
}
/// §8.3.2 `coeff_base`: the cdf is
/// `TileCoeffBaseCdf[ txSzCtx ][ ptype ][ ctx ]`, where `ctx` is the
/// `coeff_base` context — the §8.3.2 `get_coeff_base_ctx(...)` result
/// for a non-EOB coefficient, in `0..SIG_COEF_CONTEXTS` (the
/// `get_coeff_base_ctx()` lookup itself belongs to the
/// not-yet-implemented tile-content walk). Returns `None` for an
/// out-of-range index.
pub fn coeff_base_cdf(
&mut self,
tx_sz_ctx: usize,
ptype: usize,
ctx: usize,
) -> Option<&mut [u16]> {
if tx_sz_ctx < TX_SIZES && ptype < PLANE_TYPES && ctx < SIG_COEF_CONTEXTS {
Some(&mut self.coeff_base[tx_sz_ctx][ptype][ctx])
} else {
None
}
}
/// §8.3.2 `coeff_br`: the cdf is
/// `TileCoeffBrCdf[ Min(txSzCtx, TX_32X32) ][ ptype ][ ctx ]`, where
/// `ctx` is the §8.3.2 `get_br_ctx(...)` result in
/// `0..LEVEL_CONTEXTS` (the `get_br_ctx()` neighbour-magnitude
/// derivation itself needs tile-content walker state and belongs to
/// a later round).
///
/// The §3 `TX_32X32` constant (`= 3`) caps the `txSzCtx` selector at
/// the largest size for which `coeff_br` is parsed; this function
/// performs that clamp before indexing. Returns `None` only for an
/// out-of-range `ptype` / `ctx`; any `tx_sz_ctx` is accepted because
/// the selector clamps it.
pub fn coeff_br_cdf(
&mut self,
tx_sz_ctx: usize,
ptype: usize,
ctx: usize,
) -> Option<&mut [u16]> {
// §3 `TX_32X32 = 3`; clamp per the §8.3.2 spec selector.
const TX_32X32: usize = 3;
let clamped = if tx_sz_ctx > TX_32X32 {
TX_32X32
} else {
tx_sz_ctx
};
if ptype < PLANE_TYPES && ctx < LEVEL_CONTEXTS {
Some(&mut self.coeff_br[clamped][ptype][ctx])
} else {
None
}
}
// -----------------------------------------------------------------
// Round 143 — §8.3.2 inter-intra selectors.
// -----------------------------------------------------------------
/// §8.3.2 `interintra`: the cdf is `TileInterIntraCdf[ ctx ]`, where
/// the §8.3.2 `interintra` paragraph computes
/// `ctx = Size_Group[ MiSize ] - 1` (a value in
/// `0..(BLOCK_SIZE_GROUPS - 1)`). [`interintra_ctx`] performs that
/// mapping; this selector takes the already-computed context. Returns
/// `None` if `ctx >= BLOCK_SIZE_GROUPS - 1` (a caller bug).
pub fn inter_intra_cdf(&mut self, ctx: usize) -> Option<&mut [u16]> {
if ctx < BLOCK_SIZE_GROUPS - 1 {
Some(&mut self.inter_intra[ctx])
} else {
None
}
}
/// §8.3.2 `interintra_mode`: the cdf is
/// `TileInterIntraModeCdf[ ctx ]`, where the §8.3.2 `interintra_mode`
/// paragraph computes `ctx = Size_Group[ MiSize ] - 1` (a value in
/// `0..(BLOCK_SIZE_GROUPS - 1)`). [`interintra_ctx`] performs that
/// mapping; this selector takes the already-computed context. Returns
/// `None` if `ctx >= BLOCK_SIZE_GROUPS - 1` (a caller bug).
pub fn inter_intra_mode_cdf(&mut self, ctx: usize) -> Option<&mut [u16]> {
if ctx < BLOCK_SIZE_GROUPS - 1 {
Some(&mut self.inter_intra_mode[ctx])
} else {
None
}
}
/// §8.3.2 `wedge_interintra`: the cdf is
/// `TileWedgeInterIntraCdf[ MiSize ]`, a straight `0..BLOCK_SIZES`
/// index. Returns `None` if `mi_size >= BLOCK_SIZES` (a caller bug).
///
/// Per the §9.4 note (and the §5.11.28 syntax gate), only
/// `BLOCK_8X8..=BLOCK_32X32` (indices 3..=9) are reachable in
/// practice; the other rows are placeholder values and are surfaced
/// to keep `MiSize`-indexing uniform.
pub fn wedge_inter_intra_cdf(&mut self, mi_size: usize) -> Option<&mut [u16]> {
if mi_size < BLOCK_SIZES {
Some(&mut self.wedge_inter_intra[mi_size])
} else {
None
}
}
/// §8.3.2 `wedge_index`: the cdf is `TileWedgeIndexCdf[ MiSize ]`,
/// a straight `0..BLOCK_SIZES` index. Returns `None` if
/// `mi_size >= BLOCK_SIZES` (a caller bug).
///
/// Per the §9.4 `Default_Wedge_Index_Cdf` note ("Indices 0 to 2, 10
/// to 17, and 20 to 21 inclusive are never used in the first
/// dimension"), `wedge_index` is only ever coded for block sizes
/// where `Wedge_Bits[ MiSize ] > 0` (§3 table — indices 3..=9 plus
/// 18..=19); the other rows are placeholder uniform CDFs and are
/// surfaced to keep `MiSize`-indexing uniform with every other
/// `[ BLOCK_SIZES ]` table.
pub fn wedge_index_cdf(&mut self, mi_size: usize) -> Option<&mut [u16]> {
if mi_size < BLOCK_SIZES {
Some(&mut self.wedge_index[mi_size])
} else {
None
}
}
}
impl Default for TileCdfContext {
fn default() -> Self {
Self::new_from_defaults()
}
}
// ---------------------------------------------------------------------
// §8.3.2 context-derivation helpers (the parts that need only scalar
// neighbour inputs — the full neighbour lookups live in the tile walk).
// ---------------------------------------------------------------------
/// §8.3.2 `intra_frame_y_mode` context mapping:
/// `Intra_Mode_Context[ mode ]`. `mode` is a neighbour's `YMode` (or
/// `DC_PRED == 0` when that neighbour is unavailable).
pub fn intra_mode_ctx(mode: usize) -> usize {
INTRA_MODE_CONTEXT[mode]
}
/// §8.3.2 `y_mode` context mapping: `ctx = Size_Group[ MiSize ]`. The
/// result selects a [`TileCdfContext::y_mode_cdf`] row and is in
/// `0..BLOCK_SIZE_GROUPS`. `mi_size` is the current block's `MiSize`
/// (`0..BLOCK_SIZES`).
pub fn size_group(mi_size: usize) -> usize {
SIZE_GROUP[mi_size]
}
/// §5.11.44 `is_directional_mode( mode )`: returns `true` if `mode` is
/// a directional intra mode, i.e. `V_PRED <= mode <= D67_PRED`. The
/// §5.11.42 / §5.11.43 `intra_angle_info_{y,uv}` gates fire only when
/// this predicate holds; the §8.3.2 `intra_tx_type` selection uses the
/// `Filter_Intra_Mode_To_Intra_Dir` remap (a different `mode` source)
/// on its `use_filter_intra == 1` branch.
#[inline]
#[must_use]
pub const fn is_directional(mode: u8) -> bool {
let m = mode as usize;
m >= V_PRED && m <= D67_PRED
}
/// §8.3.2 `uv_mode` CFL-allowance derivation. The §8.3.2 paragraph
/// reads:
///
/// ```text
/// • Lossless == 1 AND get_plane_residual_size( MiSize, 1 ) == BLOCK_4X4
/// ⇒ TileUVModeCflAllowedCdf[ YMode ]
/// • Otherwise, Lossless == 0 AND Max( Block_Width[ MiSize ],
/// Block_Height[ MiSize ] ) <= 32
/// ⇒ TileUVModeCflAllowedCdf[ YMode ]
/// • Otherwise
/// ⇒ TileUVModeCflNotAllowedCdf[ YMode ]
/// ```
///
/// `lossless` is the §5.11.18 / §5.11.7 `LosslessArray[ segment_id ]`
/// value the caller already derived. The §5.11.38
/// `get_plane_residual_size( MiSize, 1 )` test on the `lossless`
/// branch checks whether the chroma residual block — after
/// `subsampling_x` / `subsampling_y` are applied to the luma
/// `Block_Width` / `Block_Height` — collapses to BLOCK_4X4 (i.e. both
/// dimensions equal `4`).
///
/// Returns `false` if `mi_size >= BLOCK_SIZES` (a caller bug — the
/// §5.11.22 dispatcher's bounds guard fires earlier in that case).
#[inline]
#[must_use]
pub fn cfl_allowed_for_uv_mode(
lossless: bool,
mi_size: usize,
subsampling_x: bool,
subsampling_y: bool,
) -> bool {
if mi_size >= BLOCK_SIZES {
return false;
}
let bw = block_width(mi_size);
let bh = block_height(mi_size);
if lossless {
// §5.11.38 get_plane_residual_size(MiSize, 1) == BLOCK_4X4 ⇔
// post-subsampling chroma residual is 4×4. The §5.11.38 path
// applies the §5.7.2 subsampling shifts directly:
// subW = bw >> subsampling_x
// subH = bh >> subsampling_y
// The result is BLOCK_4X4 iff both ≤ 4 (the §5.11.38 routing
// promotes a 2×n / n×2 down-shift back to 4×n / n×4 via the
// §3 enumeration, which still keeps the chroma residual size
// at BLOCK_4X4 for the lossless / smallest-block intersection
// — the conformant gate here is "post-subsampling block fits
// in a 4×4 chroma cell"). Equivalent: bw / bh <= 4 once
// shifted.
let sub_w = bw >> (subsampling_x as usize);
let sub_h = bh >> (subsampling_y as usize);
sub_w <= 4 && sub_h <= 4
} else {
// §8.3.2 second arm: Max(Block_Width, Block_Height) <= 32.
bw.max(bh) <= 32
}
}
/// §8.3.1 `init_coeff_cdfs`: derive the coefficient-CDF q-context `idx`
/// from `base_q_idx`:
///
/// ```text
/// if base_q_idx <= 20 idx = 0
/// else if base_q_idx <= 60 idx = 1
/// else if base_q_idx <= 120 idx = 2
/// else idx = 3
/// ```
///
/// The result selects the `Default_*_Cdf[ idx ]` slice copied into the
/// working coefficient CDFs (and is in `0..COEFF_CDF_Q_CTXS`).
pub fn coeff_cdf_q_ctx(base_q_idx: u8) -> usize {
if base_q_idx <= 20 {
0
} else if base_q_idx <= 60 {
1
} else if base_q_idx <= 120 {
2
} else {
3
}
}
/// §8.3.2 `partition` context:
///
/// ```text
/// ctx = left * 2 + above
/// ```
///
/// where `above` / `left` are the booleans
/// `AvailU && (Mi_Width_Log2[..] < bsl)` /
/// `AvailL && (Mi_Height_Log2[..] < bsl)` evaluated by the tile walk.
pub fn partition_ctx(above: bool, left: bool) -> usize {
(left as usize) * 2 + (above as usize)
}
/// §8.3.2 `split_or_horz`: derive the 2-symbol binary cdf from the
/// already-selected `partition` cdf (the spec's `partitionCdf`). Folds
/// the §9.4 partition probabilities of `PARTITION_VERT` /
/// `PARTITION_SPLIT` / `PARTITION_HORZ_A` / `PARTITION_VERT_A` /
/// `PARTITION_VERT_B` (plus `PARTITION_VERT_4` when `b_size` is not
/// [`BLOCK_128X128`]) into a single "split" probability per the §8.3.2
/// note ("the probability for a vertical partition is assigned to the
/// probability for the split partition"), and emits the binary
/// `cdf[0] = (1 << 15) - psum`, `cdf[1] = 1 << 15`, `cdf[2] = 0` per
/// p.362.
///
/// The §8.3.2 selection guarantees `partition_cdf` is one of the
/// `bsl >= 2` rows (W16 / W32 / W64 / W128), so the read range is well
/// within bounds: `EXT_PARTITION_TYPES` for W16 / W32 / W64, and the
/// `b_size == BLOCK_128X128` branch's read of `PARTITION_VERT_B` =
/// index 7 fits inside the 9-entry W128 row.
///
/// Returns `None` if `partition_cdf` is too short for the indexing
/// (a caller bug — `bsl == 1` (W8) is never reached for `split_or_horz`
/// per the §8.3.2 note "bsl is never equal to 1 when decoding
/// split_or_horz").
pub fn split_or_horz_cdf(partition_cdf: &[u16], b_size: usize) -> Option<[u16; 3]> {
// Always-present terms: VERT / SPLIT / HORZ_A / VERT_A / VERT_B.
// The largest index read in this group is PARTITION_VERT_B (= 7), so
// the row must have at least 8 entries (PARTITION_VERT_B - 1 == 6
// plus the partitionCdf[VERT_B] read at index 7).
let need = PARTITION_VERT_B + 1;
if partition_cdf.len() < need {
return None;
}
let mut psum: i32 = 0;
psum += partition_cdf[PARTITION_VERT] as i32 - partition_cdf[PARTITION_VERT - 1] as i32;
psum += partition_cdf[PARTITION_SPLIT] as i32 - partition_cdf[PARTITION_SPLIT - 1] as i32;
psum += partition_cdf[PARTITION_HORZ_A] as i32 - partition_cdf[PARTITION_HORZ_A - 1] as i32;
psum += partition_cdf[PARTITION_VERT_A] as i32 - partition_cdf[PARTITION_VERT_A - 1] as i32;
psum += partition_cdf[PARTITION_VERT_B] as i32 - partition_cdf[PARTITION_VERT_B - 1] as i32;
if b_size != BLOCK_128X128 {
// Index PARTITION_VERT_4 (= 9) is only present in the W16/W32/W64
// rows; the §8.3.2 selection's `b_size != BLOCK_128X128` guard
// ensures the row is long enough.
if partition_cdf.len() <= PARTITION_VERT_4 {
return None;
}
psum += partition_cdf[PARTITION_VERT_4] as i32 - partition_cdf[PARTITION_VERT_4 - 1] as i32;
}
// Per §8.2.6 well-formedness, every partitionCdf is monotonically
// increasing in [0, 1 << 15], so each (cdf[i] - cdf[i-1]) is in
// [0, 1 << 15] and the sum is also in [0, 1 << 15] (the §8.2.6
// probability mass is bounded by 1).
let psum_u16 = psum.clamp(0, 1 << 15) as u16;
Some([(1 << 15) - psum_u16, 1 << 15, 0])
}
/// §8.3.2 `split_or_vert`: derive the 2-symbol binary cdf from the
/// already-selected `partition` cdf. Folds the §9.4 partition
/// probabilities of `PARTITION_HORZ` / `PARTITION_SPLIT` /
/// `PARTITION_HORZ_A` / `PARTITION_HORZ_B` / `PARTITION_VERT_A` (plus
/// `PARTITION_HORZ_4` when `b_size` is not [`BLOCK_128X128`]) into a
/// single "split" probability, and emits the binary
/// `cdf[0] = (1 << 15) - psum`, `cdf[1] = 1 << 15`, `cdf[2] = 0` per
/// p.362.
///
/// As with [`split_or_horz_cdf`], the §8.3.2 selection guarantees
/// `bsl >= 2` so the read range never reaches the W8 row, and
/// `PARTITION_VERT_A` (= 6) fits inside the W128 row.
///
/// Returns `None` if `partition_cdf` is too short for the indexing
/// (a caller bug — `bsl == 1` is never reached for `split_or_vert`).
pub fn split_or_vert_cdf(partition_cdf: &[u16], b_size: usize) -> Option<[u16; 3]> {
// Always-present terms: HORZ / SPLIT / HORZ_A / HORZ_B / VERT_A.
// Largest index here is PARTITION_VERT_A (= 6).
let need = PARTITION_VERT_A + 1;
if partition_cdf.len() < need {
return None;
}
let mut psum: i32 = 0;
psum += partition_cdf[PARTITION_HORZ] as i32 - partition_cdf[PARTITION_HORZ - 1] as i32;
psum += partition_cdf[PARTITION_SPLIT] as i32 - partition_cdf[PARTITION_SPLIT - 1] as i32;
psum += partition_cdf[PARTITION_HORZ_A] as i32 - partition_cdf[PARTITION_HORZ_A - 1] as i32;
psum += partition_cdf[PARTITION_HORZ_B] as i32 - partition_cdf[PARTITION_HORZ_B - 1] as i32;
psum += partition_cdf[PARTITION_VERT_A] as i32 - partition_cdf[PARTITION_VERT_A - 1] as i32;
if b_size != BLOCK_128X128 {
if partition_cdf.len() <= PARTITION_HORZ_4 {
return None;
}
psum += partition_cdf[PARTITION_HORZ_4] as i32 - partition_cdf[PARTITION_HORZ_4 - 1] as i32;
}
let psum_u16 = psum.clamp(0, 1 << 15) as u16;
Some([(1 << 15) - psum_u16, 1 << 15, 0])
}
/// §8.3.2 `skip` context:
///
/// ```text
/// ctx = 0
/// if ( AvailU ) ctx += Skips[ MiRow - 1 ][ MiCol ]
/// if ( AvailL ) ctx += Skips[ MiRow ][ MiCol - 1 ]
/// ```
///
/// `above_skip` / `left_skip` are the neighbour `Skips[]` values (0 or
/// 1), already gated on `AvailU` / `AvailL` by the caller (an
/// unavailable neighbour contributes 0).
pub fn skip_ctx(above_skip: u8, left_skip: u8) -> usize {
(above_skip + left_skip) as usize
}
/// §8.3.2 `segment_id` context:
///
/// ```text
/// if ( prevUL < 0 ) ctx = 0
/// else if ( (prevUL == prevU) && (prevUL == prevL) ) ctx = 2
/// else if ( (prevUL == prevU) || (prevUL == prevL)
/// || (prevU == prevL) ) ctx = 1
/// else ctx = 0
/// ```
///
/// `prevUL` / `prevU` / `prevL` are the above-left / above / left
/// neighbour segment ids; an unavailable neighbour is signalled with
/// [`None`] (the spec's negative sentinel). When `prev_ul` is `None`
/// the result is `0` regardless of the others.
pub fn segment_id_ctx(prev_ul: Option<i32>, prev_u: Option<i32>, prev_l: Option<i32>) -> usize {
// prevUL < 0 (unavailable) ⇒ ctx = 0 unconditionally.
let ul = match prev_ul {
Some(v) => v,
None => return 0,
};
// A missing U or L neighbour is the spec's negative sentinel and so
// cannot equal anything (not even another missing neighbour — the
// spec compares concrete segment ids, not the sentinel).
let ul_eq_u = prev_u == Some(ul);
let ul_eq_l = prev_l == Some(ul);
let u_eq_l = match (prev_u, prev_l) {
(Some(u), Some(l)) => u == l,
_ => false,
};
if ul_eq_u && ul_eq_l {
2
} else if ul_eq_u || ul_eq_l || u_eq_l {
1
} else {
0
}
}
/// `get_segment_id()` per §5.11.21 (av1-spec p.72) — the inter-frame
/// per-block segment-id **prediction** function. Returns the smallest
/// `PrevSegmentIds[ row ][ col ]` value found within the on-screen
/// region of the previous-frame segmentation map covered by the
/// current block. The §5.11.19 `inter_segment_id( preSkip )` body
/// reads `predictedSegmentId = get_segment_id()` once per block; the
/// returned value is then either adopted directly
/// (`!segmentation_update_map` arm; `seg_id_predicted == 1` arm) or
/// folded into the §5.11.9 `read_segment_id()` `pred` derivation.
///
/// Spec body (av1-spec p.72):
///
/// ```text
/// get_segment_id( ) {
/// bw4 = Num_4x4_Blocks_Wide[ MiSize ]
/// bh4 = Num_4x4_Blocks_High[ MiSize ]
/// xMis = Min( MiCols - MiCol, bw4 )
/// yMis = Min( MiRows - MiRow, bh4 )
/// seg = 7
/// for ( y = 0; y < yMis; y++ )
/// for ( x = 0; x < xMis; x++ )
/// seg = Min( seg, PrevSegmentIds[ MiRow + y ][ MiCol + x ] )
/// return seg
/// }
/// ```
///
/// The initial sentinel `seg = 7` is `MAX_SEGMENTS - 1` — the
/// largest legal segment id, so any same-or-smaller previous-frame id
/// inside the window beats it; a window that finds no cell below `7`
/// (e.g. a fresh previous frame with every cell at `7`) returns `7`.
///
/// `mi_row` / `mi_col` are the current block's top-left mi-unit
/// position. `sub_size` is the §3 `BLOCK_*` ordinal indexing the §9.3
/// [`NUM_4X4_BLOCKS_WIDE`] / [`NUM_4X4_BLOCKS_HIGH`] tables. `mi_rows`
/// / `mi_cols` are the **current** frame's mi-extent (the spec writes
/// `MiCols` / `MiRows`, which are §3 sequence-header-derived current-
/// frame values), used only to clip `xMis` / `yMis` to the frame
/// edge — they are unrelated to the previous frame's extent which is
/// captured implicitly by `prev_mi_cols` and the length of
/// `prev_segment_ids`.
///
/// `prev_segment_ids` is the previous frame's `SegmentIds[][]` grid
/// in row-major layout: `prev_segment_ids[ r * prev_mi_cols + c ]`.
/// Stored as `i32` so the §5.11.9 sentinel value `-1` (an
/// unwritten cell — a previous frame with `segmentation_enabled ==
/// false` writes the literal `0` instead of `-1`, but a partially
/// written walker's grid retains `-1` per the
/// [`PartitionWalker::segment_ids`] initialisation) round-trips
/// faithfully; the `Min` reduction naturally treats `-1` as the
/// smallest possible value.
///
/// `prev_mi_rows` / `prev_mi_cols` are the previous frame's mi-extent
/// (the shape of `prev_segment_ids`). The §5.11.21 walk indexes
/// `PrevSegmentIds[ MiRow + y ][ MiCol + x ]` directly — a
/// previous-frame extent smaller than the current frame's would mean
/// the §6.10 reference-frame walk produced an inconsistent surface,
/// which is a caller bug surfaced as the out-of-range error rather
/// than silently clipping.
///
/// Returns `None` for caller-bug arguments:
/// * `sub_size >= BLOCK_SIZES` (not a valid §3 ordinal);
/// * `mi_row >= mi_rows` or `mi_col >= mi_cols` (current block
/// outside the current frame);
/// * `prev_mi_rows < mi_rows` or `prev_mi_cols < mi_cols` (previous
/// frame's segmentation surface doesn't cover the current frame's
/// extent);
/// * `prev_segment_ids.len() != prev_mi_rows * prev_mi_cols`
/// (length / shape mismatch).
///
/// Otherwise returns the §5.11.21 spec value as an `i32` (range
/// `-1..=7` — the `-1` sentinel surfaces only if a `-1` cell falls
/// inside the window).
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn get_segment_id(
prev_segment_ids: &[i32],
prev_mi_rows: u32,
prev_mi_cols: u32,
mi_rows: u32,
mi_cols: u32,
mi_row: u32,
mi_col: u32,
sub_size: usize,
) -> Option<i32> {
if sub_size >= BLOCK_SIZES {
return None;
}
if mi_row >= mi_rows || mi_col >= mi_cols {
return None;
}
if prev_mi_rows < mi_rows || prev_mi_cols < mi_cols {
return None;
}
let prev_area = (prev_mi_rows as usize).checked_mul(prev_mi_cols as usize)?;
if prev_segment_ids.len() != prev_area {
return None;
}
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
// §5.11.21 `xMis = Min( MiCols - MiCol, bw4 )`; `yMis = Min( MiRows
// - MiRow, bh4 )`. The subtractions are safe because the range
// guards above proved `mi_col < mi_cols` and `mi_row < mi_rows`.
let x_mis = bw4.min(mi_cols - mi_col);
let y_mis = bh4.min(mi_rows - mi_row);
// §5.11.21 `seg = 7` initial sentinel — `MAX_SEGMENTS - 1`, the
// largest legal id, so any same-or-smaller previous-frame id in
// the window wins.
let mut seg: i32 = (MAX_SEGMENTS as i32) - 1;
for y in 0..y_mis {
let rr = mi_row + y;
for x in 0..x_mis {
let cc = mi_col + x;
// `prev_segment_ids` is row-major over the previous
// frame's `prev_mi_cols`-wide grid. `rr < mi_rows <=
// prev_mi_rows` and `cc < mi_cols <= prev_mi_cols` by
// construction, so the index never overflows.
let v = prev_segment_ids[(rr * prev_mi_cols + cc) as usize];
if v < seg {
seg = v;
}
}
}
Some(seg)
}
/// §5.11.9 `neg_deinterleave` — the spec helper that reconstructs a
/// `segment_id` from the on-wire `diff` (the §5.11.9 `S()` symbol) the
/// caller-known `ref` value (the §5.11.9 `pred` derivation), and the
/// `max = LastActiveSegId + 1` upper bound. Spec body (av1-spec p.66):
///
/// ```text
/// neg_deinterleave( diff, ref, max ) {
/// if ( !ref ) return diff
/// if ( ref >= (max - 1) ) return max - diff - 1
/// if ( 2 * ref < max ) {
/// if ( diff <= 2 * ref ) {
/// if ( diff & 1 ) return ref + ((diff + 1) >> 1)
/// else return ref - (diff >> 1)
/// }
/// return diff
/// } else {
/// if ( diff <= 2 * (max - ref - 1) ) {
/// if ( diff & 1 ) return ref + ((diff + 1) >> 1)
/// else return ref - (diff >> 1)
/// }
/// return max - (diff + 1)
/// }
/// }
/// ```
///
/// The function is a bijection between `diff ∈ 0..max` and
/// `segment_id ∈ 0..max` for a fixed `ref ∈ 0..max`, biasing the
/// small-`diff` end of the alphabet toward values close to `ref` (the
/// `pred`-based context) so the entropy coder spends fewer bits on
/// the common "small neighbour delta" case. The two `if ( diff & 1
/// )` branches alternate the sign of the offset away from `ref`
/// (odd `diff` walks upward from `ref`; even `diff` walks downward),
/// and the `2 * ref < max` test selects whether the upward or the
/// downward side runs out first.
///
/// All arguments are `u32`. Inputs are spec-bounded: `ref < max` and
/// `diff < max` per the §5.11.9 surrounds (`ref` is `pred ∈
/// 0..LastActiveSegId+1`; `diff` is the §8.2.6 `S()` value, which the
/// [`PartitionWalker::decode_segment_id`] caller validates against
/// `LastActiveSegId` per §6.10.8 — the raw 8-symbol alphabet can code
/// values past `max` on a malformed stream, surfaced there as
/// [`crate::Error::SegmentIdOutOfRange`] before this function runs).
/// The function panics on `max == 0` (a caller bug:
/// `max = LastActiveSegId + 1 >= 1` always).
#[must_use]
pub fn neg_deinterleave(diff: u32, r: u32, max: u32) -> u32 {
debug_assert!(max >= 1, "max = LastActiveSegId + 1 >= 1");
debug_assert!(r < max, "ref < max per the §5.11.9 surrounds");
if r == 0 {
return diff;
}
if r >= max - 1 {
return max - diff - 1;
}
if 2 * r < max {
if diff <= 2 * r {
if diff & 1 != 0 {
r + ((diff + 1) >> 1)
} else {
r - (diff >> 1)
}
} else {
diff
}
} else if diff <= 2 * (max - r - 1) {
if diff & 1 != 0 {
r + ((diff + 1) >> 1)
} else {
r - (diff >> 1)
}
} else {
max - (diff + 1)
}
}
/// Analytic inverse of [`neg_deinterleave`] — the §5.11.9 encoder-side
/// helper that derives the `diff` symbol the §8.2.6 `S()` write must
/// emit so that the decoder's `neg_deinterleave( diff, pred, max )`
/// reconstructs the caller's `segment_id`.
///
/// `neg_deinterleave` is a bijection on `0..max` for any fixed
/// `pred ∈ 0..max`, so this inverse is total over the same domain.
/// Each spec arm has an algebraic inverse (no search):
///
/// * `pred == 0` — `neg_deinterleave` is the identity; so is this.
/// * `pred >= max - 1` — `neg_deinterleave` is `max - diff - 1`;
/// inverse is `diff = max - segment_id - 1`.
/// * `2 * pred < max` ("upward-bias" arm): the bidirectional fan
/// centred on `pred` covers `segment_id ∈ 0..=2 * pred`. Inside the
/// fan, odd `diff` walks up (`diff = 2 * (segment_id - pred) - 1`),
/// even `diff` walks down (`diff = 2 * (pred - segment_id)`).
/// Outside the fan (`segment_id > 2 * pred`), `neg_deinterleave`
/// is the identity, so `diff = segment_id`.
/// * `2 * pred >= max` ("downward-bias" arm): the bidirectional fan
/// covers `segment_id ∈ (2 * pred - max + 1)..max`. Inside the fan,
/// the same up/down formulas hold. Outside the fan (`segment_id <
/// 2 * pred - max + 1`), `neg_deinterleave` is
/// `max - (diff + 1)`, so `diff = max - segment_id - 1`.
///
/// All arguments are `u32`. Preconditions: `max >= 1`, `pred < max`,
/// `segment_id < max`. The decoder-side `debug_assert!` shape carries
/// over — these correspond to the §5.11.9 spec surrounds. Panics on
/// `max == 0` (caller bug; `max = LastActiveSegId + 1 >= 1` always).
///
/// The implementation is bit-exact paired with [`neg_deinterleave`]:
/// `neg_deinterleave( neg_interleave( s, p, m ), p, m ) == s` for
/// every `(s, p, m)` with `s < m` and `p < m` and `m >= 1`. Exhaustive
/// round-trip tests over `m ∈ 1..=MAX_SEGMENTS + 1 = 9` live alongside
/// the forward tests.
#[must_use]
pub fn neg_interleave(segment_id: u32, pred: u32, max: u32) -> u32 {
debug_assert!(max >= 1, "max = LastActiveSegId + 1 >= 1");
debug_assert!(pred < max, "pred < max per the §5.11.9 surrounds");
debug_assert!(
segment_id < max,
"segment_id < max per the §5.11.9 surrounds"
);
if pred == 0 {
return segment_id;
}
if pred >= max - 1 {
return max - segment_id - 1;
}
// §5.11.9 `2 * pred < max` arm — upward-bias bidirectional fan.
if 2 * pred < max {
let fan_high = 2 * pred; // segment_ids 0..=fan_high covered by the fan
if segment_id <= fan_high {
// Inside the fan. segment_id == pred ⇒ diff == 0.
// segment_id > pred ⇒ odd diff = 2 * (segment_id - pred) - 1.
// segment_id < pred ⇒ even diff = 2 * (pred - segment_id).
if segment_id == pred {
0
} else if segment_id > pred {
2 * (segment_id - pred) - 1
} else {
2 * (pred - segment_id)
}
} else {
// Outside the fan: `neg_deinterleave` is the identity
// (`return diff`), so `diff = segment_id`.
segment_id
}
} else {
// §5.11.9 `2 * pred >= max` arm — downward-bias bidirectional fan.
let fan_low = 2 * pred + 1 - max; // segment_ids fan_low..max covered
if segment_id >= fan_low {
// Inside the fan, same up/down formulas as the upward arm.
if segment_id == pred {
0
} else if segment_id > pred {
2 * (segment_id - pred) - 1
} else {
2 * (pred - segment_id)
}
} else {
// Outside the fan: `neg_deinterleave` is `max - (diff +
// 1)`, so `diff = max - segment_id - 1`.
max - segment_id - 1
}
}
}
/// §5.11.31 `read_mv()` `MvCtx` derivation. Returns
/// [`MV_INTRABC_CONTEXT`] when `use_intrabc == 1` and `0` otherwise;
/// the result is the first index into every `Mv*Cdf` selector above.
pub fn mv_ctx(use_intrabc: bool) -> usize {
if use_intrabc {
MV_INTRABC_CONTEXT
} else {
0
}
}
// ---------------------------------------------------------------------
// Round 18 — inter-mode / reference-frame §8.3.2 context helpers. Each
// directly transcribes one §8.3.2 paragraph (the scalar fragment that
// needs only neighbour-summary inputs; the AvailU/AvailL gating and the
// neighbour ref-frame lookups belong to the tile walk).
// ---------------------------------------------------------------------
/// §8.3.2 `is_inter` context:
///
/// ```text
/// if ( AvailU && AvailL )
/// ctx = (LeftIntra && AboveIntra) ? 3 : LeftIntra || AboveIntra
/// else if ( AvailU || AvailL )
/// ctx = 2 * (AvailU ? AboveIntra : LeftIntra)
/// else
/// ctx = 0
/// ```
///
/// Each input is `Some(true)` (neighbour is intra) / `Some(false)`
/// (neighbour is inter), or `None` if the neighbour is unavailable.
pub fn is_inter_ctx(above_intra: Option<bool>, left_intra: Option<bool>) -> usize {
match (above_intra, left_intra) {
(Some(a), Some(l)) => {
if a && l {
3
} else {
(a || l) as usize
}
}
(Some(a), None) => 2 * (a as usize),
(None, Some(l)) => 2 * (l as usize),
(None, None) => 0,
}
}
/// §8.3.2 `skip_mode` context: sum of the neighbour `SkipModes[]` flags
/// (0 if the neighbour is unavailable).
///
/// ```text
/// ctx = 0
/// if ( AvailU ) ctx += SkipModes[ MiRow - 1 ][ MiCol ]
/// if ( AvailL ) ctx += SkipModes[ MiRow ][ MiCol - 1 ]
/// ```
///
/// Result in `0..SKIP_MODE_CONTEXTS`.
pub fn skip_mode_ctx(above_skip_mode: u8, left_skip_mode: u8) -> usize {
(above_skip_mode + left_skip_mode) as usize
}
/// §8.3.2 `ref_count_ctx` (the inner helper used by every
/// `single_ref_p*` / `comp_ref` / `comp_bwdref` / `uni_comp_ref_p*`
/// selection):
///
/// ```text
/// if ( counts0 < counts1 ) return 0
/// else if ( counts0 == counts1 ) return 1
/// else return 2
/// ```
///
/// Result in `0..REF_CONTEXTS`.
pub fn ref_count_ctx(counts0: u32, counts1: u32) -> usize {
use core::cmp::Ordering;
match counts0.cmp(&counts1) {
Ordering::Less => 0,
Ordering::Equal => 1,
Ordering::Greater => 2,
}
}
/// §8.3.2 `compound_mode` context:
///
/// ```text
/// ctx = Compound_Mode_Ctx_Map[ RefMvContext >> 1 ]
/// [ Min(NewMvContext, COMP_NEWMV_CTXS - 1) ]
/// ```
///
/// `ref_mv_context` is the §8.3.2 `RefMvContext`; `new_mv_context` is the
/// §8.3.2 `NewMvContext`. Returns a value in `0..COMPOUND_MODE_CONTEXTS`.
///
/// The `RefMvContext >> 1` selects one of the three [`COMPOUND_MODE_CTX_MAP`]
/// rows; the `Min(.., COMP_NEWMV_CTXS - 1)` clamps the `NewMvContext`
/// to the map's 5-wide second axis.
pub fn compound_mode_ctx(ref_mv_context: usize, new_mv_context: usize) -> usize {
let row = (ref_mv_context >> 1).min(COMPOUND_MODE_CTX_MAP.len() - 1);
let col = new_mv_context.min(COMP_NEWMV_CTXS - 1);
COMPOUND_MODE_CTX_MAP[row][col]
}
// ---------------------------------------------------------------------
// Round 19 — palette / filter-intra / CFL §8.3.2 context helpers. Each
// computes a `ctx` from the scalar neighbour inputs the §5.11.x syntax
// supplies (the §5.11.46 `bsizeCtx` itself comes from the block-size
// tables in the tile walk and is passed through directly).
// ---------------------------------------------------------------------
/// §8.3.2 `has_palette_y` context:
///
/// ```text
/// ctx = 0
/// if ( AvailU && PaletteSizes[ 0 ][ MiRow - 1 ][ MiCol ] > 0 ) ctx += 1
/// if ( AvailL && PaletteSizes[ 0 ][ MiRow ][ MiCol - 1 ] > 0 ) ctx += 1
/// ```
///
/// The caller passes whether the above / left neighbour (when available)
/// carries a non-empty Y palette. Returns a value in
/// `0..PALETTE_Y_MODE_CONTEXTS`.
pub fn palette_y_mode_ctx(above_has_palette: bool, left_has_palette: bool) -> usize {
above_has_palette as usize + left_has_palette as usize
}
/// §8.3.2 `has_palette_uv` context: `ctx = (PaletteSizeY > 0) ? 1 : 0`.
/// Returns a value in `0..PALETTE_UV_MODE_CONTEXTS`.
pub fn palette_uv_mode_ctx(palette_size_y: usize) -> usize {
(palette_size_y > 0) as usize
}
/// §8.3.2 `palette_color_idx_y` / `palette_color_idx_uv` context:
/// `ctx = Palette_Color_Context[ ColorContextHash ]`.
///
/// The §5.11.50 colour-context derivation only ever produces a
/// `ColorContextHash` for which [`PALETTE_COLOR_CONTEXT`] holds a
/// non-negative entry; this helper returns `None` for the spec's `-1`
/// sentinels (which "will never be accessed") and for an out-of-range
/// hash. On the valid path the result is in `0..PALETTE_COLOR_CONTEXTS`.
pub fn palette_color_ctx(color_context_hash: usize) -> Option<usize> {
let mapped = *PALETTE_COLOR_CONTEXT.get(color_context_hash)?;
if mapped < 0 {
None
} else {
Some(mapped as usize)
}
}
/// §5.11.50 `get_palette_color_context` output: the two pieces of state
/// the spec derivation writes — the partial-selection-sort permutation
/// of the palette indices (`ColorOrder[ PALETTE_COLORS ]`, used by the
/// caller to remap `palette_color_idx_*` after the cdf decode) and the
/// `ColorContextHash` (input to [`palette_color_ctx`]).
///
/// `color_order` is `0..PALETTE_COLORS` with the top
/// [`PALETTE_NUM_NEIGHBORS`] slots in descending-score order followed by
/// the remaining indices in their original ascending order. Only the
/// first `n` slots are addressable from the cdf decode (the
/// `palette_size_*` cdf rows have width `n`); the trailing slots are
/// preserved as the spec leaves them.
///
/// `color_context_hash` is in `0..=PALETTE_MAX_COLOR_CONTEXT_HASH` and
/// always indexes a `Some(_)` entry of [`PALETTE_COLOR_CONTEXT`] when
/// fed back through [`palette_color_ctx`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PaletteColorContext {
/// `ColorOrder[ PALETTE_COLORS ]` — the permutation of palette
/// indices the §5.11.50 partial sort produces.
pub color_order: [u8; PALETTE_COLORS],
/// `ColorContextHash` — the §5.11.50 weighted-score sum. Indexes
/// [`PALETTE_COLOR_CONTEXT`] via [`palette_color_ctx`].
pub color_context_hash: usize,
}
/// §5.11.50 `get_palette_color_context` core — derives the
/// [`PaletteColorContext`] from the three palette-neighbour indices that
/// the spec function reads out of `colorMap` (left = `colorMap[r][c-1]`,
/// above-left = `colorMap[r-1][c-1]`, above = `colorMap[r-1][c]`). Pass
/// `None` for any neighbour that the §5.11.50 `c > 0` / `r > 0` guards
/// reject as out-of-block.
///
/// `n` is the palette size (the `palette_size_y` / `palette_size_uv`
/// the caller decoded via the size cdf, in `2..=PALETTE_COLORS`). Out
/// of range yields `None`; the §5.11.50 partial selection sort uses
/// `j < n` for its inner-loop upper bound, so the size must be in
/// range for the derivation to be well-defined.
///
/// The neighbour indices must themselves be `< PALETTE_COLORS` (palette
/// indices are coded by the §5.11.49 `palette_color_idx_*` cdfs whose
/// widths are at most `PALETTE_COLORS`); a higher value would address
/// `scores[]` out of range so this helper rejects it with `None`.
pub fn palette_color_context_from_neighbors(
left: Option<u8>,
above_left: Option<u8>,
above: Option<u8>,
n: usize,
) -> Option<PaletteColorContext> {
if !(2..=PALETTE_COLORS).contains(&n) {
return None;
}
// §5.11.50: scores[i] = 0; ColorOrder[i] = i for i in 0..PALETTE_COLORS.
let mut scores = [0u32; PALETTE_COLORS];
let mut color_order = [0u8; PALETTE_COLORS];
for (i, slot) in color_order.iter_mut().enumerate() {
*slot = i as u8;
}
// §5.11.50 neighbour accumulations: the left and above neighbours
// contribute +2, the above-left neighbour contributes +1.
let mut add = |neighbor: Option<u8>, weight: u32| -> Option<()> {
if let Some(idx) = neighbor {
let idx = idx as usize;
if idx >= PALETTE_COLORS {
return None;
}
scores[idx] += weight;
}
Some(())
};
add(left, 2)?;
add(above_left, 1)?;
add(above, 2)?;
// §5.11.50 partial selection sort: for the first PALETTE_NUM_NEIGHBORS
// slots, find the maximum-scoring entry in scores[i + 1 .. n] and
// swap it to the front, shifting the intervening entries right (so
// the relative order of the runners-up is preserved).
for i in 0..PALETTE_NUM_NEIGHBORS {
let mut max_score = scores[i];
let mut max_idx = i;
for (j, &score) in scores.iter().enumerate().take(n).skip(i + 1) {
if score > max_score {
max_score = score;
max_idx = j;
}
}
if max_idx != i {
let max_color_order = color_order[max_idx];
for k in (i + 1..=max_idx).rev() {
scores[k] = scores[k - 1];
color_order[k] = color_order[k - 1];
}
scores[i] = max_score;
color_order[i] = max_color_order;
}
}
// §5.11.50: ColorContextHash = sum_{i=0..PALETTE_NUM_NEIGHBORS}
// scores[i] * Palette_Color_Hash_Multipliers[i].
let mut color_context_hash: u32 = 0;
for i in 0..PALETTE_NUM_NEIGHBORS {
color_context_hash += scores[i] * PALETTE_COLOR_HASH_MULTIPLIERS[i];
}
Some(PaletteColorContext {
color_order,
color_context_hash: color_context_hash as usize,
})
}
/// §5.11.50 `get_palette_color_context( colorMap, r, c, n )` — the
/// spec-faithful 2-D entry point. `color_map` is a row-major slice of
/// palette indices with `stride` entries per row; `(r, c)` is the
/// current position in the diagonal walk (§5.11.49 reads
/// `palette_color_idx_*` once per `(r, c)` along the anti-diagonal).
///
/// The §5.11.50 boundary guards (`r > 0`, `c > 0`) decide which
/// neighbours are read; this helper applies them and then defers to
/// [`palette_color_context_from_neighbors`] for the scoring.
///
/// Returns `None` if `n` is out of `2..=PALETTE_COLORS`, if `(r, c)`
/// (or any read neighbour) falls outside `stride` × ⌈len/stride⌉, or
/// if any read neighbour holds a value `>= PALETTE_COLORS`.
pub fn get_palette_color_context(
color_map: &[u8],
stride: usize,
r: usize,
c: usize,
n: usize,
) -> Option<PaletteColorContext> {
if stride == 0 {
return None;
}
let at = |row: usize, col: usize| -> Option<u8> {
if col >= stride {
return None;
}
color_map.get(row * stride + col).copied()
};
// The center position must itself be in-bounds — the §5.11.49 walk
// would not have called this helper otherwise.
at(r, c)?;
let left = if c > 0 { Some(at(r, c - 1)?) } else { None };
let above_left = if r > 0 && c > 0 {
Some(at(r - 1, c - 1)?)
} else {
None
};
let above = if r > 0 { Some(at(r - 1, c)?) } else { None };
palette_color_context_from_neighbors(left, above_left, above, n)
}
/// Selects the §5.11.49 plane the walker is running on: chooses
/// between [`TileCdfContext::palette_y_color_cdf`] (Y plane,
/// `palette_color_idx_y`) and [`TileCdfContext::palette_uv_color_cdf`]
/// (UV plane, `palette_color_idx_uv`). The two paths differ only in
/// which cdf-family they read from; the diagonal walk and the
/// border-fill are identical per §5.11.49.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PalettePlane {
/// `ColorMapY[ ][ ]` — `palette_color_idx_y` against
/// `TilePaletteSize{n}YColorCdf`.
Y,
/// `ColorMapUV[ ][ ]` — `palette_color_idx_uv` against
/// `TilePaletteSize{n}UVColorCdf`.
Uv,
}
/// §5.11.49 `palette_tokens( )` per-plane walker — decodes one plane's
/// `ColorMap{Y,UV}` from a [`SymbolDecoder`] and an initialised
/// [`TileCdfContext`].
///
/// The spec function decodes both planes back-to-back in a single
/// `palette_tokens( )` call. This helper covers exactly one of those
/// passes; the caller invokes it once for `Y` (when `PaletteSizeY != 0`)
/// and again for `Uv` (when `PaletteSizeUV != 0`) with the
/// chroma-subsampled dimensions per the §5.11.49 `blockHeight =
/// blockHeight >> subsampling_y` adjustments. Both calls are spec-faithful
/// in isolation — the spec performs the same diagonal walk + border-fill
/// for each plane and never mixes Y and UV state.
///
/// Parameters:
///
/// * `dec` — the §8.2 arithmetic decoder, positioned just after the
/// `NS(PaletteSize{Y,UV})` call that produced `color_index_map_{y,uv}`.
/// * `tile_ctx` — the §8.3.1 working CDF context; the walker borrows
/// `palette_{y,uv}_color_cdf` once per decoded sample (the row is
/// adapted in place per §8.3 unless the §5.9.2 `disable_cdf_update`
/// flag was set at `init_symbol`).
/// * `plane` — see [`PalettePlane`].
/// * `palette_size` — `PaletteSize{Y,UV}` in `2..=PALETTE_COLORS`.
/// * `block_width`, `block_height` — the §5.11.49 `blockWidth` and
/// `blockHeight` (already subsampled + `<4` corrected by the caller
/// for the UV path).
/// * `onscreen_width`, `onscreen_height` — the §5.11.49 `onscreenWidth`
/// and `onscreenHeight` (same caller-side adjustments).
/// * `color_index_map` — the `NS(PaletteSize)` byte the caller read for
/// the top-left sample of the plane. Becomes `ColorMap{Y,UV}[0][0]`.
/// * `color_map` — a row-major output buffer of length at least
/// `block_height * stride`. The walker writes the §5.11.49 diagonal
/// and the border-fill into `color_map[row * stride + col]`.
/// * `stride` — the row stride of `color_map`. Must be `>= block_width`.
///
/// Returns `Ok(())` on success. The §5.11.49 decode produces one
/// `S(palette_color_idx_*)` symbol per anti-diagonal position; the
/// `read_symbol` may bubble [`Error::UnexpectedEnd`] up if the
/// bitstream runs short (this is the §8.2 underflow path, propagated
/// verbatim).
///
/// Errors:
///
/// * [`Error::InvalidPaletteWalkArgs`] — caller bug. Returned when any
/// of: `palette_size` is outside `2..=PALETTE_COLORS`; `stride <
/// block_width`; `color_map.len() < block_height * stride`;
/// `onscreen_width == 0` or `onscreen_height == 0`; `onscreen_width
/// greater than `block_width` or `onscreen_height` greater than
/// `block_height`; `color_index_map >= palette_size`; or a
/// partial-sort run reads a neighbour value `>= PALETTE_COLORS`
/// (cannot happen if every prior write came from this walker, which
/// always emits values `< palette_size <= PALETTE_COLORS`, so this
/// is a buffer-shared-with-a-bug signal).
/// * [`Error::PaletteColorContextUnmapped`] — the §5.11.50
/// `ColorContextHash` landed on an unmapped entry of
/// `Palette_Color_Context[]` (hash 0, 1, 3, or 4). The spec sweep
/// over every realisable neighbour combination shows these hashes
/// are unreachable, so this is bitstream conformance — surface it
/// rather than silently feeding a `-1` context to the cdf selector.
#[allow(clippy::too_many_arguments)]
pub fn palette_tokens_plane(
dec: &mut crate::symbol_decoder::SymbolDecoder<'_>,
tile_ctx: &mut TileCdfContext,
plane: PalettePlane,
palette_size: usize,
block_width: usize,
block_height: usize,
onscreen_width: usize,
onscreen_height: usize,
color_index_map: u8,
color_map: &mut [u8],
stride: usize,
) -> Result<(), crate::Error> {
// Caller-bug guards. Each maps to InvalidPaletteWalkArgs; none can
// be triggered by a conformant §5.11.49 caller.
if !(2..=PALETTE_COLORS).contains(&palette_size) {
return Err(crate::Error::InvalidPaletteWalkArgs);
}
if onscreen_width == 0 || onscreen_height == 0 {
return Err(crate::Error::InvalidPaletteWalkArgs);
}
if onscreen_width > block_width || onscreen_height > block_height {
return Err(crate::Error::InvalidPaletteWalkArgs);
}
if stride < block_width {
return Err(crate::Error::InvalidPaletteWalkArgs);
}
if color_map.len() < block_height.saturating_mul(stride) {
return Err(crate::Error::InvalidPaletteWalkArgs);
}
if (color_index_map as usize) >= palette_size {
return Err(crate::Error::InvalidPaletteWalkArgs);
}
// §5.11.49: ColorMap{Y,UV}[0][0] = color_index_map_{y,uv}.
color_map[0] = color_index_map;
// §5.11.49 diagonal walk over the onscreen rectangle. Each (r, c) =
// (i - j, j) sample is decoded against the §5.11.50 colour context.
for i in 1..(onscreen_height + onscreen_width - 1) {
// j descends from min(i, onscreenWidth - 1) to
// max(0, i - onscreenHeight + 1) — the spec's anti-diagonal
// ordering, which guarantees the three neighbours
// (left, above-left, above) are already written.
let j_hi = core::cmp::min(i, onscreen_width - 1);
let j_lo = i.saturating_sub(onscreen_height - 1);
let mut j = j_hi;
loop {
let r = i - j;
let c = j;
// §5.11.50 colour context — invariant: every neighbour
// already in color_map is < palette_size <= PALETTE_COLORS
// (we just wrote it as an indexed ColorOrder slot whose
// codomain is 0..palette_size).
let pcc = get_palette_color_context(color_map, stride, r, c, palette_size)
.ok_or(crate::Error::InvalidPaletteWalkArgs)?;
let ctx = palette_color_ctx(pcc.color_context_hash)
.ok_or(crate::Error::PaletteColorContextUnmapped)?;
let cdf = match plane {
PalettePlane::Y => tile_ctx.palette_y_color_cdf(palette_size, ctx),
PalettePlane::Uv => tile_ctx.palette_uv_color_cdf(palette_size, ctx),
}
.ok_or(crate::Error::InvalidPaletteWalkArgs)?;
let idx = dec.read_symbol(cdf)? as usize;
// §5.11.49: ColorMap[r][c] = ColorOrder[ palette_color_idx ].
// The cdf has `palette_size` symbols, so `idx < palette_size`
// <= PALETTE_COLORS and the indexing is in-bounds.
color_map[r * stride + c] = pcc.color_order[idx];
if j == j_lo {
break;
}
j -= 1;
}
}
// §5.11.49 horizontal border-fill: for each on-screen row, extend
// the right edge by replicating column `onscreenWidth - 1`.
for r in 0..onscreen_height {
let last = color_map[r * stride + (onscreen_width - 1)];
for c in onscreen_width..block_width {
color_map[r * stride + c] = last;
}
}
// §5.11.49 vertical border-fill: for each row below the on-screen
// band, replicate row `onscreenHeight - 1` across the full block
// width.
if onscreen_height < block_height {
// Snapshot row (onscreenHeight - 1) into a small temp so the
// copy doesn't alias the destination through `color_map[..]`.
let src_row_start = (onscreen_height - 1) * stride;
let mut row_snapshot = [0u8; 64]; // block widths are <= 64
let row_w = block_width;
debug_assert!(row_w <= row_snapshot.len(), "block width fits in 64");
row_snapshot[..row_w].copy_from_slice(&color_map[src_row_start..src_row_start + row_w]);
for r in onscreen_height..block_height {
let dst = r * stride;
color_map[dst..dst + row_w].copy_from_slice(&row_snapshot[..row_w]);
}
}
Ok(())
}
/// §7.11.4 palette prediction process (av1-spec p.286) — the per-
/// transform-block sample-generation leaf for palette-coded intra
/// blocks. Writes `pred[ i ][ j ] = palette[ map[ y*4 + i ][ x*4 + j ] ]`
/// for `i = 0..h-1`, `j = 0..w-1`, where `w = Tx_Width[ txSz ]` and
/// `h = Tx_Height[ txSz ]`.
///
/// This is the consumer of the §5.11.49 `ColorMap{Y,UV}` index map
/// surfaced on [`DecodedBlock::color_map_y`] / [`DecodedBlock::color_map_uv`]
/// (r325) together with the §5.11.46 `PaletteColors{Y,U,V}[ ]` colour
/// table: each map entry is a palette index in `0..palette.len()` and
/// each output sample is the corresponding palette colour. The spec
/// invokes this process once per transform block of a palette block,
/// passing the transform block's `(x, y)` position in 4×4 units
/// relative to the top-left of the palette block (so the map window
/// for this transform block starts at row `y*4`, column `x*4`).
///
/// ## Arguments
///
/// * `txsz` — the transform-block size ordinal (`0..TX_SIZES_ALL`).
/// Selects `w = Tx_Width[ txSz ]`, `h = Tx_Height[ txSz ]`.
/// * `x` / `y` — the transform block's location in 4×4 units relative
/// to the top-left of the palette block (the spec's `x` / `y`). The
/// map window begins at `(y*4, x*4)`.
/// * `map` — the full §5.11.49 colour-index map for the plane (row-
/// major, `block_h × stride` with `stride >= block_w`); for the
/// crate's [`DecodedPaletteMap`] pass [`DecodedPaletteMap::data`] and
/// its [`DecodedPaletteMap::stride`].
/// * `stride` — the row stride of `map` (the `DecodedPaletteMap`
/// `stride`, equal to its `block_w`).
/// * `palette` — the §5.11.46 plane palette colour table
/// (`palette_colors_{y,u,v}[ 0..PaletteSize ]`). Each map index must
/// be `< palette.len()`.
/// * `pred` — output buffer, row-major; length must be `>= h * w`.
/// `pred[ i*w + j ]` receives the predicted sample for `(i, j)`.
///
/// ## Returns
///
/// `Ok(())` on success.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments: `txsz >= TX_SIZES_ALL`, `w > 64`, `h > 64`,
/// `pred.len() < h * w`, an empty `palette`, the requested map window
/// (`(y*4 + h - 1)` rows by `(x*4 + w - 1)` columns at the given
/// `stride`) exceeding `map.len()`, or any map index `>= palette.len()`.
pub fn predict_palette(
txsz: usize,
x: usize,
y: usize,
map: &[u8],
stride: usize,
palette: &[u16],
pred: &mut [u16],
) -> Result<(), crate::Error> {
if txsz >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let w = TX_WIDTH[txsz];
let h = TX_HEIGHT[txsz];
if w > 64 || h > 64 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if palette.is_empty() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if pred.len() < h.saturating_mul(w) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if stride < w {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let base_row = y.saturating_mul(4);
let base_col = x.saturating_mul(4);
// The map window the spec reads is rows `base_row..base_row+h`,
// columns `base_col..base_col+w`. The deepest map element accessed
// is `(base_row + h - 1) * stride + (base_col + w - 1)`.
let last_row = base_row + h - 1;
let last_col = base_col + w - 1;
let max_idx = last_row.saturating_mul(stride).saturating_add(last_col);
if max_idx >= map.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §7.11.4: CurrFrame[ plane ][ startY + i ][ startX + j ] =
// palette[ map[ y*4 + i ][ x*4 + j ] ].
for i in 0..h {
let map_row = (base_row + i) * stride + base_col;
let pred_row = i * w;
for j in 0..w {
let idx = map[map_row + j] as usize;
if idx >= palette.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
pred[pred_row + j] = palette[idx];
}
}
Ok(())
}
/// §8.3.2 `cfl_alpha_u` context: `ctx = (signU - 1) * 3 + signV`, which
/// the spec notes equals `cfl_alpha_signs - 2`. `sign_u` / `sign_v` are
/// the §5.11.45 joint-sign components (`CFL_SIGN_ZERO == 0`,
/// `CFL_SIGN_NEG == 1`, `CFL_SIGN_POS == 2`). Only reached when
/// `sign_u != CFL_SIGN_ZERO`, so the result is in `0..CFL_ALPHA_CONTEXTS`.
pub fn cfl_alpha_u_ctx(sign_u: usize, sign_v: usize) -> usize {
(sign_u - 1) * 3 + sign_v
}
/// §8.3.2 `cfl_alpha_v` context: `ctx = (signV - 1) * 3 + signU`. Only
/// reached when `sign_v != CFL_SIGN_ZERO`, so the result is in
/// `0..CFL_ALPHA_CONTEXTS`.
pub fn cfl_alpha_v_ctx(sign_u: usize, sign_v: usize) -> usize {
(sign_v - 1) * 3 + sign_u
}
// ---------------------------------------------------------------------
// Round 20 — transform-size §8.3.2 context helpers. Each computes a
// `ctx` from scalar inputs the caller derives from the §5.11.15 /
// §5.11.16 syntax + the local neighbour state. The full neighbour
// `get_above_tx_width` / `get_left_tx_height` walks live in the tile
// walk (none of those tables are yet tracked here); the helpers below
// take the already-computed `aboveW` / `leftH` width-and-height
// neighbours and the `maxTxWidth` / `maxTxHeight` from the spec's
// `Tx_Width` / `Tx_Height` tables.
// ---------------------------------------------------------------------
/// §8.3.2 `tx_depth` context formula:
///
/// ```text
/// ctx = (aboveW >= maxTxWidth) + (leftH >= maxTxHeight)
/// ```
///
/// `above_w` and `left_h` are the §8.3.2-defined neighbour widths /
/// heights (`0` when the neighbour is unavailable; the
/// `Block_Width[ MiSizes[..] ]` / `get_above_tx_width(..)` /
/// `get_left_tx_height(..)` ladders for the present-neighbour case
/// happen in the tile walk). `max_tx_width` / `max_tx_height` are the
/// `Tx_Width[ maxRectTxSize ]` / `Tx_Height[ maxRectTxSize ]` values.
/// Result is in `0..TX_SIZE_CONTEXTS`.
pub fn tx_depth_ctx(above_w: u32, left_h: u32, max_tx_width: u32, max_tx_height: u32) -> usize {
(above_w >= max_tx_width) as usize + (left_h >= max_tx_height) as usize
}
/// §8.3.2 `txfm_split` context formula:
///
/// ```text
/// above = get_above_tx_width( row, col ) < Tx_Width[ txSz ]
/// left = get_left_tx_height( row, col ) < Tx_Height[ txSz ]
/// size = Min( 64, Max( Block_Width[ MiSize ], Block_Height[ MiSize ] ) )
/// maxTxSz = find_tx_size( size, size )
/// txSzSqrUp = Tx_Size_Sqr_Up[ txSz ]
/// ctx = (txSzSqrUp != maxTxSz) * 3
/// + (TX_SIZES - 1 - maxTxSz) * 6
/// + above + left
/// ```
///
/// `above` / `left` are the two booleans the spec computes from
/// `get_above_tx_width` / `get_left_tx_height` against `Tx_Width[ txSz ]`
/// / `Tx_Height[ txSz ]`; `tx_sz_sqr_up` is `Tx_Size_Sqr_Up[ txSz ]` and
/// `max_tx_sz` is `find_tx_size( size, size )` for `size = Min( 64,
/// Max( Block_Width, Block_Height ) )` — both `TX_4X4..TX_64X64`
/// indices.
///
/// Returns `None` if the computed ctx would land outside
/// `0..TXFM_PARTITION_CONTEXTS` (a caller-input bug; the spec's
/// reachable combinations stay in range).
pub fn txfm_split_ctx(above: bool, left: bool, tx_sz_sqr_up: u32, max_tx_sz: u32) -> Option<usize> {
if max_tx_sz as usize >= TX_SIZES {
return None;
}
let split = (tx_sz_sqr_up != max_tx_sz) as usize;
let size_term = (TX_SIZES - 1 - max_tx_sz as usize).checked_mul(6)?;
let ctx = split * 3 + size_term + (above as usize) + (left as usize);
if ctx < TXFM_PARTITION_CONTEXTS {
Some(ctx)
} else {
None
}
}
/// §5.11.36 `find_tx_size( w, h )` (av1-spec p.74) — the linear scan
/// over `TX_SIZES_ALL` returning the first ordinal whose
/// `(Tx_Width[txSz], Tx_Height[txSz])` pair equals `(w, h)`.
///
/// Spec body:
///
/// ```text
/// find_tx_size( w, h ) {
/// for ( txSz = 0; txSz < TX_SIZES_ALL; txSz++ )
/// if ( Tx_Width[ txSz ] == w && Tx_Height[ txSz ] == h )
/// break
/// return txSz
/// }
/// ```
///
/// The §5.11.36 spec note guarantees a hit for every `(w, h)` pair the
/// callers produce (the §8.3.2 `txfm_split` ctx site uses
/// `size = Min( 64, Max( Block_Width, Block_Height ) )` for both axes,
/// always landing on one of the square sizes `TX_4X4..TX_64X64`).
/// Returns `None` if the linear scan finds no match (a caller bug —
/// `(w, h)` outside the §6.10.16 table).
#[must_use]
pub fn find_tx_size(w: usize, h: usize) -> Option<usize> {
(0..TX_SIZES_ALL).find(|&i| TX_WIDTH[i] == w && TX_HEIGHT[i] == h)
}
// ---------------------------------------------------------------------
// Round 21 — inter-frame transform-type §8.3.2 helpers. Mirrors the
// `is_inter == 1` branch of §5.11.48 `get_tx_set()`.
// ---------------------------------------------------------------------
/// §5.11.48 `get_tx_set()` (the `is_inter == 1` branch). Maps the
/// `txSzSqr` / `txSzSqrUp` pair (and the frame's `reduced_tx_set`) to
/// the inter transform-set index `set ∈ { TX_SET_DCTONLY,
/// TX_SET_INTER_1, TX_SET_INTER_2, TX_SET_INTER_3 }`.
///
/// ```text
/// if ( txSzSqrUp > TX_32X32 ) return TX_SET_DCTONLY
/// if ( reduced_tx_set || txSzSqrUp == TX_32X32 ) return TX_SET_INTER_3
/// else if ( txSzSqr == TX_16X16 ) return TX_SET_INTER_2
/// return TX_SET_INTER_1
/// ```
///
/// `tx_sz_sqr` and `tx_sz_sqr_up` are `Tx_Size_Sqr[ txSz ]` and
/// `Tx_Size_Sqr_Up[ txSz ]` (`TX_4X4 = 0..TX_64X64 = 4`). The intra
/// branch is covered by the companion [`intra_tx_type_set`].
pub fn inter_tx_type_set(tx_sz_sqr: u32, tx_sz_sqr_up: u32, reduced_tx_set: bool) -> u32 {
// §3 `TX_32X32 = 3` per the spec's per-`TX_*` constant table.
const TX_16X16: u32 = 2;
const TX_32X32: u32 = 3;
if tx_sz_sqr_up > TX_32X32 {
TX_SET_DCTONLY
} else if reduced_tx_set || tx_sz_sqr_up == TX_32X32 {
TX_SET_INTER_3
} else if tx_sz_sqr == TX_16X16 {
TX_SET_INTER_2
} else {
TX_SET_INTER_1
}
}
// ---------------------------------------------------------------------
// Round 137 — intra-frame transform-type §8.3.2 helpers. Mirrors the
// `is_inter == 0` branch of §5.11.48 `get_tx_set()` and the §8.3.2
// `intraDir` derivation (`YMode` or
// `Filter_Intra_Mode_To_Intra_Dir[ filter_intra_mode ]`).
// ---------------------------------------------------------------------
/// §5.11.48 `get_tx_set()` (the `is_inter == 0` branch). Maps the
/// `txSzSqr` / `txSzSqrUp` pair (and the frame's `reduced_tx_set`) to
/// the intra transform-set index `set ∈ { TX_SET_DCTONLY,
/// TX_SET_INTRA_1, TX_SET_INTRA_2 }`.
///
/// ```text
/// if ( txSzSqrUp > TX_32X32 ) return TX_SET_DCTONLY
/// if ( txSzSqrUp == TX_32X32 ) return TX_SET_DCTONLY
/// else if ( reduced_tx_set ) return TX_SET_INTRA_2
/// else if ( txSzSqr == TX_16X16 ) return TX_SET_INTRA_2
/// return TX_SET_INTRA_1
/// ```
///
/// `tx_sz_sqr` and `tx_sz_sqr_up` are `Tx_Size_Sqr[ txSz ]` and
/// `Tx_Size_Sqr_Up[ txSz ]` (`TX_4X4 = 0..TX_64X64 = 4`).
pub fn intra_tx_type_set(tx_sz_sqr: u32, tx_sz_sqr_up: u32, reduced_tx_set: bool) -> u32 {
// §3 `TX_32X32 = 3` per the spec's per-`TX_*` constant table.
const TX_16X16: u32 = 2;
const TX_32X32: u32 = 3;
// The two §5.11.48 `return TX_SET_DCTONLY` gates merge: the outer
// pre-`is_inter` guard fires for `txSzSqrUp > TX_32X32`, and the
// first intra-branch line fires for `txSzSqrUp == TX_32X32`.
if tx_sz_sqr_up >= TX_32X32 {
TX_SET_DCTONLY
} else if reduced_tx_set || tx_sz_sqr == TX_16X16 {
TX_SET_INTRA_2
} else {
TX_SET_INTRA_1
}
}
/// §8.3.2 `intra_tx_type` — `intraDir` derivation. Mirrors the spec:
///
/// ```text
/// if ( use_filter_intra == 1 )
/// intraDir = Filter_Intra_Mode_To_Intra_Dir[ filter_intra_mode ]
/// else
/// intraDir = YMode
/// ```
///
/// `y_mode` is the current block's `YMode` (`0..INTRA_MODES`);
/// `filter_intra_mode` is the §5.11.x `filter_intra_mode` syntax value
/// (`0..INTRA_FILTER_MODES`) — supply `0` and `use_filter_intra = false`
/// for blocks that didn't read a filter-intra mode.
///
/// Returns `None` if any input is out of the spec-bounded range (a
/// caller bug — `y_mode < INTRA_MODES` and
/// `filter_intra_mode < INTRA_FILTER_MODES` per the §3 enumerations).
pub fn intra_dir(use_filter_intra: bool, y_mode: usize, filter_intra_mode: usize) -> Option<usize> {
if use_filter_intra {
FILTER_INTRA_MODE_TO_INTRA_DIR
.get(filter_intra_mode)
.copied()
} else if y_mode < INTRA_MODES {
Some(y_mode)
} else {
None
}
}
// ---------------------------------------------------------------------
// Round 22 — inter-frame interpolation-filter §8.3.2 helper. The
// §8.3.2 ctx formula folds the §5.11.x `dir` / `RefFrame[1]` scope
// inputs with the two `(above|left)Type` neighbour-filter inputs into
// a single `0..INTERP_FILTER_CONTEXTS` index.
// ---------------------------------------------------------------------
/// Sentinel returned by the §8.3.2 `aboveType` / `leftType` neighbour
/// reads when the neighbour is unavailable or carries a different
/// reference frame (so its `InterpFilters` entry doesn't count as a
/// matching neighbour). Per §8.3.2 the spec's literal initialiser
/// `aboveType = 3; leftType = 3` matches `INTERP_FILTERS`, i.e. one
/// past the highest reachable filter index.
pub const INTERP_FILTER_NONE: usize = INTERP_FILTERS;
/// §8.3.2 `interp_filter` context. Mirrors the spec block:
///
/// ```text
/// ctx = ((dir & 1) * 2 + (RefFrame[1] > INTRA_FRAME)) * 4
/// leftType = 3
/// aboveType = 3
///
/// if (AvailL) {
/// if (RefFrames[ MiRow ][ MiCol - 1 ][ 0 ] == RefFrame[ 0 ] ||
/// RefFrames[ MiRow ][ MiCol - 1 ][ 1 ] == RefFrame[ 0 ])
/// leftType = InterpFilters[ MiRow ][ MiCol - 1 ][ dir ]
/// }
/// if (AvailU) {
/// if (RefFrames[ MiRow - 1 ][ MiCol ][ 0 ] == RefFrame[ 0 ] ||
/// RefFrames[ MiRow - 1 ][ MiCol ][ 1 ] == RefFrame[ 0 ])
/// aboveType = InterpFilters[ MiRow - 1 ][ MiCol ][ dir ]
/// }
///
/// if (leftType == aboveType) ctx += leftType
/// else if (leftType == 3) ctx += aboveType
/// else if (aboveType == 3) ctx += leftType
/// else ctx += 3
/// ```
///
/// The caller supplies the already-resolved `above_type` / `left_type`
/// in `0..INTERP_FILTERS` for a matching neighbour, or
/// [`INTERP_FILTER_NONE`] (== `INTERP_FILTERS`, the spec's literal `3`)
/// for an unavailable / mismatched neighbour — the §5.11.x neighbour
/// walk owns that resolution. `dir` is 0 (horizontal) or 1 (vertical),
/// and `is_compound` is the §5.11.27 `isCompound = RefFrame[1] >
/// INTRA_FRAME` derivation.
///
/// Returns `Some(ctx)` in `0..INTERP_FILTER_CONTEXTS`, or `None` if
/// any input is out of the spec-bounded range (a caller bug —
/// `above_type` / `left_type` strictly bounded to `0..=INTERP_FILTERS`,
/// `dir <= 1`).
pub fn interp_filter_ctx(
above_type: usize,
left_type: usize,
dir: u32,
is_compound: bool,
) -> Option<usize> {
if dir > 1 {
return None;
}
if above_type > INTERP_FILTERS || left_type > INTERP_FILTERS {
return None;
}
let mut ctx = (((dir & 1) as usize) * 2 + (is_compound as usize)) * 4;
if left_type == above_type {
ctx += left_type;
} else if left_type == INTERP_FILTER_NONE {
ctx += above_type;
} else if above_type == INTERP_FILTER_NONE {
ctx += left_type;
} else {
ctx += INTERP_FILTERS;
}
if ctx < INTERP_FILTER_CONTEXTS {
Some(ctx)
} else {
None
}
}
// ---------------------------------------------------------------------
// Round 30 (r170) — §8.3.2 ref-frame ctx helpers consumed by the
// §5.11.25 `read_ref_frames` reader. Each helper mirrors a literal
// av1-spec §8.3.2 block (`comp_mode` p.366, `comp_ref` family p.366-
// 367, `single_ref_*` p.368, `comp_ref_type` p.382, `uni_comp_ref*`
// p.383) and consumes the §5.11.18 prologue's `Left*` / `Above*`
// derivations directly. Free functions so direct callers can verify
// the spec correspondence against the §3 / §6.10.1 ref-frame ordinals
// without instantiating a walker.
// ---------------------------------------------------------------------
/// §8.3.2 `check_backward(refFrame)` predicate — true ⇔ `refFrame` is
/// in the backward-direction group (`BWDREF_FRAME = 5..=ALTREF_FRAME =
/// 7`). The spec literal `((refFrame >= BWDREF_FRAME) && (refFrame <=
/// ALTREF_FRAME))` (av1-spec p.366).
#[inline]
pub fn check_backward(ref_frame: i32) -> bool {
(5..=7).contains(&ref_frame)
}
/// §8.3.2 `is_samedir_ref_pair(ref0, ref1)` predicate — true ⇔ both
/// ref-frame indices lie in the same direction group (both forward or
/// both backward). The spec literal `((ref0 >= BWDREF_FRAME) == (ref1
/// >= BWDREF_FRAME))` (av1-spec p.383).
#[inline]
pub fn is_samedir_ref_pair(ref0: i32, ref1: i32) -> bool {
(ref0 >= 5) == (ref1 >= 5)
}
/// §8.3.2 `count_refs(frameType)` helper consumed by the `comp_ref`
/// family + `single_ref_p1` ctx derivations. Counts how many of the
/// four `Above/LeftRefFrame[0..2]` slots equal `frame_type`, gated by
/// `avail_u` / `avail_l`.
///
/// Spec body (av1-spec p.366):
///
/// ```text
/// count_refs(frameType) {
/// c = 0
/// if ( AvailU ) {
/// if ( AboveRefFrame[ 0 ] == frameType ) c++
/// if ( AboveRefFrame[ 1 ] == frameType ) c++
/// }
/// if ( AvailL ) {
/// if ( LeftRefFrame[ 0 ] == frameType ) c++
/// if ( LeftRefFrame[ 1 ] == frameType ) c++
/// }
/// return c
/// }
/// ```
#[inline]
pub fn count_refs(
frame_type: i32,
avail_u: bool,
above_ref_frame: [i32; 2],
avail_l: bool,
left_ref_frame: [i32; 2],
) -> u32 {
let mut c = 0u32;
if avail_u {
if above_ref_frame[0] == frame_type {
c += 1;
}
if above_ref_frame[1] == frame_type {
c += 1;
}
}
if avail_l {
if left_ref_frame[0] == frame_type {
c += 1;
}
if left_ref_frame[1] == frame_type {
c += 1;
}
}
c
}
/// §8.3.2 `comp_mode` ctx (av1-spec p.366). Returns a value in
/// `0..COMP_INTER_CONTEXTS = 5`.
///
/// Spec body:
///
/// ```text
/// if ( AvailU && AvailL ) {
/// if ( AboveSingle && LeftSingle )
/// ctx = check_backward( AboveRefFrame[ 0 ] )
/// ^ check_backward( LeftRefFrame[ 0 ] )
/// else if ( AboveSingle )
/// ctx = 2 + ( check_backward( AboveRefFrame[ 0 ] ) || AboveIntra )
/// else if ( LeftSingle )
/// ctx = 2 + ( check_backward( LeftRefFrame[ 0 ] ) || LeftIntra )
/// else
/// ctx = 4
/// } else if ( AvailU ) {
/// if ( AboveSingle ) ctx = check_backward( AboveRefFrame[ 0 ] )
/// else ctx = 3
/// } else if ( AvailL ) {
/// if ( LeftSingle ) ctx = check_backward( LeftRefFrame[ 0 ] )
/// else ctx = 3
/// } else {
/// ctx = 1
/// }
/// ```
#[allow(clippy::too_many_arguments)]
pub fn comp_mode_ctx(
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_intra: bool,
left_intra: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
) -> usize {
if avail_u && avail_l {
if above_single && left_single {
(check_backward(above_ref_frame[0]) as usize)
^ (check_backward(left_ref_frame[0]) as usize)
} else if above_single {
2 + ((check_backward(above_ref_frame[0]) || above_intra) as usize)
} else if left_single {
2 + ((check_backward(left_ref_frame[0]) || left_intra) as usize)
} else {
4
}
} else if avail_u {
if above_single {
check_backward(above_ref_frame[0]) as usize
} else {
3
}
} else if avail_l {
if left_single {
check_backward(left_ref_frame[0]) as usize
} else {
3
}
} else {
1
}
}
/// §8.3.2 `comp_ref_type` ctx (av1-spec p.382). Returns a value in
/// `0..COMP_REF_TYPE_CONTEXTS = 5`.
///
/// Spec body inlined from p.382:
///
/// ```text
/// above0 = AboveRefFrame[ 0 ]; above1 = AboveRefFrame[ 1 ]
/// left0 = LeftRefFrame[ 0 ]; left1 = LeftRefFrame[ 1 ]
/// aboveCompInter = AvailU && !AboveIntra && !AboveSingle
/// leftCompInter = AvailL && !LeftIntra && !LeftSingle
/// aboveUniComp = aboveCompInter && is_samedir_ref_pair(above0, above1)
/// leftUniComp = leftCompInter && is_samedir_ref_pair(left0, left1)
///
/// if ( AvailU && !AboveIntra && AvailL && !LeftIntra ) {
/// samedir = is_samedir_ref_pair(above0, left0)
/// if ( !aboveCompInter && !leftCompInter ) ctx = 1 + 2*samedir
/// else if ( !aboveCompInter )
/// ctx = !leftUniComp ? 1 : 3 + samedir
/// else if ( !leftCompInter )
/// ctx = !aboveUniComp ? 1 : 3 + samedir
/// else {
/// if ( !aboveUniComp && !leftUniComp ) ctx = 0
/// else if ( !aboveUniComp || !leftUniComp ) ctx = 2
/// else
/// ctx = 3 + ((above0 == BWDREF_FRAME) ==
/// (left0 == BWDREF_FRAME))
/// }
/// } else if ( AvailU && AvailL ) {
/// if ( aboveCompInter ) ctx = 1 + 2 * aboveUniComp
/// else if ( leftCompInter ) ctx = 1 + 2 * leftUniComp
/// else ctx = 2
/// } else if ( aboveCompInter ) ctx = 4 * aboveUniComp
/// else if ( leftCompInter ) ctx = 4 * leftUniComp
/// else ctx = 2
/// ```
#[allow(clippy::too_many_arguments)]
pub fn comp_ref_type_ctx(
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_intra: bool,
left_intra: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
) -> usize {
let above0 = above_ref_frame[0];
let above1 = above_ref_frame[1];
let left0 = left_ref_frame[0];
let left1 = left_ref_frame[1];
let above_comp_inter = avail_u && !above_intra && !above_single;
let left_comp_inter = avail_l && !left_intra && !left_single;
let above_uni_comp = above_comp_inter && is_samedir_ref_pair(above0, above1);
let left_uni_comp = left_comp_inter && is_samedir_ref_pair(left0, left1);
if avail_u && !above_intra && avail_l && !left_intra {
let samedir = is_samedir_ref_pair(above0, left0) as usize;
if !above_comp_inter && !left_comp_inter {
1 + 2 * samedir
} else if !above_comp_inter {
if !left_uni_comp {
1
} else {
3 + samedir
}
} else if !left_comp_inter {
if !above_uni_comp {
1
} else {
3 + samedir
}
} else if !above_uni_comp && !left_uni_comp {
0
} else if !above_uni_comp || !left_uni_comp {
2
} else {
3 + ((above0 == 5) == (left0 == 5)) as usize
}
} else if avail_u && avail_l {
if above_comp_inter {
1 + 2 * (above_uni_comp as usize)
} else if left_comp_inter {
1 + 2 * (left_uni_comp as usize)
} else {
2
}
} else if above_comp_inter {
4 * (above_uni_comp as usize)
} else if left_comp_inter {
4 * (left_uni_comp as usize)
} else {
2
}
}
// ---------------------------------------------------------------------
// §8.3.2 coefficient context derivation (`get_coeff_base_ctx` /
// `get_br_ctx`). These compute the per-coefficient `ctx` index that the
// `coeff_base` / `coeff_base_eob` / `coeff_br` selectors above consume.
// They read a coefficient-magnitude array (`Quant`) plus scalar
// transform / position state supplied by the tile-content walk (which
// is implemented separately); they own the §8.3.2 neighbour scan only.
// ---------------------------------------------------------------------
/// §3 `TX_CLASS_2D` — the two-dimensional transform class returned by
/// `get_tx_class()` for transforms with non-identity behaviour on both
/// axes (the default class for `DCT_DCT` and friends).
pub const TX_CLASS_2D: usize = 0;
/// §3 `TX_CLASS_HORIZ` — the transform class for horizontal-only
/// transforms (`H_DCT` / `H_ADST` / `H_FLIPADST`).
pub const TX_CLASS_HORIZ: usize = 1;
/// §3 `TX_CLASS_VERT` — the transform class for vertical-only
/// transforms (`V_DCT` / `V_ADST` / `V_FLIPADST`).
pub const TX_CLASS_VERT: usize = 2;
/// `TX_SIZES_ALL` (§3) — the number of transform sizes including the
/// rectangular variants; the first axis of [`COEFF_BASE_CTX_OFFSET`]
/// and the index space for [`ADJUSTED_TX_SIZE`] / [`TX_WIDTH`] /
/// [`TX_HEIGHT`] / [`TX_WIDTH_LOG2`].
pub const TX_SIZES_ALL: usize = 19;
/// `Adjusted_Tx_Size[ TX_SIZES_ALL ]` (§ Additional tables). Maps each
/// transform size to the size whose dimensions cap the coefficient
/// context scan at 32×32 — `get_coeff_base_ctx()` / `get_br_ctx()` use
/// `bwl`, `width`, `height` derived from this adjusted size while
/// `Coeff_Base_Ctx_Offset` is still indexed by the *original* size.
/// Entries are themselves `TX_SIZES_ALL` indices.
pub const ADJUSTED_TX_SIZE: [usize; TX_SIZES_ALL] = [
0, // TX_4X4 -> TX_4X4
1, // TX_8X8 -> TX_8X8
2, // TX_16X16 -> TX_16X16
3, // TX_32X32 -> TX_32X32
3, // TX_64X64 -> TX_32X32
5, // TX_4X8 -> TX_4X8
6, // TX_8X4 -> TX_8X4
7, // TX_8X16 -> TX_8X16
8, // TX_16X8 -> TX_16X8
9, // TX_16X32 -> TX_16X32
10, // TX_32X16 -> TX_32X16
3, // TX_32X64 -> TX_32X32
3, // TX_64X32 -> TX_32X32
13, // TX_4X16 -> TX_4X16
14, // TX_16X4 -> TX_16X4
15, // TX_8X32 -> TX_8X32
16, // TX_32X8 -> TX_32X8
9, // TX_16X64 -> TX_16X32
10, // TX_64X16 -> TX_32X16
];
/// `Tx_Width[ TX_SIZES_ALL ]` (§ Additional tables) — the width in
/// pixels of each transform size.
pub const TX_WIDTH: [usize; TX_SIZES_ALL] = [
4, 8, 16, 32, 64, 4, 8, 8, 16, 16, 32, 32, 64, 4, 16, 8, 32, 16, 64,
];
/// `Tx_Height[ TX_SIZES_ALL ]` (§ Additional tables) — the height in
/// pixels of each transform size.
pub const TX_HEIGHT: [usize; TX_SIZES_ALL] = [
4, 8, 16, 32, 64, 8, 4, 16, 8, 32, 16, 64, 32, 16, 4, 32, 8, 64, 16,
];
/// `Tx_Width_Log2[ TX_SIZES_ALL ]` (§ Additional tables) — the base-2
/// logarithm of each transform width (the `bwl` shift used to convert a
/// scan position into `(row, col)` within the coefficient array).
pub const TX_WIDTH_LOG2: [usize; TX_SIZES_ALL] =
[2, 3, 4, 5, 6, 2, 3, 3, 4, 4, 5, 5, 6, 2, 4, 3, 5, 4, 6];
/// `Tx_Size_Sqr_Up[ TX_SIZES_ALL ]` (§ Additional tables). For a
/// transform size `t` (of width `w` and height `h`), returns the square
/// tx size whose side length is `Max(w, h)`. Entries are themselves
/// `TX_SIZES_ALL` indices in the `TX_4X4..TX_64X64` range.
///
/// Used by §5.11.40 `compute_tx_type()` (the `txSzSqrUp > TX_32X32`
/// fallback to `DCT_DCT`), by §5.11.48 `get_tx_set()`, and by the
/// `txfm_split` context formula. Replaces the locally-scoped
/// `TX_32X32 = 3` numeric constants the existing
/// [`inter_tx_type_set`] / [`intra_tx_type_set`] helpers used.
pub const TX_SIZE_SQR_UP: [usize; TX_SIZES_ALL] = [
0, // TX_4X4 -> TX_4X4
1, // TX_8X8 -> TX_8X8
2, // TX_16X16 -> TX_16X16
3, // TX_32X32 -> TX_32X32
4, // TX_64X64 -> TX_64X64
1, // TX_4X8 -> TX_8X8
1, // TX_8X4 -> TX_8X8
2, // TX_8X16 -> TX_16X16
2, // TX_16X8 -> TX_16X16
3, // TX_16X32 -> TX_32X32
3, // TX_32X16 -> TX_32X32
4, // TX_32X64 -> TX_64X64
4, // TX_64X32 -> TX_64X64
2, // TX_4X16 -> TX_16X16
2, // TX_16X4 -> TX_16X16
3, // TX_8X32 -> TX_32X32
3, // TX_32X8 -> TX_32X32
4, // TX_16X64 -> TX_64X64
4, // TX_64X16 -> TX_64X64
];
/// `Sig_Ref_Diff_Offset[ 3 ][ SIG_REF_DIFF_OFFSET_NUM ][ 2 ]`
/// (§ Additional tables). Per transform class, the
/// `(rowDelta, colDelta)` offsets scanned by `get_coeff_base_ctx()` to
/// accumulate the neighbour magnitude. Indexed `[txClass][idx][0|1]`.
pub const SIG_REF_DIFF_OFFSET: [[[usize; 2]; SIG_REF_DIFF_OFFSET_NUM]; 3] = [
// TX_CLASS_2D
[[0, 1], [1, 0], [1, 1], [0, 2], [2, 0]],
// TX_CLASS_HORIZ
[[0, 1], [1, 0], [0, 2], [0, 3], [0, 4]],
// TX_CLASS_VERT
[[0, 1], [1, 0], [2, 0], [3, 0], [4, 0]],
];
/// `Mag_Ref_Offset_With_Tx_Class[ 3 ][ 3 ][ 2 ]` (§ Additional tables).
/// Per transform class, the three `(rowDelta, colDelta)` offsets scanned
/// by `get_br_ctx()` to accumulate the neighbour base-range magnitude.
/// Indexed `[txClass][idx][0|1]`.
pub const MAG_REF_OFFSET_WITH_TX_CLASS: [[[usize; 2]; 3]; 3] = [
// TX_CLASS_2D
[[0, 1], [1, 0], [1, 1]],
// TX_CLASS_HORIZ
[[0, 1], [1, 0], [0, 2]],
// TX_CLASS_VERT
[[0, 1], [1, 0], [2, 0]],
];
/// `Coeff_Base_Pos_Ctx_Offset[ 3 ]` (§8.3.2) — the offsets added to the
/// magnitude bucket in the one-dimensional (vertical / horizontal)
/// branch of `get_coeff_base_ctx()`. Indexed by `Min(idx, 2)` where
/// `idx` is the row (vertical) or column (horizontal) of the position.
pub const COEFF_BASE_POS_CTX_OFFSET: [usize; 3] = [
SIG_COEF_CONTEXTS_2D,
SIG_COEF_CONTEXTS_2D + 5,
SIG_COEF_CONTEXTS_2D + 10,
];
/// `Coeff_Base_Ctx_Offset[ TX_SIZES_ALL ][ 5 ][ 5 ]` (§8.3.2) — the
/// per-position context offset added in the two-dimensional branch of
/// `get_coeff_base_ctx()`. Indexed `[txSz][Min(row,4)][Min(col,4)]`
/// using the *original* (non-adjusted) transform size.
pub const COEFF_BASE_CTX_OFFSET: [[[usize; 5]; 5]; TX_SIZES_ALL] = [
// TX_4X4
[
[0, 1, 6, 6, 0],
[1, 6, 6, 21, 0],
[6, 6, 21, 21, 0],
[6, 21, 21, 21, 0],
[0, 0, 0, 0, 0],
],
// TX_8X8
[
[0, 1, 6, 6, 21],
[1, 6, 6, 21, 21],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_16X16
[
[0, 1, 6, 6, 21],
[1, 6, 6, 21, 21],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_32X32
[
[0, 1, 6, 6, 21],
[1, 6, 6, 21, 21],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_64X64
[
[0, 1, 6, 6, 21],
[1, 6, 6, 21, 21],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_4X8
[
[0, 11, 11, 11, 0],
[11, 11, 11, 11, 0],
[6, 6, 21, 21, 0],
[6, 21, 21, 21, 0],
[21, 21, 21, 21, 0],
],
// TX_8X4
[
[0, 16, 6, 6, 21],
[16, 16, 6, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
[0, 0, 0, 0, 0],
],
// TX_8X16
[
[0, 11, 11, 11, 11],
[11, 11, 11, 11, 11],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_16X8
[
[0, 16, 6, 6, 21],
[16, 16, 6, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
],
// TX_16X32
[
[0, 11, 11, 11, 11],
[11, 11, 11, 11, 11],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_32X16
[
[0, 16, 6, 6, 21],
[16, 16, 6, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
],
// TX_32X64
[
[0, 11, 11, 11, 11],
[11, 11, 11, 11, 11],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_64X32
[
[0, 16, 6, 6, 21],
[16, 16, 6, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
],
// TX_4X16
[
[0, 11, 11, 11, 0],
[11, 11, 11, 11, 0],
[6, 6, 21, 21, 0],
[6, 21, 21, 21, 0],
[21, 21, 21, 21, 0],
],
// TX_16X4
[
[0, 16, 6, 6, 21],
[16, 16, 6, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
[0, 0, 0, 0, 0],
],
// TX_8X32
[
[0, 11, 11, 11, 11],
[11, 11, 11, 11, 11],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_32X8
[
[0, 16, 6, 6, 21],
[16, 16, 6, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
],
// TX_16X64
[
[0, 11, 11, 11, 11],
[11, 11, 11, 11, 11],
[6, 6, 21, 21, 21],
[6, 21, 21, 21, 21],
[21, 21, 21, 21, 21],
],
// TX_64X16
[
[0, 16, 6, 6, 21],
[16, 16, 6, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
[16, 16, 21, 21, 21],
],
];
// ---------------------------------------------------------------------
// Round 142 — §5.11.40 `compute_tx_type()` derivation. Pure plumbing
// the tile-content walker will consume to map a 4×4 (blockX, blockY)
// position into the per-plane transform type. The arithmetic itself is
// independent of the msac decoder — it is a table lookup chain over the
// tx-set / tx-type tables, plus the `TxTypes[y][x]` luma cache the
// walker has already filled in.
// ---------------------------------------------------------------------
/// §6.10.16 `TxSize` ordinals (`TxSize` semantics table). Indices into
/// the `Tx_Size_Sqr_Up` / `Tx_Width` / `Tx_Height` / `Tx_Width_Log2`
/// tables. Previously local `const TX_16X16: u32 = 2;` / `const
/// TX_32X32: u32 = 3;` shadows inside the [`inter_tx_type_set`] /
/// [`intra_tx_type_set`] helpers used these same values; the public
/// constants below let new callers (e.g. [`compute_tx_type`]) reference
/// the same ordinals without re-typing them.
pub const TX_4X4: usize = 0;
/// See [`TX_4X4`] for the §6.10.16 ordinal source.
pub const TX_8X8: usize = 1;
/// See [`TX_4X4`] for the §6.10.16 ordinal source.
pub const TX_16X16: usize = 2;
/// See [`TX_4X4`] for the §6.10.16 ordinal source.
pub const TX_32X32: usize = 3;
/// See [`TX_4X4`] for the §6.10.16 ordinal source.
pub const TX_64X64: usize = 4;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `5` (4 wide × 8 tall).
pub const TX_4X8: usize = 5;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `6` (8 wide × 4 tall).
pub const TX_8X4: usize = 6;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `7` (8 wide × 16 tall).
pub const TX_8X16: usize = 7;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `8` (16 wide × 8 tall).
pub const TX_16X8: usize = 8;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `9` (16 wide × 32 tall).
pub const TX_16X32: usize = 9;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `10` (32 wide × 16 tall).
pub const TX_32X16: usize = 10;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `11` (32 wide × 64 tall).
pub const TX_32X64: usize = 11;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `12` (64 wide × 32 tall).
pub const TX_64X32: usize = 12;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `13` (4 wide × 16 tall).
pub const TX_4X16: usize = 13;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `14` (16 wide × 4 tall).
pub const TX_16X4: usize = 14;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `15` (8 wide × 32 tall).
pub const TX_8X32: usize = 15;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `16` (32 wide × 8 tall).
pub const TX_32X8: usize = 16;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `17` (16 wide × 64 tall).
pub const TX_16X64: usize = 17;
/// See [`TX_4X4`] for the §6.10.16 ordinal source. Rectangular
/// `TX_SIZES_ALL` ordinal `18` (64 wide × 16 tall).
pub const TX_64X16: usize = 18;
/// `MAX_VARTX_DEPTH` (§3, av1-spec p.7) — maximum depth for variable
/// transform trees. The §5.11.17 `read_var_tx_size` recursion stops
/// reading `txfm_split` once `depth` reaches this value (or once
/// `txSz == TX_4X4`).
pub const MAX_VARTX_DEPTH: u32 = 2;
/// `Max_Tx_Size_Rect[ BLOCK_SIZES ]` (§ Additional tables,
/// av1-spec p.402). For each block size, the largest rectangular
/// transform size that fits. Indexed by `MiSize` in `0..BLOCK_SIZES`.
/// Entries are `TX_SIZES_ALL` ordinals. Used by §5.11.15
/// `read_tx_size` (`maxRectTxSize = Max_Tx_Size_Rect[ MiSize ]`) and
/// by §5.11.16 `read_block_tx_size`.
pub const MAX_TX_SIZE_RECT: [usize; BLOCK_SIZES] = [
TX_4X4, // BLOCK_4X4
TX_4X8, // BLOCK_4X8
TX_8X4, // BLOCK_8X4
TX_8X8, // BLOCK_8X8
TX_8X16, // BLOCK_8X16
TX_16X8, // BLOCK_16X8
TX_16X16, // BLOCK_16X16
TX_16X32, // BLOCK_16X32
TX_32X16, // BLOCK_32X16
TX_32X32, // BLOCK_32X32
TX_32X64, // BLOCK_32X64
TX_64X32, // BLOCK_64X32
TX_64X64, // BLOCK_64X64
TX_64X64, // BLOCK_64X128
TX_64X64, // BLOCK_128X64
TX_64X64, // BLOCK_128X128
TX_4X16, // BLOCK_4X16
TX_16X4, // BLOCK_16X4
TX_8X32, // BLOCK_8X32
TX_32X8, // BLOCK_32X8
TX_16X64, // BLOCK_16X64
TX_64X16, // BLOCK_64X16
];
/// `Max_Tx_Depth[ BLOCK_SIZES ]` (§5.11.15, av1-spec p.69). For each
/// block size, the maximum transform depth (the number of times the
/// transform must be split to reach a 4×4 transform size). Indexed by
/// `MiSize` in `0..BLOCK_SIZES`. Spec note: this value can exceed
/// [`MAX_TX_DEPTH`] (`2`) for the larger block sizes — the
/// `tx_depth` syntax element only encodes values in `0..=2`, which
/// the §8.3.2 `tx_depth_cdf` lookup row dimensionality reflects.
///
/// Named `MAX_TX_DEPTH_TABLE` rather than `MAX_TX_DEPTH` to avoid
/// shadowing the existing [`MAX_TX_DEPTH`] symbol-cap constant
/// (the §3 constant, which is `2` and bounds the row length of
/// `Default_Tx_*Cdf`).
pub const MAX_TX_DEPTH_TABLE: [u32; BLOCK_SIZES] = [
0, 1, 1, 1, // BLOCK_4X4, BLOCK_4X8, BLOCK_8X4, BLOCK_8X8
2, 2, 2, 3, // BLOCK_8X16, BLOCK_16X8, BLOCK_16X16, BLOCK_16X32
3, 3, 4, 4, // BLOCK_32X16, BLOCK_32X32, BLOCK_32X64, BLOCK_64X32
4, 4, 4, 4, // BLOCK_64X64, BLOCK_64X128, BLOCK_128X64, BLOCK_128X128
2, 2, 3, 3, // BLOCK_4X16, BLOCK_16X4, BLOCK_8X32, BLOCK_32X8
4, 4, // BLOCK_16X64, BLOCK_64X16
];
/// `Split_Tx_Size[ TX_SIZES_ALL ]` (§ Additional tables,
/// av1-spec p.404). For each transform size, the size obtained by
/// splitting it into four sub-transforms. Indexed by `TxSize` in
/// `0..TX_SIZES_ALL`. Used by §5.11.15 (the `tx_depth` loop that
/// repeatedly splits `maxRectTxSize`) and by §5.11.17
/// `read_var_tx_size` (the per-recursion split).
pub const SPLIT_TX_SIZE: [usize; TX_SIZES_ALL] = [
TX_4X4, // TX_4X4 -> TX_4X4 (no further split)
TX_4X4, // TX_8X8 -> TX_4X4
TX_8X8, // TX_16X16 -> TX_8X8
TX_16X16, // TX_32X32 -> TX_16X16
TX_32X32, // TX_64X64 -> TX_32X32
TX_4X4, // TX_4X8 -> TX_4X4
TX_4X4, // TX_8X4 -> TX_4X4
TX_8X8, // TX_8X16 -> TX_8X8
TX_8X8, // TX_16X8 -> TX_8X8
TX_16X16, // TX_16X32 -> TX_16X16
TX_16X16, // TX_32X16 -> TX_16X16
TX_32X32, // TX_32X64 -> TX_32X32
TX_32X32, // TX_64X32 -> TX_32X32
TX_4X8, // TX_4X16 -> TX_4X8
TX_8X4, // TX_16X4 -> TX_8X4
TX_8X16, // TX_8X32 -> TX_8X16
TX_16X8, // TX_32X8 -> TX_16X8
TX_16X32, // TX_16X64 -> TX_16X32
TX_32X16, // TX_64X16 -> TX_32X16
];
/// `get_tx_size( plane, txSz )` per §5.11.37 (av1-spec p.87-88) — the
/// per-plane transform-size lookup the §5.11.34 `residual()` body
/// invokes once per plane to translate the §5.11.16 / §5.11.17 luma
/// `TxSize` into the chroma `txSz` argument that drives
/// `transform_block(plane, ..., txSz, ...)`.
///
/// Spec body:
///
/// ```text
/// get_tx_size( plane, txSz ) {
/// if ( plane == 0 )
/// return txSz
/// uvTx = Max_Tx_Size_Rect[ get_plane_residual_size( MiSize, plane ) ]
/// if ( Tx_Width[ uvTx ] == 64 || Tx_Height[ uvTx ] == 64 ) {
/// if ( Tx_Width[ uvTx ] == 16 ) return TX_16X32
/// if ( Tx_Height[ uvTx ] == 16 ) return TX_32X16
/// return TX_32X32
/// }
/// return uvTx
/// }
/// ```
///
/// The `subsampling_x` / `subsampling_y` are routed through to
/// [`get_plane_residual_size`] so the chroma residual size is correct
/// for 4:4:4 (`0, 0`), 4:2:2 (`1, 0`), and 4:2:0 (`1, 1`) streams.
///
/// Returns `Some(txSz)` for in-range arguments. Returns `None` when
/// `plane > 2`, `mi_size >= BLOCK_SIZES`, `tx_size >= TX_SIZES_ALL`,
/// `subsampling_{x,y} > 1`, or [`get_plane_residual_size`] returns
/// `None` (the §6.4.1 conformance gap).
#[inline]
#[must_use]
pub fn get_tx_size(
plane: u8,
tx_size: usize,
mi_size: usize,
subsampling_x: u8,
subsampling_y: u8,
) -> Option<usize> {
if plane > 2 {
return None;
}
if tx_size >= TX_SIZES_ALL {
return None;
}
if mi_size >= BLOCK_SIZES {
return None;
}
if subsampling_x > 1 || subsampling_y > 1 {
return None;
}
if plane == 0 {
return Some(tx_size);
}
let plane_sz = get_plane_residual_size(mi_size, plane, subsampling_x, subsampling_y)?;
let uv_tx = MAX_TX_SIZE_RECT[plane_sz];
let w = TX_WIDTH[uv_tx];
let h = TX_HEIGHT[uv_tx];
if w == 64 || h == 64 {
if w == 16 {
return Some(TX_16X32);
}
if h == 16 {
return Some(TX_32X16);
}
return Some(TX_32X32);
}
Some(uv_tx)
}
/// §6.10.19 / §3 transform-type ordinals. The spec lists `DCT_DCT = 0`
/// through `H_FLIPADST = 15` in a single enumeration; these are the
/// indices used by the [`MODE_TO_TXFM`] / [`TX_TYPE_IN_SET_INTRA`] /
/// [`TX_TYPE_IN_SET_INTER`] / [`get_tx_class`] tables, and the §5.11.40
/// `DCT_DCT` fallback returned by [`compute_tx_type`] when the
/// transform is too large or the per-plane derivation falls back.
pub const DCT_DCT: usize = 0;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const ADST_DCT: usize = 1;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const DCT_ADST: usize = 2;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const ADST_ADST: usize = 3;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const FLIPADST_DCT: usize = 4;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const DCT_FLIPADST: usize = 5;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const FLIPADST_FLIPADST: usize = 6;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const ADST_FLIPADST: usize = 7;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const FLIPADST_ADST: usize = 8;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const IDTX: usize = 9;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const V_DCT: usize = 10;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const H_DCT: usize = 11;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const V_ADST: usize = 12;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const H_ADST: usize = 13;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const V_FLIPADST: usize = 14;
/// See [`DCT_DCT`] for the §6.10.19 ordinal source.
pub const H_FLIPADST: usize = 15;
/// `TX_SET_TYPES_INTRA` (§3) — the number of `is_inter == 0` rows in
/// [`TX_TYPE_IN_SET_INTRA`] (`TX_SET_DCTONLY`, `TX_SET_INTRA_1`,
/// `TX_SET_INTRA_2`).
pub const TX_SET_TYPES_INTRA: usize = 3;
/// `TX_SET_TYPES_INTER` (§3) — the number of `is_inter == 1` rows in
/// [`TX_TYPE_IN_SET_INTER`] (`TX_SET_DCTONLY`, `TX_SET_INTER_1`,
/// `TX_SET_INTER_2`, `TX_SET_INTER_3`).
pub const TX_SET_TYPES_INTER: usize = 4;
/// `Mode_To_Txfm[ UV_INTRA_MODES_CFL_ALLOWED ]` (§ Additional tables).
/// Maps each chroma intra prediction mode to the transform type the
/// §5.11.40 `compute_tx_type()` derivation uses as the per-plane default
/// before the `is_tx_type_in_set` admission filter. Indexed by
/// `UVMode` in `0..UV_INTRA_MODES_CFL_ALLOWED`.
pub const MODE_TO_TXFM: [usize; UV_INTRA_MODES_CFL_ALLOWED] = [
DCT_DCT, // DC_PRED
ADST_DCT, // V_PRED
DCT_ADST, // H_PRED
DCT_DCT, // D45_PRED
ADST_ADST, // D135_PRED
ADST_DCT, // D113_PRED
DCT_ADST, // D157_PRED
DCT_ADST, // D203_PRED
ADST_DCT, // D67_PRED
ADST_ADST, // SMOOTH_PRED
ADST_DCT, // SMOOTH_V_PRED
DCT_ADST, // SMOOTH_H_PRED
ADST_ADST, // PAETH_PRED
DCT_DCT, // UV_CFL_PRED
];
/// `Tx_Type_In_Set_Intra[ TX_SET_TYPES_INTRA ][ TX_TYPES ]` (§5.11.40).
/// One row per intra `txSet` index (`TX_SET_DCTONLY`, `TX_SET_INTRA_1`,
/// `TX_SET_INTRA_2`). Row `txSet`'s column `txType` is `1` iff the spec
/// considers `txType` admissible for that set; the §5.11.40
/// `is_tx_type_in_set(txSet, txType)` predicate is a direct table read.
pub const TX_TYPE_IN_SET_INTRA: [[u8; TX_TYPES]; TX_SET_TYPES_INTRA] = [
// TX_SET_DCTONLY: only DCT_DCT.
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// TX_SET_INTRA_1: DCT/ADST cross-set plus IDTX/V_DCT/H_DCT.
[1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
// TX_SET_INTRA_2: DCT/ADST cross-set plus IDTX.
[1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
];
/// `Tx_Type_In_Set_Inter[ TX_SET_TYPES_INTER ][ TX_TYPES ]` (§5.11.40).
/// One row per inter `txSet` index (`TX_SET_DCTONLY`, `TX_SET_INTER_1`,
/// `TX_SET_INTER_2`, `TX_SET_INTER_3`). Row `txSet`'s column `txType`
/// is `1` iff the spec considers `txType` admissible for that set; the
/// §5.11.40 `is_tx_type_in_set(txSet, txType)` predicate on the
/// `is_inter == 1` branch is a direct table read.
pub const TX_TYPE_IN_SET_INTER: [[u8; TX_TYPES]; TX_SET_TYPES_INTER] = [
// TX_SET_DCTONLY: only DCT_DCT.
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
// TX_SET_INTER_1: every transform type.
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
// TX_SET_INTER_2: full set minus the four V_/H_FLIPADST tail.
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
// TX_SET_INTER_3: { IDTX, DCT_DCT }.
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
];
/// §5.11.40 `is_tx_type_in_set( txSet, txType )` — the admission filter
/// the [`compute_tx_type`] derivation runs after looking up the
/// per-plane default `txType`. Reads the [`TX_TYPE_IN_SET_INTER`] /
/// [`TX_TYPE_IN_SET_INTRA`] table per the `is_inter` flag and returns
/// `true` when the entry is non-zero.
///
/// Returns `false` for any out-of-range `tx_set` / `tx_type` index
/// rather than panicking; the spec's reachable values always stay in
/// range, so a `false` return tells the caller a bookkeeping bug has
/// presented an unreachable combination.
pub fn is_tx_type_in_set(is_inter: bool, tx_set: u32, tx_type: usize) -> bool {
let set = tx_set as usize;
if tx_type >= TX_TYPES {
return false;
}
if is_inter {
TX_TYPE_IN_SET_INTER
.get(set)
.map(|row| row[tx_type] != 0)
.unwrap_or(false)
} else {
TX_TYPE_IN_SET_INTRA
.get(set)
.map(|row| row[tx_type] != 0)
.unwrap_or(false)
}
}
/// §5.11.40 `compute_tx_type( plane, txSz, blockX, blockY )` — the
/// per-plane transform-type derivation the tile-content walker reads
/// before kicking off coefficient decoding and inverse transform.
///
/// ```text
/// txSzSqrUp = Tx_Size_Sqr_Up[ txSz ]
/// if ( Lossless || txSzSqrUp > TX_32X32 ) return DCT_DCT
/// txSet = get_tx_set( txSz )
/// if ( plane == 0 ) return TxTypes[ blockY ][ blockX ]
/// if ( is_inter ) {
/// x4 = Max( MiCol, blockX << subsampling_x )
/// y4 = Max( MiRow, blockY << subsampling_y )
/// txType = TxTypes[ y4 ][ x4 ]
/// if ( !is_tx_type_in_set( txSet, txType ) ) return DCT_DCT
/// return txType
/// }
/// txType = Mode_To_Txfm[ UVMode ]
/// if ( !is_tx_type_in_set( txSet, txType ) ) return DCT_DCT
/// return txType
/// ```
///
/// All bitstream-derived state the spec function reads from globals is
/// passed in explicitly:
///
/// * `plane` — `0` for luma (Y), nonzero for chroma (U / V). Only the
/// `plane == 0` branch is special-cased; the §5.11.40 lookup returns
/// the same value for plane 1 and plane 2 with chroma inputs.
/// * `tx_sz` — `TxSize` index in `TX_4X4..TX_64X64..TX_64X16`
/// (§6.10.16 ordinal). Out-of-range inputs return `DCT_DCT` (the
/// §5.11.40 `txSzSqrUp > TX_32X32` fallback's safest extension to a
/// bookkeeping bug).
/// * `lossless`, `is_inter`, `mi_row`, `mi_col`, `subsampling_x` /
/// `subsampling_y`, `uv_mode` — the spec globals of the same names.
/// * `block_x`, `block_y` — the §5.11.40 4×4-granularity block
/// coordinates (in 4×4 luma units; the spec's `blockX << subsampling_x`
/// product yields the same units as `MiCol`).
/// * `tx_types(y, x)` — the §5.11.40 `TxTypes[y][x]` luma cache the
/// walker maintains. Passed as a closure so the helper does not bake
/// in a particular storage shape — the walker may back it by a dense
/// 2D array, a sparse map, or a `MiRow / MiCol`-relative tile-local
/// view. The closure is only invoked on the luma / inter chroma
/// branches.
///
/// Returns a transform-type ordinal in `0..TX_TYPES` (see [`DCT_DCT`]
/// through [`H_FLIPADST`]).
#[allow(clippy::too_many_arguments)]
pub fn compute_tx_type<F>(
plane: usize,
tx_sz: usize,
lossless: bool,
is_inter: bool,
tx_set: u32,
mi_row: u32,
mi_col: u32,
block_x: u32,
block_y: u32,
subsampling_x: u32,
subsampling_y: u32,
uv_mode: usize,
tx_types: F,
) -> usize
where
F: Fn(u32, u32) -> usize,
{
// `Lossless || txSzSqrUp > TX_32X32` short-circuit. Treat
// out-of-range tx_sz as DCT_DCT for the same reason: the spec's
// reachable inputs all land in `TX_SIZES_ALL`, so an out-of-range
// index is a caller bug whose safest behaviour matches the
// §5.11.40 too-large fallback.
if lossless {
return DCT_DCT;
}
let tx_sz_sqr_up = match TX_SIZE_SQR_UP.get(tx_sz) {
Some(v) => *v,
None => return DCT_DCT,
};
if tx_sz_sqr_up > TX_32X32 {
return DCT_DCT;
}
if plane == 0 {
return tx_types(block_y, block_x);
}
if is_inter {
// `x4 = Max( MiCol, blockX << subsampling_x )` —
// `block_x << subsampling_x` lifts the chroma 4×4 coordinate
// back into luma units; the `Max` against `MiCol` clips the
// first row/col of a chroma block to the top-left of its
// luma siblings (this matters when the chroma block straddles
// the MI grid origin).
let x4 = (block_x << subsampling_x).max(mi_col);
let y4 = (block_y << subsampling_y).max(mi_row);
let tx_type = tx_types(y4, x4);
if !is_tx_type_in_set(true, tx_set, tx_type) {
return DCT_DCT;
}
return tx_type;
}
let tx_type = match MODE_TO_TXFM.get(uv_mode) {
Some(v) => *v,
None => return DCT_DCT,
};
if !is_tx_type_in_set(false, tx_set, tx_type) {
return DCT_DCT;
}
tx_type
}
// ---------------------------------------------------------------------
// Round 183 — §7.12.2 dequantization-function helpers + §7.12.3 step-1
// dequantization. The §5.11.34 `residual()` dispatcher consumes the
// `Dequant[i][j] = sign * (|Quant[pos] * q2| & 0xFFFFFF) / dqDenom`
// loop these helpers compose, with `q2 = Round2( q * Quantizer_Matrix[
// ... ], 5 )` on the `using_qmatrix && PlaneTxType < IDTX &&
// SegQMLevel < 15` arm and `q2 = q` otherwise. The §9.5.3
// `Quantizer_Matrix[][][]` table lands in [`crate::qmatrix`] (r204);
// `dequantize_step1` reads it via `qmatrix::qmatrix_value`.
// ---------------------------------------------------------------------
/// `Dc_Qlookup[ 3 ][ 256 ]` per §7.12.2 (av1-spec p.289-290) — the
/// per-bit-depth dc-quantizer index lookup. Row 0 is `BitDepth == 8`,
/// row 1 is `BitDepth == 10`, row 2 is `BitDepth == 12`; the row index
/// is `(BitDepth - 8) >> 1` per the §7.12.2 `dc_q( b )` definition.
/// Column index is `Clip3( 0, 255, b )` where `b = base_q_idx +
/// delta_q_dc + segment_delta`.
pub const DC_QLOOKUP: [[i32; 256]; 3] = [
[
4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 26,
27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42, 43, 43, 44, 45, 46,
47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 62, 62, 63, 64, 65,
66, 66, 67, 68, 69, 70, 70, 71, 72, 73, 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83,
84, 85, 85, 87, 88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111,
113, 114, 116, 117, 118, 120, 121, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144,
146, 148, 150, 152, 154, 156, 158, 161, 164, 166, 169, 172, 174, 177, 180, 182, 185, 187,
190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220, 223, 226, 230, 233, 237, 240, 243,
247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292, 296, 300, 304, 309, 313,
317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, 369, 374, 379, 384, 389, 395, 400,
406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513, 522,
530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755,
775, 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282,
1336,
],
[
4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, 60, 64, 68, 71,
75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, 132, 136, 140, 143,
147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200, 204, 208, 212,
215, 219, 223, 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, 273, 276,
280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334, 337,
343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448,
454, 460, 466, 472, 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584,
592, 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759,
770, 782, 795, 807, 819, 831, 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988,
1001, 1015, 1030, 1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218,
1236, 1253, 1271, 1288, 1306, 1323, 1342, 1361, 1379, 1398, 1416, 1436, 1456, 1476, 1496,
1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, 1741, 1766, 1791, 1817, 1844,
1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363,
2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375,
3478, 3586, 3702, 3823, 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347,
],
[
4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, 166, 180, 194, 208,
222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, 405, 421, 437, 453, 469, 484,
500, 516, 532, 548, 564, 580, 596, 611, 627, 643, 659, 674, 690, 706, 721, 737, 752, 768,
783, 798, 814, 829, 844, 859, 874, 889, 904, 919, 934, 949, 964, 978, 993, 1008, 1022,
1037, 1051, 1065, 1080, 1094, 1108, 1122, 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234,
1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342, 1368, 1393, 1419, 1444, 1469, 1494, 1519,
1544, 1569, 1594, 1618, 1643, 1668, 1692, 1717, 1741, 1765, 1789, 1814, 1838, 1862, 1885,
1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199, 2233, 2267, 2300, 2334, 2367,
2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, 2788, 2830, 2872, 2913, 2954,
2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517, 3565, 3621, 3677,
3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420, 4479, 4546,
4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291, 5367, 5442, 5517, 5591,
5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867,
6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, 8492, 8635,
8788, 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482,
11776, 12081, 12409, 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943,
17575, 18237, 18949, 19718, 20521, 21387,
],
];
/// `Ac_Qlookup[ 3 ][ 256 ]` per §7.12.2 (av1-spec p.291-292) — the
/// per-bit-depth ac-quantizer index lookup. Row layout matches
/// [`DC_QLOOKUP`].
pub const AC_QLOOKUP: [[i32; 256]; 3] = [
[
4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
99, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130,
132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173,
176, 179, 182, 185, 188, 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235,
239, 243, 247, 251, 255, 260, 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323,
329, 335, 341, 347, 353, 359, 366, 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448,
456, 465, 474, 483, 492, 501, 510, 520, 530, 540, 550, 560, 571, 582, 593, 604, 615, 627,
639, 651, 663, 676, 689, 702, 715, 729, 743, 757, 771, 786, 801, 816, 832, 848, 864, 881,
898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066, 1087, 1108, 1129, 1151, 1173, 1196,
1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537, 1567, 1597,
1628, 1660, 1692, 1725, 1759, 1793, 1828,
],
[
4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, 67, 71, 75, 79,
83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, 149, 154, 158, 163,
168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231, 235, 240, 244,
249, 253, 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, 319, 324,
328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396, 401,
409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547,
555, 563, 571, 579, 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737,
749, 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002,
1018, 1038, 1058, 1078, 1098, 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314,
1338, 1362, 1386, 1411, 1435, 1463, 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727,
1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079, 2119, 2159, 2199, 2239, 2283,
2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, 2971, 3027,
3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028,
4104, 4184, 4264, 4348, 4432, 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, 5372,
5476, 5584, 5692, 5804, 5916, 6032, 6148, 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172,
7312,
],
[
4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, 183, 199, 214, 230,
247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, 456, 475, 493, 511, 530, 548,
567, 586, 604, 623, 642, 660, 679, 698, 716, 735, 753, 772, 791, 809, 828, 846, 865, 884,
902, 920, 939, 957, 976, 994, 1012, 1030, 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175,
1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317, 1335, 1352, 1370, 1387, 1405, 1422, 1440,
1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595, 1627, 1660, 1693, 1725, 1758, 1791,
1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, 2085, 2118, 2150, 2183, 2216, 2248, 2281,
2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750, 2798, 2847, 2895, 2943,
2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, 3619, 3684, 3748, 3812,
3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791, 4871, 4967,
5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410, 6522,
6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, 8011, 8155, 8315, 8475, 8635,
8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228,
11437, 11661, 11885, 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365,
14637, 14925, 15213, 15502, 15806, 16110, 16414, 16734, 17054, 17390, 17726, 18062, 18414,
18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486, 21902, 22334, 22766, 23214, 23662,
24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247,
],
];
/// §7.12.2 `dc_q( b ) = Dc_Qlookup[ (BitDepth-8) >> 1 ][ Clip3( 0, 255,
/// b ) ]`. `bit_depth` must be 8, 10, or 12 (the only AV1 profile bit
/// depths per §6.4.1); other values are clamped into the row table by
/// `(bit_depth - 8) >> 1` (the spec's reachable inputs are exactly
/// `{ 8, 10, 12 }`).
#[inline]
pub fn dc_q(bit_depth: u8, b: i32) -> i32 {
let row = ((bit_depth.max(8) - 8) >> 1) as usize;
let row = row.min(2);
let col = b.clamp(0, 255) as usize;
DC_QLOOKUP[row][col]
}
/// §7.12.2 `ac_q( b ) = Ac_Qlookup[ (BitDepth-8) >> 1 ][ Clip3( 0, 255,
/// b ) ]`. See [`dc_q`] for the row / column derivation.
#[inline]
pub fn ac_q(bit_depth: u8, b: i32) -> i32 {
let row = ((bit_depth.max(8) - 8) >> 1) as usize;
let row = row.min(2);
let col = b.clamp(0, 255) as usize;
AC_QLOOKUP[row][col]
}
/// Per-segment / per-plane quantizer state — the §7.12.2 `get_qindex` /
/// `get_dc_quant` / `get_ac_quant` driver state.
///
/// The §5.9.12 / §5.9.13 / §5.9.14 / §5.9.13 parser populates the
/// `base_q_idx` / `delta_q_*` / `seg_feature_*` slots; the per-frame
/// dispatcher passes a `QuantizerParams` to [`PartitionWalker::residual`]
/// so the §7.12.3 step-1 loop has everything it needs to derive
/// `Dequant[i][j]` from a `Quant[]` raster.
///
/// `current_q_index` reflects the §5.9.18 `delta_q_present` per-block
/// running state; it is `base_q_idx` for blocks with no `delta_q`
/// override. `lossless` is the per-segment §6.8.11 `LosslessArray[seg]`
/// derivation (`true` iff `Round( get_qindex( 1, seg ) ) == 0 &&
/// DeltaQYDc == 0 && DeltaQUAc == 0 && DeltaQUDc == 0 && DeltaQVAc == 0
/// && DeltaQVDc == 0`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QuantizerParams {
/// §5.9.12 `base_q_idx`.
pub base_q_idx: u8,
/// §5.9.12 `DeltaQYDc`.
pub delta_q_y_dc: i8,
/// §5.9.12 `DeltaQUDc`.
pub delta_q_u_dc: i8,
/// §5.9.12 `DeltaQUAc`.
pub delta_q_u_ac: i8,
/// §5.9.12 `DeltaQVDc`.
pub delta_q_v_dc: i8,
/// §5.9.12 `DeltaQVAc`.
pub delta_q_v_ac: i8,
/// §5.9.12 `using_qmatrix`.
pub using_qmatrix: bool,
/// §6.4.1 `BitDepth` ∈ { 8, 10, 12 }.
pub bit_depth: u8,
/// §5.9.18 `delta_q_present`. Gates the §7.12.2
/// `if ( ignoreDeltaQ == 0 && delta_q_present == 1 )` branches.
pub delta_q_present: bool,
/// §5.9.18 `CurrentQIndex`. Carries the per-block §5.11.13 running
/// quantiser-index after the latest `read_delta_q()` update; equals
/// `base_q_idx` when `delta_q_present == 0`.
pub current_q_index: u8,
/// §5.9.14 `segmentation_enabled`.
pub segmentation_enabled: bool,
/// §5.9.14 `FeatureEnabled[ seg ][ SEG_LVL_ALT_Q ]` — `true`
/// iff the per-segment `SEG_LVL_ALT_Q` slot is active. Read at
/// `get_qindex` `seg_feature_active_idx( seg, SEG_LVL_ALT_Q )`.
pub seg_alt_q_active: [bool; MAX_SEGMENTS],
/// §5.9.14 `FeatureData[ seg ][ SEG_LVL_ALT_Q ]` — the per-segment
/// signed delta added to `base_q_idx` (or `CurrentQIndex` on the
/// `delta_q_present` arm) before `Clip3( 0, 255, _ )`.
pub seg_alt_q_data: [i16; MAX_SEGMENTS],
/// §5.9.2 `SegQMLevel[ plane ][ segmentId ]` — the per-segment
/// quantizer-matrix level triple the §7.12.3 step-1b QM arm reads.
/// Derived in the §5.9.2 `uncompressed_header()` trailer: when
/// `using_qmatrix == 1` a non-lossless segment carries
/// `[ qm_y, qm_u, qm_v ]` and a lossless segment the no-QM
/// sentinel `15`; when `using_qmatrix == 0` the array is never
/// consulted (§7.12.3 gates on `using_qmatrix` first) and stays at
/// the all-`15` neutral fill.
pub seg_qm_level: [[u8; MAX_SEGMENTS]; 3],
}
impl QuantizerParams {
/// Default-construct a `QuantizerParams` with all-zero deltas, no
/// segmentation, and 8-bit depth. Useful as a neutral starting
/// state for tests + the §5.11.5 walker's intra-only path.
#[must_use]
pub const fn neutral(base_q_idx: u8, bit_depth: u8) -> Self {
Self {
base_q_idx,
delta_q_y_dc: 0,
delta_q_u_dc: 0,
delta_q_u_ac: 0,
delta_q_v_dc: 0,
delta_q_v_ac: 0,
using_qmatrix: false,
bit_depth,
delta_q_present: false,
current_q_index: base_q_idx,
segmentation_enabled: false,
seg_alt_q_active: [false; MAX_SEGMENTS],
seg_alt_q_data: [0; MAX_SEGMENTS],
seg_qm_level: [[15; MAX_SEGMENTS]; 3],
}
}
/// `SegQMLevel[ 0..3 ][ segment_id ]` — the per-plane QM-level
/// triple for one block's segment, in the `[u8; 3]` plane-indexed
/// shape [`ResidualContext::seg_qm_level`] carries into the
/// §7.12.3 step-1 dequantization loop.
#[must_use]
pub fn seg_qm_level_for(&self, segment_id: u8) -> [u8; 3] {
let seg = (segment_id as usize).min(MAX_SEGMENTS - 1);
[
self.seg_qm_level[0][seg],
self.seg_qm_level[1][seg],
self.seg_qm_level[2][seg],
]
}
}
/// §7.12.2 `get_qindex( ignoreDeltaQ, segmentId )` (av1-spec p.293) —
/// the per-block quantiser index used to feed `dc_q` / `ac_q`.
///
/// ```text
/// if ( seg_feature_active_idx( segmentId, SEG_LVL_ALT_Q ) ) {
/// data = FeatureData[ segmentId ][ SEG_LVL_ALT_Q ]
/// qindex = base_q_idx + data
/// if ( !ignoreDeltaQ && delta_q_present )
/// qindex = CurrentQIndex + data
/// return Clip3( 0, 255, qindex )
/// }
/// if ( !ignoreDeltaQ && delta_q_present )
/// return CurrentQIndex
/// return base_q_idx
/// ```
///
/// The `seg_feature_active_idx` predicate is `segmentation_enabled &&
/// seg_alt_q_active[seg]`. `seg_id` is the per-block `segment_id`
/// (`0..MAX_SEGMENTS`).
#[inline]
pub fn get_qindex(quant: &QuantizerParams, ignore_delta_q: bool, segment_id: u8) -> i32 {
let seg = (segment_id as usize).min(MAX_SEGMENTS - 1);
let seg_active = quant.segmentation_enabled && quant.seg_alt_q_active[seg];
if seg_active {
let data = quant.seg_alt_q_data[seg] as i32;
let qindex = if !ignore_delta_q && quant.delta_q_present {
(quant.current_q_index as i32) + data
} else {
(quant.base_q_idx as i32) + data
};
return qindex.clamp(0, 255);
}
if !ignore_delta_q && quant.delta_q_present {
return quant.current_q_index as i32;
}
quant.base_q_idx as i32
}
/// §7.12.2 `get_dc_quant( plane )` (av1-spec p.293-294). Returns
/// `dc_q( get_qindex( 0, segment_id ) + DeltaQ?Dc )` per plane.
///
/// `plane` is `0` (Y), `1` (U), or `2` (V). The §7.12.2 spec passes
/// `ignoreDeltaQ = 0` here (the §5.9.18 delta-q running override
/// applies).
#[inline]
pub fn get_dc_quant(quant: &QuantizerParams, plane: u8, segment_id: u8) -> i32 {
let base = get_qindex(quant, false, segment_id);
let delta = match plane {
0 => quant.delta_q_y_dc as i32,
1 => quant.delta_q_u_dc as i32,
_ => quant.delta_q_v_dc as i32,
};
dc_q(quant.bit_depth, base + delta)
}
/// §7.12.2 `get_ac_quant( plane )` (av1-spec p.294). Returns
/// `ac_q( get_qindex( 0, segment_id ) + DeltaQ?Ac )`. Plane 0's AC
/// delta is implicit zero (the spec has no `DeltaQYAc`; `base_q_idx`
/// itself IS the Y AC qindex).
#[inline]
pub fn get_ac_quant(quant: &QuantizerParams, plane: u8, segment_id: u8) -> i32 {
let base = get_qindex(quant, false, segment_id);
let delta = match plane {
0 => 0,
1 => quant.delta_q_u_ac as i32,
_ => quant.delta_q_v_ac as i32,
};
ac_q(quant.bit_depth, base + delta)
}
/// `dqDenom` per §7.12.3 (av1-spec p.294). 2 for 32x32-axis transforms
/// (`TX_32X32`, `TX_16X32`, `TX_32X16`, `TX_16X64`, `TX_64X16`); 4 for
/// 64x64-axis transforms (`TX_64X64`, `TX_32X64`, `TX_64X32`); 1
/// otherwise.
#[inline]
pub fn dequant_denom(tx_size: usize) -> i64 {
match tx_size {
v if v == TX_32X32 || v == TX_16X32 || v == TX_32X16 || v == TX_16X64 || v == TX_64X16 => 2,
v if v == TX_64X64 || v == TX_32X64 || v == TX_64X32 => 4,
_ => 1,
}
}
/// §7.12.3 step-1 dequantization (av1-spec p.294-295) — replaces the
/// r182 placeholder identity dequant in the `residual()` /
/// `transform_block_emit` path.
///
/// The spec body computes
///
/// ```text
/// for i = 0..(th-1) for j = 0..(tw-1) {
/// q = (i == 0 && j == 0) ? get_dc_quant(plane) : get_ac_quant(plane)
/// if ( using_qmatrix && PlaneTxType < IDTX
/// && SegQMLevel[plane][segment_id] < 15 ) {
/// q2 = Round2( q * Quantizer_Matrix[ ... ], 5 )
/// } else {
/// q2 = q
/// }
/// dq = Quant[ i*tw + j ] * q2
/// sign = ( dq < 0 ) ? -1 : 1
/// dq2 = sign * ( |dq| & 0xFFFFFF ) / dqDenom
/// Dequant[i][j] = Clip3( -(1<<(7+BitDepth)),
/// (1<<(7+BitDepth)) - 1, dq2 )
/// }
/// ```
///
/// where `tw = Min(32, Tx_Width[txSz])`, `th = Min(32, Tx_Height[txSz])`,
/// and `dqDenom` is [`dequant_denom`]. Coefficient positions
/// `(i, j)` with `i >= th` or `j >= tw` stay at the §5.11.39 `Dequant[
/// i ][ j ] = 0` pre-loop default and are emitted as zero — the
/// returned buffer is dense `w * h` (with `w = Tx_Width[txSz]`,
/// `h = Tx_Height[txSz]`) so it feeds [`crate::transform::inverse_transform_2d`]
/// directly.
///
/// ## QM matrix path
///
/// When `using_qmatrix == 1 && plane_tx_type < IDTX && seg_qm_level
/// < 15` the spec replaces `q2 = q` with
///
/// ```text
/// q2 = Round2( q * Quantizer_Matrix[ seg_qm_level ][ plane > 0 ]
/// [ Qm_Offset[ tx_size ] + i * tw + j ], 5 )
/// ```
///
/// The full §9.5.3 `Quantizer_Matrix[15][2][3344]` and
/// `Qm_Offset[TX_SIZES_ALL]` tables live in [`crate::qmatrix`]; this
/// loop reads them via [`crate::qmatrix::qmatrix_value`]. `Round2`
/// is the §A.3 integer form `(x + (1 << (n - 1))) >> n`.
///
/// ## Arguments
///
/// * `quant_levels` — the §5.11.39 `Quant[]` raster-order signed
/// levels, `Tx_Width[txSz] * Tx_Height[txSz]` cells.
/// * `tx_size` — the per-TU `TX_SIZES_ALL` ordinal.
/// * `plane` — `0` (Y), `1` (U), `2` (V). Drives the
/// `get_dc_quant` / `get_ac_quant` plane axis.
/// * `segment_id` — `0..MAX_SEGMENTS`. Drives `get_qindex`.
/// * `plane_tx_type` — the §5.11.40 `PlaneTxType` derivation (one of
/// `DCT_DCT..H_FLIPADST`). Only the `< IDTX` guard on the QM-active
/// branch consults it.
/// * `seg_qm_level` — `SegQMLevel[plane][segment_id]` from §6.8.13.
/// `15` (or any value `>= 15`) takes the no-QM identity branch.
/// * `quant` — the per-frame [`QuantizerParams`].
///
/// Returns a `Vec<i64>` of length `Tx_Width[txSz] * Tx_Height[txSz]`
/// in row-major order — the §7.12.3 `Dequant[i][j]` table feeding
/// the §7.13 inverse-transform.
#[allow(clippy::too_many_arguments)]
pub fn dequantize_step1(
quant_levels: &[i32],
tx_size: usize,
plane: u8,
segment_id: u8,
plane_tx_type: usize,
seg_qm_level: u8,
quant: &QuantizerParams,
) -> Vec<i64> {
debug_assert!(tx_size < TX_SIZES_ALL);
let w = TX_WIDTH[tx_size];
let h = TX_HEIGHT[tx_size];
let tw = core::cmp::min(32, w);
let th = core::cmp::min(32, h);
let dq_denom = dequant_denom(tx_size);
let clip_min: i64 = -(1i64 << (7 + quant.bit_depth as u32));
let clip_max: i64 = (1i64 << (7 + quant.bit_depth as u32)) - 1;
let dc_q_val = get_dc_quant(quant, plane, segment_id) as i64;
let ac_q_val = get_ac_quant(quant, plane, segment_id) as i64;
let qm_active = quant.using_qmatrix && plane_tx_type < IDTX && seg_qm_level < 15;
let mut dequant = vec![0i64; w * h];
for i in 0..th {
for j in 0..tw {
let q = if i == 0 && j == 0 { dc_q_val } else { ac_q_val };
// §7.12.3 step 1b: `q2 = q` on the no-QM arm; the
// QM-active arm reads §9.5.3 Quantizer_Matrix.
let q2 = if qm_active {
let qm = crate::qmatrix::qmatrix_value(seg_qm_level, plane, tx_size, i, j) as i64;
// Round2( q * qm, 5 ) per §3 p.13: (x + (1<<4)) >> 5.
(q * qm + 16) >> 5
} else {
q
};
// §7.12.3 step 1c: `dq = Quant[ i * tw + j ] * q2` — the
// Quant array uses the COMPACT `tw`-stride layout (the §9.2
// scan tables for 64-dim transforms address a `Min(32, w) ×
// Min(32, h)` grid), while `Dequant[ i ][ j ]` is the full
// `w × h` array the §7.13.3 inverse transform consumes
// (cells past 32 stay zero per its i/j < 32 gate).
let dq = (quant_levels[i * tw + j] as i64) * q2;
let sign: i64 = if dq < 0 { -1 } else { 1 };
let dq2 = sign * ((dq.unsigned_abs() & 0xFF_FFFF) as i64) / dq_denom;
dequant[i * w + j] = dq2.clamp(clip_min, clip_max);
}
}
dequant
}
// ---------------------------------------------------------------------
// Round 183 — §5.11.47 `transform_type()` per-TU S() read + inversion
// tables.
// ---------------------------------------------------------------------
/// `Tx_Type_Intra_Inv_Set1[ 7 ]` per §5.11.47 (av1-spec p.100). Inversion
/// table for `intra_tx_type` when `set == TX_SET_INTRA_1`.
pub const TX_TYPE_INTRA_INV_SET1: [usize; 7] =
[IDTX, DCT_DCT, V_DCT, H_DCT, ADST_ADST, ADST_DCT, DCT_ADST];
/// `Tx_Type_Intra_Inv_Set2[ 5 ]` per §5.11.47. Inversion table for
/// `intra_tx_type` when `set == TX_SET_INTRA_2`.
pub const TX_TYPE_INTRA_INV_SET2: [usize; 5] = [IDTX, DCT_DCT, ADST_ADST, ADST_DCT, DCT_ADST];
/// `Tx_Type_Inter_Inv_Set1[ 16 ]` per §5.11.47. Inversion table for
/// `inter_tx_type` when `set == TX_SET_INTER_1`.
pub const TX_TYPE_INTER_INV_SET1: [usize; 16] = [
IDTX,
V_DCT,
H_DCT,
V_ADST,
H_ADST,
V_FLIPADST,
H_FLIPADST,
DCT_DCT,
ADST_DCT,
DCT_ADST,
FLIPADST_DCT,
DCT_FLIPADST,
ADST_ADST,
FLIPADST_FLIPADST,
ADST_FLIPADST,
FLIPADST_ADST,
];
/// `Tx_Type_Inter_Inv_Set2[ 12 ]` per §5.11.47. Inversion table for
/// `inter_tx_type` when `set == TX_SET_INTER_2`.
pub const TX_TYPE_INTER_INV_SET2: [usize; 12] = [
IDTX,
V_DCT,
H_DCT,
DCT_DCT,
ADST_DCT,
DCT_ADST,
FLIPADST_DCT,
DCT_FLIPADST,
ADST_ADST,
FLIPADST_FLIPADST,
ADST_FLIPADST,
FLIPADST_ADST,
];
/// `Tx_Type_Inter_Inv_Set3[ 2 ]` per §5.11.47. Inversion table for
/// `inter_tx_type` when `set == TX_SET_INTER_3`.
pub const TX_TYPE_INTER_INV_SET3: [usize; 2] = [IDTX, DCT_DCT];
/// §5.11.47 forward (encode-side) `TxType -> intra_tx_type` /
/// `inter_tx_type` ordinal lookup — the inverse of the §5.11.47 read
/// path's `Tx_Type_*_Inv_Set{1,2,3}[ symbol ] -> TxType` inversion
/// tables ([`TX_TYPE_INTRA_INV_SET1`] etc.). Given the resolved
/// §5.11.48 `set` (`TX_SET_INTRA_{1,2}` / `TX_SET_INTER_{1,2,3}`) and a
/// §3 `TxType`, returns the per-set symbol ordinal the
/// `intra_tx_type` / `inter_tx_type` S() carries, or `None` when the
/// `TxType` is not admissible in `set` (the `is_tx_type_in_set`
/// predicate fails — a caller bug on the write side, since a conformant
/// encoder only commits a `TxType` the chosen `set` admits).
///
/// On `set == TX_SET_DCTONLY` no symbol is coded (the §5.11.47 `set >
/// 0` guard short-circuits to `DCT_DCT`), so this helper returns `None`
/// there: callers gate the S() on `set > 0` before consulting it.
#[must_use]
pub fn tx_type_to_symbol(set: u32, is_inter: bool, tx_type: usize) -> Option<u32> {
let table: &[usize] = if is_inter {
match set {
TX_SET_INTER_1 => &TX_TYPE_INTER_INV_SET1,
TX_SET_INTER_2 => &TX_TYPE_INTER_INV_SET2,
TX_SET_INTER_3 => &TX_TYPE_INTER_INV_SET3,
_ => return None,
}
} else {
match set {
TX_SET_INTRA_1 => &TX_TYPE_INTRA_INV_SET1,
TX_SET_INTRA_2 => &TX_TYPE_INTRA_INV_SET2,
_ => return None,
}
};
table.iter().position(|&t| t == tx_type).map(|p| p as u32)
}
/// Per-block §5.11.34 `residual()` quantiser / tx-type context — the
/// fields the §5.11.47 `transform_type` reader, §5.11.40
/// `compute_tx_type` derivation, and §7.12.3 step-1 dequant loop need
/// beyond what the §5.11.34 dispatcher already plumbs.
///
/// Constructed once per block (the §5.11.5 walker derives every field
/// from the per-block syntax it already parses) and forwarded to
/// [`PartitionWalker::residual`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResidualContext {
/// §5.9.18 / §7.12.2 per-frame quantiser state. Drives
/// `get_dc_quant` / `get_ac_quant`.
pub quant: QuantizerParams,
/// §5.11.4 `reduced_tx_set` — the frame-header bit gating the
/// §5.11.48 `get_tx_set()` reduction.
pub reduced_tx_set: bool,
/// §8.3.2 `intra_tx_type` `intraDir` — the [`intra_dir`] result for
/// the current block. Unused on the inter path (the §8.3.2
/// `inter_tx_type` CDF has no `intraDir` axis).
pub intra_dir: usize,
/// `segment_id` per §5.11.9 / §5.9.14. Drives `get_qindex` and
/// `SegQMLevel[plane][segment_id]`.
pub segment_id: u8,
/// `SegQMLevel[ plane ][ segment_id ]` per §6.8.13. Three planes,
/// `15` for the "skip QM" sentinel. Stored as a flat `[u8; 3]` —
/// indexed by `plane`. Pass `[15; 3]` to force the no-QM identity
/// path (matches the `using_qmatrix == false` path of the §7.12.3
/// step-1 loop).
pub seg_qm_level: [u8; 3],
/// `UVMode` per §5.11.7 / §5.11.22 — the chroma intra-prediction
/// mode. Drives `compute_tx_type`'s `Mode_To_Txfm[UVMode]` lookup
/// on the chroma path. `DC_PRED = 0` for the walker's intra-only
/// path until the §5.11.7 reader's chroma arm lands.
pub uv_mode: u8,
/// `true` when the block is intra-predicted (`is_inter == 0` and
/// not `use_intrabc`). Gates the §7.11.2.1
/// [`PartitionWalker::predict_intra_into_curr_frame`] prediction
/// write that precedes the §7.12.3 step-3 residual merge — the
/// inter / IntraBC arms produce their prediction through the
/// §7.11.3 motion-compensation path instead.
pub is_intra: bool,
/// `YMode` per §5.11.7 / §5.11.22 — the luma intra-prediction mode
/// (`DC_PRED .. PAETH_PRED`). Distinct from `intra_dir`, which is
/// the §8.3.2 tx-type `intraDir` (filter-intra remapped). Drives
/// the luma-plane §7.11.2.1 prediction write.
pub y_mode: u8,
/// `AngleDeltaY` per §5.11.42 — the signed `[-3, 3]` directional
/// angle delta applied to a directional `YMode` (`D45_PRED ..
/// D67_PRED`). `0` for non-directional modes.
pub angle_delta_y: i8,
/// `AngleDeltaUV` per §5.11.42 — the signed `[-3, 3]` directional
/// angle delta applied to a directional `UVMode`. `0` for
/// non-directional modes / no chroma.
pub angle_delta_uv: i8,
/// `CflAlphaU` per §5.11.45 (av1-spec p.97) — the signed
/// chroma-from-luma alpha for plane 1 (`signU * (cfl_alpha_u +
/// 1)`, in `[-16, 16]`). Read by the §7.11.5
/// `predict_chroma_from_luma` scale (`Round2Signed(alpha * (L - lumaAvg),
/// 6)`). `0` when `UVMode != UV_CFL_PRED` (no CfL block) — the §7.11.5
/// process is only invoked on the `isCfl` arm, so the value is
/// inert otherwise.
pub cfl_alpha_u: i8,
/// `CflAlphaV` per §5.11.45 — the signed chroma-from-luma alpha for
/// plane 2 (`signV * (cfl_alpha_v + 1)`, in `[-16, 16]`). See
/// [`Self::cfl_alpha_u`].
pub cfl_alpha_v: i8,
/// `filter_intra_mode` per §5.11.24 (av1-spec p.92) — `Some(mode)`
/// (`mode ∈ 0..INTRA_FILTER_MODES`) when the block's luma plane uses
/// the §7.11.2.3 recursive intra prediction (`use_filter_intra ==
/// 1`); `None` otherwise. Gates the §7.11.2.1 first dispatch arm in
/// [`PartitionWalker::predict_intra_into_curr_frame`]. Luma-only —
/// the chroma planes never run filter-intra.
pub filter_intra_mode: Option<u8>,
/// `AvailL` per §5.11.5 — `true` if the block has a decoded
/// neighbour to its left. With the §5.11.34 chunk-local `x > 0`
/// it forms the §7.11.2.1 `haveLeft` input (`AvailL || x > 0`) for
/// the luma plane.
pub avail_l: bool,
/// `AvailU` per §5.11.5 — `true` if the block has a decoded
/// neighbour above. Forms `haveAbove` (`AvailU || y > 0`) for luma.
pub avail_u: bool,
/// `AvailLChroma` per §5.11.5 — the chroma-plane `haveLeft` base.
pub avail_l_chroma: bool,
/// `AvailUChroma` per §5.11.5 — the chroma-plane `haveAbove` base.
pub avail_u_chroma: bool,
/// `enable_intra_edge_filter` per §5.5.2 (sequence header). Gates
/// the §7.11.2.4 step-4 directional pre-pass: the §7.11.2.7 filter
/// corner, §7.11.2.9/§7.11.2.12 intra edge filter, and
/// §7.11.2.10/§7.11.2.11 intra edge upsample applied to `AboveRow[]`
/// / `LeftCol[]` before the directional kernel runs. When `false`
/// the directional prediction reads the unfiltered, un-upsampled
/// edges (`upsampleAbove = upsampleLeft = 0`, no filter).
pub enable_intra_edge_filter: bool,
}
impl ResidualContext {
/// Neutral / DC-only `ResidualContext` — `base_q_idx` configurable,
/// every other slot at its spec default (no QM, no segmentation,
/// `UVMode = DC_PRED`). Useful for the §5.11.5 walker's reachable
/// path and for tests.
#[must_use]
pub const fn neutral(base_q_idx: u8, bit_depth: u8) -> Self {
Self {
quant: QuantizerParams::neutral(base_q_idx, bit_depth),
reduced_tx_set: false,
intra_dir: 0,
segment_id: 0,
seg_qm_level: [15, 15, 15],
uv_mode: 0,
is_intra: true,
y_mode: 0,
angle_delta_y: 0,
angle_delta_uv: 0,
cfl_alpha_u: 0,
cfl_alpha_v: 0,
filter_intra_mode: None,
avail_l: false,
avail_u: false,
avail_l_chroma: false,
avail_u_chroma: false,
enable_intra_edge_filter: false,
}
}
}
/// §5.11.47 `transform_type( x4, y4, txSz )` outcome — the decoded
/// luma `TxType` ordinal plus the `TxTypes[ y4 + j ][ x4 + i ]`
/// grid-stamp footprint the caller writes back.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TransformTypeReadout {
/// The decoded `TxType` ordinal (`DCT_DCT..H_FLIPADST`).
pub tx_type: u8,
/// `Tx_Width[ txSz ] >> 2` — the number of 4×4 luma columns the
/// caller stamps `TxTypes[ y4 + j ][ x4 + i ] = TxType` over.
pub w4: u8,
/// `Tx_Height[ txSz ] >> 2` — the number of 4×4 luma rows the
/// caller stamps over.
pub h4: u8,
}
/// `Tx_Size_Sqr` ordinal index. Used to look up the §8.3.2
/// `intra_tx_type` / `inter_tx_type` CDF row.
#[inline]
pub fn tx_size_sqr_index(tx_size: usize) -> usize {
// `Tx_Size_Sqr` per §3 maps each `TX_SIZES_ALL` ordinal to a
// square `TX_4X4..TX_64X64` ordinal (matching
// `Min( Tx_Width, Tx_Height )` with the log2(side) - 2 rule).
// The square `TX_NxN` ordinal is `log2(side) - 2` because
// `TX_4X4 = 0`, `TX_8X8 = 1`, `TX_16X16 = 2`, `TX_32X32 = 3`,
// `TX_64X64 = 4`.
let side = core::cmp::min(TX_WIDTH[tx_size], TX_HEIGHT[tx_size]);
(side.trailing_zeros() as usize) - 2
}
impl PartitionWalker {
/// r464 — import the per-mi grids of a `h4 × w4` rect from
/// `src` (at `src_row`, `src_col`) into this walker at `dst_row`,
/// `dst_col`: every per-cell grid, the `TxTypes[]` / `cdef_idx[]`
/// cells, the per-column above and per-row left coefficient /
/// segment contexts of the rect's span, and the decoded-block
/// records. The encoder's tile-parallel search walks each tile on
/// a TILE-SIZED walker (the tile at the origin of its own frame —
/// every §5.11 context and availability is tile-scoped, so the
/// walk is the frame walk's) and stitches the result back here.
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn import_rect_from(
&mut self,
src: &Self,
src_row: u32,
src_col: u32,
dst_row: u32,
dst_col: u32,
h4: u32,
w4: u32,
subsampling_x: u8,
subsampling_y: u8,
) {
let h4 = h4
.min(src.mi_rows - src_row.min(src.mi_rows))
.min(self.mi_rows - dst_row.min(self.mi_rows));
let w4 = w4
.min(src.mi_cols - src_col.min(src.mi_cols))
.min(self.mi_cols - dst_col.min(self.mi_cols));
let (sa, da) = (
(src.mi_rows as usize) * (src.mi_cols as usize),
(self.mi_rows as usize) * (self.mi_cols as usize),
);
if !src.palette_colors.is_empty() {
self.ensure_palette_colors();
}
for rr in 0..h4 {
let sbase = ((src_row + rr) * src.mi_cols + src_col) as usize;
let dbase = ((dst_row + rr) * self.mi_cols + dst_col) as usize;
let n = w4 as usize;
macro_rules! copy_cells {
($field:ident, $mult:expr) => {
self.$field[dbase * $mult..(dbase + n) * $mult]
.copy_from_slice(&src.$field[sbase * $mult..(sbase + n) * $mult]);
};
}
copy_cells!(mi_sizes, 1);
copy_cells!(skips, 1);
copy_cells!(skip_modes, 1);
copy_cells!(delta_lfs, FRAME_LF_COUNT);
copy_cells!(cdef_idx, 1);
copy_cells!(is_inters, 1);
copy_cells!(segment_ids, 1);
copy_cells!(y_modes, 1);
copy_cells!(uv_modes, 1);
copy_cells!(tx_sizes, 1);
copy_cells!(inter_tx_sizes, 1);
copy_cells!(tx_types, 1);
copy_cells!(ref_frames, 2);
copy_cells!(mvs, 4);
copy_cells!(comp_group_idxs, 1);
copy_cells!(compound_idxs, 1);
copy_cells!(interp_filters, 2);
copy_cells!(compound_types, 1);
copy_cells!(compound_wedge_indices, 1);
copy_cells!(compound_wedge_signs, 1);
copy_cells!(compound_mask_types, 1);
copy_cells!(interintra_modes, 1);
copy_cells!(wedge_interintras, 1);
copy_cells!(interintra_wedge_indices, 1);
copy_cells!(motion_modes, 1);
if !src.local_warp_params.is_empty() {
self.ensure_local_warp_params();
copy_cells!(local_warp_params, 6);
}
copy_cells!(local_warp_valid, 1);
for plane in 0..3usize {
let (sp, dp) = (plane * sa + sbase, plane * da + dbase);
self.palette_sizes[dp..dp + n].copy_from_slice(&src.palette_sizes[sp..sp + n]);
if !src.palette_colors.is_empty() {
self.palette_colors[dp * PALETTE_COLORS..(dp + n) * PALETTE_COLORS]
.copy_from_slice(
&src.palette_colors[sp * PALETTE_COLORS..(sp + n) * PALETTE_COLORS],
);
}
}
}
// Per-column above / per-row left contexts of the rect's span
// (three planes, the chroma planes at their subsampled index).
for plane in 0..3usize {
let (ssx, ssy) = if plane == 0 {
(0u32, 0u32)
} else {
(u32::from(subsampling_x), u32::from(subsampling_y))
};
let (sab, dab) = (plane * src.mi_cols as usize, plane * self.mi_cols as usize);
let (slb, dlb) = (plane * src.mi_rows as usize, plane * self.mi_rows as usize);
let c_n = ((w4 + ssx) >> ssx) as usize;
let r_n = ((h4 + ssy) >> ssy) as usize;
let (sc0, dc0) = ((src_col >> ssx) as usize, (dst_col >> ssx) as usize);
let (sr0, dr0) = ((src_row >> ssy) as usize, (dst_row >> ssy) as usize);
let c_n = c_n
.min(src.mi_cols as usize - sc0.min(src.mi_cols as usize))
.min(self.mi_cols as usize - dc0.min(self.mi_cols as usize));
let r_n = r_n
.min(src.mi_rows as usize - sr0.min(src.mi_rows as usize))
.min(self.mi_rows as usize - dr0.min(self.mi_rows as usize));
self.above_level_context[dab + dc0..dab + dc0 + c_n]
.copy_from_slice(&src.above_level_context[sab + sc0..sab + sc0 + c_n]);
self.above_dc_context[dab + dc0..dab + dc0 + c_n]
.copy_from_slice(&src.above_dc_context[sab + sc0..sab + sc0 + c_n]);
self.left_level_context[dlb + dr0..dlb + dr0 + r_n]
.copy_from_slice(&src.left_level_context[slb + sr0..slb + sr0 + r_n]);
self.left_dc_context[dlb + dr0..dlb + dr0 + r_n]
.copy_from_slice(&src.left_dc_context[slb + sr0..slb + sr0 + r_n]);
}
{
let (sc, dc, n) = (src_col as usize, dst_col as usize, w4 as usize);
self.above_seg_pred_context[dc..dc + n]
.copy_from_slice(&src.above_seg_pred_context[sc..sc + n]);
let (sr, dr, n) = (src_row as usize, dst_row as usize, h4 as usize);
self.left_seg_pred_context[dr..dr + n]
.copy_from_slice(&src.left_seg_pred_context[sr..sr + n]);
}
for b in &src.blocks {
if b.mi_row >= src_row
&& b.mi_row < src_row + h4
&& b.mi_col >= src_col
&& b.mi_col < src_col + w4
{
self.blocks.push(DecodedBlockRecord {
mi_row: b.mi_row - src_row + dst_row,
mi_col: b.mi_col - src_col + dst_col,
sub_size: b.sub_size,
});
}
}
}
}
impl PartitionWalker {
/// r464 — diagnostic: the names of the fields on which `self` and
/// `other` differ (empty when equal).
#[doc(hidden)]
pub fn differing_fields(&self, other: &Self) -> Vec<&'static str> {
let mut out = Vec::new();
// The palette-colour and local-warp grids are materialised
// lazily: an empty grid equals an all-zero one.
let pal_eq_i32 = |a: &[i32], b: &[i32]| -> bool {
if a.len() == b.len() {
a == b
} else {
let (short, long) = if a.is_empty() { (a, b) } else { (b, a) };
short.is_empty() && long.iter().all(|&v| v == 0)
}
};
let pal_eq = |a: &[u16], b: &[u16]| -> bool {
if a.len() == b.len() {
a == b
} else {
let (short, long) = if a.is_empty() { (a, b) } else { (b, a) };
short.is_empty() && long.iter().all(|&v| v == 0)
}
};
if self.mi_rows != other.mi_rows {
out.push("mi_rows");
}
if self.mi_cols != other.mi_cols {
out.push("mi_cols");
}
if self.geometry != other.geometry {
out.push("geometry");
}
if self.enable_intra_edge_filter != other.enable_intra_edge_filter {
out.push("enable_intra_edge_filter");
}
if self.walk_avail_u != other.walk_avail_u {
out.push("walk_avail_u");
}
if self.walk_avail_l != other.walk_avail_l {
out.push("walk_avail_l");
}
if self.inter_pred_scratch != other.inter_pred_scratch {
out.push("inter_pred_scratch");
}
if !pal_eq_i32(&self.local_warp_params, &other.local_warp_params) {
out.push("local_warp_params");
}
if self.local_warp_valid != other.local_warp_valid {
out.push("local_warp_valid");
}
if self.mi_sizes != other.mi_sizes {
out.push("mi_sizes");
}
if self.skips != other.skips {
out.push("skips");
}
if self.skip_modes != other.skip_modes {
out.push("skip_modes");
}
if self.current_q_index != other.current_q_index {
out.push("current_q_index");
}
if self.read_deltas_pending != other.read_deltas_pending {
out.push("read_deltas_pending");
}
if self.current_delta_lf != other.current_delta_lf {
out.push("current_delta_lf");
}
if self.delta_lfs != other.delta_lfs {
out.push("delta_lfs");
}
if self.cdef_idx != other.cdef_idx {
out.push("cdef_idx");
}
if self.is_inters != other.is_inters {
out.push("is_inters");
}
if self.segment_ids != other.segment_ids {
out.push("segment_ids");
}
if self.above_seg_pred_context != other.above_seg_pred_context {
out.push("above_seg_pred_context");
}
if self.left_seg_pred_context != other.left_seg_pred_context {
out.push("left_seg_pred_context");
}
if self.above_level_context != other.above_level_context {
out.push("above_level_context");
}
if self.above_dc_context != other.above_dc_context {
out.push("above_dc_context");
}
if self.left_level_context != other.left_level_context {
out.push("left_level_context");
}
if self.left_dc_context != other.left_dc_context {
out.push("left_dc_context");
}
if self.y_modes != other.y_modes {
out.push("y_modes");
}
if self.uv_modes != other.uv_modes {
out.push("uv_modes");
}
if self.tx_sizes != other.tx_sizes {
out.push("tx_sizes");
}
if self.inter_tx_sizes != other.inter_tx_sizes {
out.push("inter_tx_sizes");
}
if self.tx_types != other.tx_types {
out.push("tx_types");
}
if self.ref_frames != other.ref_frames {
out.push("ref_frames");
}
if self.palette_sizes != other.palette_sizes {
out.push("palette_sizes");
}
if !pal_eq(&self.palette_colors, &other.palette_colors) {
out.push("palette_colors");
}
if self.mvs != other.mvs {
out.push("mvs");
}
if self.comp_group_idxs != other.comp_group_idxs {
out.push("comp_group_idxs");
}
if self.compound_idxs != other.compound_idxs {
out.push("compound_idxs");
}
if self.interp_filters != other.interp_filters {
out.push("interp_filters");
}
if self.compound_types != other.compound_types {
out.push("compound_types");
}
if self.compound_wedge_indices != other.compound_wedge_indices {
out.push("compound_wedge_indices");
}
if self.compound_wedge_signs != other.compound_wedge_signs {
out.push("compound_wedge_signs");
}
if self.compound_mask_types != other.compound_mask_types {
out.push("compound_mask_types");
}
if self.interintra_modes != other.interintra_modes {
out.push("interintra_modes");
}
if self.wedge_interintras != other.wedge_interintras {
out.push("wedge_interintras");
}
if self.interintra_wedge_indices != other.interintra_wedge_indices {
out.push("interintra_wedge_indices");
}
if self.motion_modes != other.motion_modes {
out.push("motion_modes");
}
if self.blocks != other.blocks {
out.push("blocks");
}
if self.lr_origin != other.lr_origin {
out.push("lr_origin");
}
if self.curr_frame != other.curr_frame {
out.push("curr_frame");
}
if self.luma_overhang != other.luma_overhang {
out.push("luma_overhang");
}
if self.ref_lr_wiener != other.ref_lr_wiener {
out.push("ref_lr_wiener");
}
if self.ref_sgr_xqd != other.ref_sgr_xqd {
out.push("ref_sgr_xqd");
}
if self.lr_units != other.lr_units {
out.push("lr_units");
}
if self.block_decoded != other.block_decoded {
out.push("block_decoded");
}
if self.max_luma_w != other.max_luma_w {
out.push("max_luma_w");
}
if self.max_luma_h != other.max_luma_h {
out.push("max_luma_h");
}
if self.current_palette_size_y != other.current_palette_size_y {
out.push("current_palette_size_y");
}
if self.current_palette_size_uv != other.current_palette_size_uv {
out.push("current_palette_size_uv");
}
if self.current_color_map_y != other.current_color_map_y {
out.push("current_color_map_y");
}
if self.current_color_map_uv != other.current_color_map_uv {
out.push("current_color_map_uv");
}
out
}
}
impl PartitionWalker {
/// `transform_type( x4, y4, txSz )` per §5.11.47 (av1-spec p.99-100).
/// Decodes the per-luma-TU `TxType` from the bitstream and emits
/// the value the caller stamps into `TxTypes[ y4 + j ][ x4 + i ]`.
///
/// The spec body reads:
///
/// ```text
/// set = get_tx_set( txSz )
/// if ( set > 0 && (segmentation_enabled ? get_qindex(1, segment_id) : base_q_idx) > 0 ) {
/// if ( is_inter ) {
/// inter_tx_type S()
/// TxType = Tx_Type_Inter_Inv_Set{1,2,3}[ inter_tx_type ]
/// } else {
/// intra_tx_type S()
/// TxType = Tx_Type_Intra_Inv_Set{1,2}[ intra_tx_type ]
/// }
/// } else {
/// TxType = DCT_DCT
/// }
/// for ( i = 0; i < (Tx_Width[txSz] >> 2); i++ )
/// for ( j = 0; j < (Tx_Height[txSz] >> 2); j++ )
/// TxTypes[ y4 + j ][ x4 + i ] = TxType
/// ```
///
/// The `set > 0 && ((segmentation_enabled ? get_qindex(1,
/// segment_id) : base_q_idx) > 0)` guard short-circuits to
/// `DCT_DCT` on the §5.11.48 `TX_SET_DCTONLY` path AND on the
/// "qindex is zero ⇒ no transform-type symbol" path; the spec
/// inversion tables [`TX_TYPE_INTRA_INV_SET1`] /
/// [`TX_TYPE_INTRA_INV_SET2`] / [`TX_TYPE_INTER_INV_SET1`] /
/// [`TX_TYPE_INTER_INV_SET2`] / [`TX_TYPE_INTER_INV_SET3`] map
/// the per-set symbol ordinal back into the §3 `TxType`
/// enumeration.
///
/// `intra_dir` is the §8.3.2 [`intra_dir`] result (the per-plane
/// `intraDir` axis the §8.3.2 `intra_tx_type` CDF uses); for
/// inter blocks the axis is unused.
///
/// The caller is responsible for writing the returned
/// `tx_type` over the `(w4, h4)` 4×4-luma footprint of the TU into
/// the walker's `TxTypes[][]` grid (use [`Self::stamp_tx_type`]).
#[allow(clippy::too_many_arguments)]
pub fn transform_type(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
tx_size: usize,
is_inter: bool,
intra_dir: usize,
reduced_tx_set: bool,
segment_id: u8,
quant: &QuantizerParams,
) -> Result<TransformTypeReadout, crate::Error> {
if tx_size >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.48 `get_tx_set( txSz )`. Compute `Tx_Size_Sqr[ txSz ]`
// and `Tx_Size_Sqr_Up[ txSz ]` per §3.
let tx_sz_sqr = tx_size_sqr_index(tx_size);
let tx_sz_sqr_up = TX_SIZE_SQR_UP[tx_size];
let set = if is_inter {
inter_tx_type_set(tx_sz_sqr as u32, tx_sz_sqr_up as u32, reduced_tx_set)
} else {
intra_tx_type_set(tx_sz_sqr as u32, tx_sz_sqr_up as u32, reduced_tx_set)
};
// §5.11.47 `(segmentation_enabled ? get_qindex(1, segment_id)
// : base_q_idx) > 0` guard — `ignoreDeltaQ = 1` in the §7.12.2
// qindex lookup (delta-q is intentionally ignored here).
let q_for_guard = if quant.segmentation_enabled {
get_qindex(quant, true, segment_id)
} else {
quant.base_q_idx as i32
};
let codes_symbol = set > 0 && q_for_guard > 0;
let tx_type: u8 = if !codes_symbol {
DCT_DCT as u8
} else if is_inter {
let sqr = tx_size_sqr_index(tx_size);
let cdf = cdfs.inter_tx_type_cdf(set, sqr as u32).ok_or(
// Should never happen on the conformant path —
// `inter_tx_type_set` returns one of {DCTONLY, 1, 2,
// 3}, and `inter_tx_type_cdf` only returns `None` for
// DCTONLY (which the `codes_symbol == false` guard
// above catches first).
crate::Error::PartitionWalkOutOfRange,
)?;
let raw = decoder
.read_symbol(cdf)
.map_err(|_| crate::Error::UnexpectedEnd)? as usize;
let table: &[usize] = match set {
TX_SET_INTER_1 => &TX_TYPE_INTER_INV_SET1,
TX_SET_INTER_2 => &TX_TYPE_INTER_INV_SET2,
TX_SET_INTER_3 => &TX_TYPE_INTER_INV_SET3,
_ => return Err(crate::Error::PartitionWalkOutOfRange),
};
// Symbol values must index inside the inversion table —
// a conformant bitstream stays in `0..table.len()`.
if raw >= table.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
table[raw] as u8
} else {
let sqr = tx_size_sqr_index(tx_size);
let cdf = cdfs
.intra_tx_type_cdf(set, sqr as u32, intra_dir)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let raw = decoder
.read_symbol(cdf)
.map_err(|_| crate::Error::UnexpectedEnd)? as usize;
let table: &[usize] = match set {
TX_SET_INTRA_1 => &TX_TYPE_INTRA_INV_SET1,
TX_SET_INTRA_2 => &TX_TYPE_INTRA_INV_SET2,
_ => return Err(crate::Error::PartitionWalkOutOfRange),
};
if raw >= table.len() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
table[raw] as u8
};
let w4 = (TX_WIDTH[tx_size] >> 2) as u8;
let h4 = (TX_HEIGHT[tx_size] >> 2) as u8;
Ok(TransformTypeReadout { tx_type, w4, h4 })
}
/// Stamp `TxTypes[ y4 + j ][ x4 + i ] = tx_type` over a
/// `(w4, h4)` 4×4-luma-sample-unit footprint anchored at
/// `(x4, y4)`. Used by §5.11.39 (`all_zero == 1` ⇒ `DCT_DCT`
/// fill), §5.11.47 (decoded `TxType` fill), and on the §5.11.40
/// `compute_tx_type()` chroma path the §5.11.39 `all_zero` arm
/// pre-stamps for.
///
/// `x4` / `y4` are in 4×4-luma units (i.e. mi-units with
/// `MI_SIZE_LOG2 == 2`). Cells outside the `mi_rows * mi_cols`
/// grid bound are silently dropped (matches the §5.11.39 spec
/// behaviour for a TU clipped against the bottom/right frame
/// edge — those cells are unreachable for any subsequent
/// `TxTypes[][]` read).
pub fn stamp_tx_type(&mut self, x4: u32, y4: u32, w4: u32, h4: u32, tx_type: u8) {
for j in 0..h4 {
let row = y4 + j;
if row >= self.mi_rows {
break;
}
for i in 0..w4 {
let col = x4 + i;
if col >= self.mi_cols {
break;
}
let idx = (row * self.mi_cols + col) as usize;
self.tx_types[idx] = tx_type;
}
}
}
/// Read `TxTypes[ y4 ][ x4 ]` from the walker's grid. Returns the
/// `DCT_DCT = 0` pre-fill for cells the §5.11.39 / §5.11.47
/// readers have not yet stamped.
#[inline]
pub fn tx_type_at(&self, y4: i32, x4: i32) -> u8 {
if y4 < 0 || x4 < 0 || (y4 as u32) >= self.mi_rows || (x4 as u32) >= self.mi_cols {
return DCT_DCT as u8;
}
self.tx_types[(y4 as u32 * self.mi_cols + x4 as u32) as usize]
}
}
/// §8.3.2 `get_tx_class( txType )` — maps a transform type to its
/// transform class. The vertical-only types (`V_DCT` / `V_ADST` /
/// `V_FLIPADST`) return [`TX_CLASS_VERT`]; the horizontal-only types
/// (`H_DCT` / `H_ADST` / `H_FLIPADST`) return [`TX_CLASS_HORIZ`]; every
/// other transform type returns [`TX_CLASS_2D`].
///
/// The transform-type enumeration values are the §3 `*_DCT` / `*_ADST`
/// / `*_FLIPADST` constants; `v_dct` / `v_adst` / `v_flipadst` and
/// `h_dct` / `h_adst` / `h_flipadst` flag the directional types. The
/// full §8.3.2 `compute_tx_type()` derivation that produces `txType`
/// from the bitstream belongs to the tile-content walk; this helper
/// performs only the class reduction.
pub fn get_tx_class(
v_dct: bool,
v_adst: bool,
v_flipadst: bool,
h_dct: bool,
h_adst: bool,
h_flipadst: bool,
) -> usize {
if v_dct || v_adst || v_flipadst {
TX_CLASS_VERT
} else if h_dct || h_adst || h_flipadst {
TX_CLASS_HORIZ
} else {
TX_CLASS_2D
}
}
/// §8.3.2 `get_coeff_base_ctx( txSz, plane, blockX, blockY, pos, c,
/// isEob )` — the `coeff_base` / `coeff_base_eob` neighbour-derivation.
///
/// ```text
/// adjTxSz = Adjusted_Tx_Size[ txSz ]
/// bwl = Tx_Width_Log2[ adjTxSz ]
/// width = 1 << bwl
/// height = Tx_Height[ adjTxSz ]
/// if ( isEob ) { // EOB-position buckets
/// if ( c == 0 ) return SIG_COEF_CONTEXTS - 4
/// if ( c <= (height<<bwl)/8 ) return SIG_COEF_CONTEXTS - 3
/// if ( c <= (height<<bwl)/4 ) return SIG_COEF_CONTEXTS - 2
/// return SIG_COEF_CONTEXTS - 1
/// }
/// row = pos >> bwl ; col = pos - (row << bwl)
/// mag = sum over SIG_REF_DIFF_OFFSET_NUM neighbours of
/// Min( Abs( Quant[(refRow<<bwl)+refCol] ), 3 ) // in-bounds only
/// ctx = Min( (mag + 1) >> 1, 4 )
/// if ( txClass == TX_CLASS_2D ) {
/// if ( row == 0 && col == 0 ) return 0
/// return ctx + Coeff_Base_Ctx_Offset[ txSz ][ Min(row,4) ][ Min(col,4) ]
/// }
/// idx = (txClass == TX_CLASS_VERT) ? row : col
/// return ctx + Coeff_Base_Pos_Ctx_Offset[ Min(idx, 2) ]
/// ```
///
/// The neighbour scan uses [`SIG_REF_DIFF_OFFSET`] with the bound check
/// `refRow < height && refCol < width` (`width = 1 << bwl`); the
/// `Coeff_Base_Ctx_Offset` lookup uses the *original* `tx_size`, not the
/// adjusted size. `quant` is the coefficient-magnitude array (`Quant`)
/// indexed `(refRow << bwl) + refCol`; `tx_class` is the
/// [`get_tx_class`] result supplied by the caller (the
/// `compute_tx_type()` derivation belongs to the tile walk); `pos` is
/// the scan position `scan[c]`; `c` is the scan index; `is_eob` selects
/// the EOB-position buckets.
///
/// Returns a value in `0..SIG_COEF_CONTEXTS` (the EOB buckets occupy the
/// top four). The `coeff_base_eob` selector applies
/// `- SIG_COEF_CONTEXTS + SIG_COEF_CONTEXTS_EOB` to map onto
/// `0..SIG_COEF_CONTEXTS_EOB` — see [`get_coeff_base_eob_ctx`].
pub fn get_coeff_base_ctx(
quant: &[i32],
tx_size: usize,
tx_class: usize,
pos: usize,
c: usize,
is_eob: bool,
) -> usize {
let adj_tx_sz = ADJUSTED_TX_SIZE[tx_size];
let bwl = TX_WIDTH_LOG2[adj_tx_sz];
let width = 1usize << bwl;
let height = TX_HEIGHT[adj_tx_sz];
if is_eob {
if c == 0 {
return SIG_COEF_CONTEXTS - 4;
}
if c <= (height << bwl) / 8 {
return SIG_COEF_CONTEXTS - 3;
}
if c <= (height << bwl) / 4 {
return SIG_COEF_CONTEXTS - 2;
}
return SIG_COEF_CONTEXTS - 1;
}
let row = pos >> bwl;
let col = pos - (row << bwl);
// Accumulate the neighbour magnitude over the SIG_REF_DIFF_OFFSET_NUM
// offsets, clamping each in-bounds neighbour magnitude to 3.
let mut mag: i32 = 0;
for offset in &SIG_REF_DIFF_OFFSET[tx_class] {
let ref_row = row + offset[0];
let ref_col = col + offset[1];
// The offsets are non-negative, so refRow / refCol >= 0 holds by
// construction; only the upper bound needs checking.
if ref_row < height && ref_col < width {
let q = quant[(ref_row << bwl) + ref_col].unsigned_abs() as i32;
mag += q.min(3);
}
}
let ctx = (((mag + 1) >> 1).min(4)) as usize;
if tx_class == TX_CLASS_2D {
if row == 0 && col == 0 {
return 0;
}
return ctx + COEFF_BASE_CTX_OFFSET[tx_size][row.min(4)][col.min(4)];
}
let idx = if tx_class == TX_CLASS_VERT { row } else { col };
ctx + COEFF_BASE_POS_CTX_OFFSET[idx.min(2)]
}
/// §8.3.2 `coeff_base_eob` context: the [`get_coeff_base_ctx`] result
/// with `is_eob = true`, reduced onto `0..SIG_COEF_CONTEXTS_EOB` via
/// `ctx - SIG_COEF_CONTEXTS + SIG_COEF_CONTEXTS_EOB`. The result is the
/// `ctx` index consumed by [`TileCdfContext::coeff_base_eob_cdf`].
pub fn get_coeff_base_eob_ctx(
quant: &[i32],
tx_size: usize,
tx_class: usize,
pos: usize,
c: usize,
) -> usize {
// The §8.3.2 reduction is `ctx - SIG_COEF_CONTEXTS + SIG_COEF_CONTEXTS_EOB`
// in signed arithmetic. The isEob path returns a value in
// `SIG_COEF_CONTEXTS-4 ..= SIG_COEF_CONTEXTS-1`, so adding the EOB
// context count first keeps the intermediate non-negative for usize.
(get_coeff_base_ctx(quant, tx_size, tx_class, pos, c, true) + SIG_COEF_CONTEXTS_EOB)
- SIG_COEF_CONTEXTS
}
/// §8.3.2 `get_br_ctx` — the `coeff_br` neighbour-derivation.
///
/// ```text
/// adjTxSz = Adjusted_Tx_Size[ txSz ]
/// bwl = Tx_Width_Log2[ adjTxSz ] ; txw = Tx_Width[ adjTxSz ]
/// txh = Tx_Height[ adjTxSz ]
/// row = pos >> bwl ; col = pos - (row << bwl)
/// mag = sum over 3 neighbours of
/// Min( Quant[refRow*txw+refCol], COEFF_BASE_RANGE+NUM_BASE_LEVELS+1 )
/// mag = Min( (mag + 1) >> 1, 6 )
/// if ( pos == 0 ) ctx = mag
/// else if ( txClass == 0 ) ctx = mag + (row<2 && col<2 ? 7 : 14)
/// else if ( txClass == 1 ) ctx = mag + (col==0 ? 7 : 14)
/// else ctx = mag + (row==0 ? 7 : 14)
/// ```
///
/// The neighbour scan uses [`MAG_REF_OFFSET_WITH_TX_CLASS`] (three
/// offsets) with the bound check `refRow < txh && refCol < (1 << bwl)`.
/// Note the magnitude is `Quant[..]` directly (not its absolute value)
/// and the clamp is `COEFF_BASE_RANGE + NUM_BASE_LEVELS + 1`, distinct
/// from the `get_coeff_base_ctx()` scan. `quant` is the
/// coefficient-magnitude array indexed `refRow * txw + refCol`;
/// `tx_class` is the [`get_tx_class`] result supplied by the caller;
/// `pos` is the scan position.
///
/// Returns a value in `0..LEVEL_CONTEXTS`, the `ctx` index consumed by
/// [`TileCdfContext::coeff_br_cdf`].
pub fn get_br_ctx(quant: &[i32], tx_size: usize, tx_class: usize, pos: usize) -> usize {
let adj_tx_sz = ADJUSTED_TX_SIZE[tx_size];
let bwl = TX_WIDTH_LOG2[adj_tx_sz];
let txw = TX_WIDTH[adj_tx_sz];
let txh = TX_HEIGHT[adj_tx_sz];
let row = pos >> bwl;
let col = pos - (row << bwl);
let clamp = (COEFF_BASE_RANGE + NUM_BASE_LEVELS + 1) as i32;
let mut mag: i32 = 0;
for offset in &MAG_REF_OFFSET_WITH_TX_CLASS[tx_class] {
let ref_row = row + offset[0];
let ref_col = col + offset[1];
if ref_row < txh && ref_col < (1usize << bwl) {
mag += quant[ref_row * txw + ref_col].min(clamp);
}
}
let mag = (((mag + 1) >> 1).min(6)) as usize;
if pos == 0 {
mag
} else if tx_class == TX_CLASS_2D {
if row < 2 && col < 2 {
mag + 7
} else {
mag + 14
}
} else if tx_class == TX_CLASS_HORIZ {
if col == 0 {
mag + 7
} else {
mag + 14
}
} else if row == 0 {
mag + 7
} else {
mag + 14
}
}
// ---------------------------------------------------------------------
// Round 143 — inter-intra group (§9.4): `Default_Inter_Intra_Cdf`,
// `Default_Inter_Intra_Mode_Cdf`, `Default_Wedge_Inter_Intra_Cdf`. All
// three CDFs are read by the §5.11.28 `read_interintra_mode` syntax
// inside the inter-block mode-info parse (the `interintra`,
// `interintra_mode`, and `wedge_interintra` elements). §6.10.27 enumerates
// `INTERINTRA_MODES = 4` values for `interintra_mode` (`II_DC_PRED`,
// `II_V_PRED`, `II_H_PRED`, `II_SMOOTH_PRED`).
//
// §8.3.2 selection for `interintra` / `interintra_mode` is
// `ctx = Size_Group[ MiSize ] - 1`; the §5.11.28 gate confines coded
// blocks to the `BLOCK_8X8`..`BLOCK_32X32` band, so the resulting `ctx`
// is one of `{0, 1, 2}` — matching the `[ BLOCK_SIZE_GROUPS - 1 ]` outer
// dimension. The §8.3.2 `wedge_interintra` selection is a straight
// `TileWedgeInterIntraCdf[ MiSize ]` index; per the §9.4 note only block
// sizes 3..=9 (the BLOCK_8X8..BLOCK_32X32 band) are reachable, but the
// table is transcribed full-width to keep the indexing uniform with
// every other `[ BLOCK_SIZES ]` table.
// ---------------------------------------------------------------------
/// `Default_Inter_Intra_Cdf[ BLOCK_SIZE_GROUPS - 1 ][ 3 ]` (§9.4). Binary
/// symbol `interintra` (§5.11.28 `read_interintra_mode` gate; selects
/// whether an inter prediction should be blended with an intra prediction,
/// per §6.10.27 semantics). The §8.3.2 selection computes
/// `ctx = Size_Group[ MiSize ] - 1` (the spec's literal text), valid in
/// `0..(BLOCK_SIZE_GROUPS - 1)`. Each row carries two cumulative
/// frequencies plus the §8.3 adaptation counter (starts at 0).
pub const DEFAULT_INTER_INTRA_CDF: [[u16; 3]; BLOCK_SIZE_GROUPS - 1] =
[[26887, 32768, 0], [27597, 32768, 0], [30237, 32768, 0]];
/// `Default_Inter_Intra_Mode_Cdf[ BLOCK_SIZE_GROUPS - 1 ][ INTERINTRA_MODES + 1 ]`
/// (§9.4). Codes `interintra_mode ∈ { II_DC_PRED, II_V_PRED, II_H_PRED,
/// II_SMOOTH_PRED }` (§6.10.27 enumeration). The §8.3.2 selection
/// computes `ctx = Size_Group[ MiSize ] - 1`, valid in
/// `0..(BLOCK_SIZE_GROUPS - 1)`. Each row carries
/// `INTERINTRA_MODES = 4` cumulative frequencies plus the §8.3 adaptation
/// counter (starts at 0).
pub const DEFAULT_INTER_INTRA_MODE_CDF: [[u16; INTERINTRA_MODES + 1]; BLOCK_SIZE_GROUPS - 1] = [
[1875, 11082, 27332, 32768, 0],
[2473, 9996, 26388, 32768, 0],
[4238, 11537, 25926, 32768, 0],
];
/// `Default_Wedge_Inter_Intra_Cdf[ BLOCK_SIZES ][ 3 ]` (§9.4). Binary
/// symbol `wedge_interintra` (§5.11.28; §6.10.27 selects whether wedge
/// blending should be used). The §8.3.2 selection is a straight
/// `TileWedgeInterIntraCdf[ MiSize ]` index. Per the §9.4 note only
/// indices 3..=9 (the `BLOCK_8X8`..`BLOCK_32X32` band — the same band
/// the §5.11.28 syntax gate confines coded blocks to) are ever used in
/// the first dimension; the rows outside that band are placeholder
/// `{16384, 32768, 0}` entries and are transcribed full-width to keep
/// `MiSize`-indexing uniform with every other `[ BLOCK_SIZES ]` table.
pub const DEFAULT_WEDGE_INTER_INTRA_CDF: [[u16; 3]; BLOCK_SIZES] = [
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[20036, 32768, 0],
[24957, 32768, 0],
[26704, 32768, 0],
[27530, 32768, 0],
[29564, 32768, 0],
[29444, 32768, 0],
[26872, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
[16384, 32768, 0],
];
/// §8.3.2 `interintra` / `interintra_mode` context mapping:
/// `ctx = Size_Group[ MiSize ] - 1`. The result selects a
/// [`TileCdfContext::inter_intra_cdf`] /
/// [`TileCdfContext::inter_intra_mode_cdf`] row and is in
/// `0..(BLOCK_SIZE_GROUPS - 1)`. `mi_size` is the current block's `MiSize`
/// (`0..BLOCK_SIZES`).
///
/// The §5.11.28 syntax gate restricts coded blocks to
/// `BLOCK_8X8 <= MiSize <= BLOCK_32X32` (per §6.10.27), where
/// `Size_Group[ MiSize ]` is one of `{1, 2, 3}` and the subtraction is
/// well-defined. Returns `None` if `Size_Group[ MiSize ] == 0` (a caller
/// bug: the §5.11.28 gate would not have coded `interintra` /
/// `interintra_mode` in that case) or if `mi_size >= BLOCK_SIZES`.
pub fn interintra_ctx(mi_size: usize) -> Option<usize> {
if mi_size >= BLOCK_SIZES {
return None;
}
let g = SIZE_GROUP[mi_size];
if g == 0 {
None
} else {
Some(g - 1)
}
}
/// §8.3.2 `comp_group_idx` context selection (av1-spec p.384). The
/// spec body reads:
///
/// ```text
/// ctx = 0
/// if ( AvailU ) {
/// if ( !AboveSingle )
/// ctx += CompGroupIdxs[ MiRow - 1 ][ MiCol ]
/// else if ( AboveRefFrame[ 0 ] == ALTREF_FRAME )
/// ctx += 3
/// }
/// if ( AvailL ) {
/// if ( !LeftSingle )
/// ctx += CompGroupIdxs[ MiRow ][ MiCol - 1 ]
/// else if ( LeftRefFrame[ 0 ] == ALTREF_FRAME )
/// ctx += 3
/// }
/// ctx = Min( 5, ctx )
/// ```
///
/// `above_comp_group` / `left_comp_group` are the neighbour
/// `CompGroupIdxs` values (binary `0` or `1`); they're only consulted
/// on the `!AboveSingle` / `!LeftSingle` arm.
///
/// `above_ref_0_altref` / `left_ref_0_altref` carry the
/// `RefFrame[ 0 ] == ALTREF_FRAME = 7` test, only consulted on the
/// `AboveSingle` / `LeftSingle` arm.
///
/// Returns a value in `0..COMP_GROUP_IDX_CONTEXTS` (`= 0..6`); the
/// final `Min( 5, ctx )` clamp is applied here.
#[inline]
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn comp_group_idx_ctx(
avail_u: bool,
above_single: bool,
above_comp_group: u8,
above_ref_0_altref: bool,
avail_l: bool,
left_single: bool,
left_comp_group: u8,
left_ref_0_altref: bool,
) -> usize {
let mut ctx: usize = 0;
if avail_u {
if !above_single {
ctx += above_comp_group as usize;
} else if above_ref_0_altref {
ctx += 3;
}
}
if avail_l {
if !left_single {
ctx += left_comp_group as usize;
} else if left_ref_0_altref {
ctx += 3;
}
}
if ctx > 5 {
5
} else {
ctx
}
}
/// §8.3.2 `compound_idx` context selection (av1-spec p.384). The spec
/// body reads:
///
/// ```text
/// fwd = Abs( get_relative_dist( OrderHints[ RefFrame[ 0 ] ], OrderHint ) )
/// bck = Abs( get_relative_dist( OrderHints[ RefFrame[ 1 ] ], OrderHint ) )
/// ctx = ( fwd == bck ) ? 3 : 0
/// if ( AvailU ) {
/// if ( !AboveSingle )
/// ctx += CompoundIdxs[ MiRow - 1 ][ MiCol ]
/// else if ( AboveRefFrame[ 0 ] == ALTREF_FRAME )
/// ctx++
/// }
/// if ( AvailL ) {
/// if ( !LeftSingle )
/// ctx += CompoundIdxs[ MiRow ][ MiCol - 1 ]
/// else if ( LeftRefFrame[ 0 ] == ALTREF_FRAME )
/// ctx++
/// }
/// ```
///
/// `dist_equal` is the `fwd == bck` outcome the caller computed via
/// §7.8.1 `get_relative_dist` against the resolved `OrderHints[]` /
/// `OrderHint` frame-header state.
///
/// `above_compound` / `left_compound` are the neighbour `CompoundIdxs`
/// values (binary `0` or `1`); they're only consulted on the
/// `!AboveSingle` / `!LeftSingle` arm.
///
/// `above_ref_0_altref` / `left_ref_0_altref` carry the
/// `RefFrame[ 0 ] == ALTREF_FRAME = 7` test, only consulted on the
/// `AboveSingle` / `LeftSingle` arm.
///
/// Returns a value in `0..COMPOUND_IDX_CONTEXTS` (`= 0..6`): the
/// maximum is `3 (dist_equal) + 1 (above) + 1 (left) = 5`, the
/// minimum is `0`. The spec body does not apply a `Min(..)` clamp —
/// the cap is structural.
#[inline]
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn compound_idx_ctx(
dist_equal: bool,
avail_u: bool,
above_single: bool,
above_compound: u8,
above_ref_0_altref: bool,
avail_l: bool,
left_single: bool,
left_compound: u8,
left_ref_0_altref: bool,
) -> usize {
let mut ctx: usize = if dist_equal { 3 } else { 0 };
if avail_u {
if !above_single {
ctx += above_compound as usize;
} else if above_ref_0_altref {
ctx += 1;
}
}
if avail_l {
if !left_single {
ctx += left_compound as usize;
} else if left_ref_0_altref {
ctx += 1;
}
}
debug_assert!(
ctx < COMPOUND_IDX_CONTEXTS,
"compound_idx ctx must be in 0..COMPOUND_IDX_CONTEXTS"
);
ctx
}
// ---------------------------------------------------------------------
// Round 144 — wedge-index CDF (§9.4): `Default_Wedge_Index_Cdf`. The
// table codes the `wedge_index ∈ 0..WEDGE_TYPES` element read by two
// §5.11 syntax call sites:
// * §5.11.28 `read_interintra_mode` — the inter-intra wedge branch,
// reached when `wedge_interintra == 1`.
// * §5.11.29 `read_compound_type` — the inter-inter `COMPOUND_WEDGE`
// branch, reached when `compound_type == COMPOUND_WEDGE` (gated on
// `Wedge_Bits[ MiSize ] > 0`).
// Both call sites read the same default CDF; the §8.3.2 selection text
// is "wedge_index: The cdf is given by TileWedgeIndexCdf[ MiSize ]".
//
// Per the §9.4 note (p.436), only first-dimension indices 3..=9 (the
// `BLOCK_8X8`..`BLOCK_32X32` band) and 18..=19 (`BLOCK_8X16` /
// `BLOCK_16X8` 2:1 rectangles) are reachable — matching the rows where
// the §3 `Wedge_Bits[ BLOCK_SIZES ]` table is non-zero. The remaining
// rows hold a placeholder uniform CDF (step 2048 = 32768 / 16) and are
// transcribed full-width to keep the indexing uniform with every other
// `[ BLOCK_SIZES ]` table.
// ---------------------------------------------------------------------
/// `Default_Wedge_Index_Cdf[ BLOCK_SIZES ][ WEDGE_TYPES + 1 ]` (§9.4,
/// p.435). Symbol `wedge_index` (§5.11.28 wedge sub-branch and §5.11.29
/// `COMPOUND_WEDGE` branch; selects the direction and offset of the
/// wedge mask used during blending). The §8.3.2 selection is a straight
/// `TileWedgeIndexCdf[ MiSize ]` index. Per the §9.4 note only indices
/// `3..=9` and `18..=19` (the rows where `Wedge_Bits[ MiSize ] > 0`) are
/// ever used in the first dimension; the rows outside that set are the
/// placeholder uniform CDF `{ 2048, 4096, …, 30720, 32768, 0 }` and are
/// transcribed full-width to keep `MiSize`-indexing uniform with every
/// other `[ BLOCK_SIZES ]` table. Each row carries `WEDGE_TYPES = 16`
/// cumulative frequencies plus the §8.3 adaptation counter (starts at
/// 0).
pub const DEFAULT_WEDGE_INDEX_CDF: [[u16; WEDGE_TYPES + 1]; BLOCK_SIZES] = [
// Rows 0..=2 (BLOCK_4X4 / BLOCK_4X8 / BLOCK_8X4) — placeholder
// uniform, never reached (Wedge_Bits = 0).
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
// Rows 3..=9 (BLOCK_8X8..BLOCK_32X32) — reachable.
[
2438, 4440, 6599, 8663, 11005, 12874, 15751, 18094, 20359, 22362, 24127, 25702, 27752,
29450, 31171, 32768, 0,
],
[
806, 3266, 6005, 6738, 7218, 7367, 7771, 14588, 16323, 17367, 18452, 19422, 22839, 26127,
29629, 32768, 0,
],
[
2779, 3738, 4683, 7213, 7775, 8017, 8655, 14357, 17939, 21332, 24520, 27470, 29456, 30529,
31656, 32768, 0,
],
[
1684, 3625, 5675, 7108, 9302, 11274, 14429, 17144, 19163, 20961, 22884, 24471, 26719,
28714, 30877, 32768, 0,
],
[
1142, 3491, 6277, 7314, 8089, 8355, 9023, 13624, 15369, 16730, 18114, 19313, 22521, 26012,
29550, 32768, 0,
],
[
2742, 4195, 5727, 8035, 8980, 9336, 10146, 14124, 17270, 20533, 23434, 25972, 27944, 29570,
31416, 32768, 0,
],
[
1727, 3948, 6101, 7796, 9841, 12344, 15766, 18944, 20638, 22038, 23963, 25311, 26988,
28766, 31012, 32768, 0,
],
// Rows 10..=17 — placeholder uniform.
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
// Rows 18..=19 (BLOCK_8X16 / BLOCK_16X8) — reachable.
[
154, 987, 1925, 2051, 2088, 2111, 2151, 23033, 23703, 24284, 24985, 25684, 27259, 28883,
30911, 32768, 0,
],
[
1135, 1322, 1493, 2635, 2696, 2737, 2770, 21016, 22935, 25057, 27251, 29173, 30089, 30960,
31933, 32768, 0,
],
// Rows 20..=21 — placeholder uniform.
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
[
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
],
];
// ---------------------------------------------------------------------
// Round 150 — §9.3 `Partition_Subsize` table + §3 BLOCK_* enum staging
// (av1-spec p.402-403). Stages the `[10][BLOCK_SIZES]` lookup that the
// §5.11.4 `decode_partition()` body (av1-spec p.61-62) consumes:
//
// subSize = Partition_Subsize[ partition ][ bSize ]
// splitSize = Partition_Subsize[ PARTITION_SPLIT ][ bSize ]
//
// followed by per-partition `decode_block` / `decode_partition` calls
// against the resolved subSize / splitSize. The spec note at p.401 —
// "The table will never get accessed for rectangular block sizes" —
// is reflected by every rectangular bSize column carrying BLOCK_INVALID
// across the entire table; the [`partition_subsize`] helper surfaces
// that as `None` so a future `decode_partition` body never silently
// hands a sentinel to the next level of recursion.
//
// The §3 enumeration (av1-spec p.171-172, the `Name of subSize` table)
// is staged alongside as named [`BLOCK_*`] / [`BLOCK_INVALID`] constants
// so the [`PARTITION_SUBSIZE`] entries read as the spec spells them.
// Pre-existing [`BLOCK_8X8`] (idx 3, r149) and [`BLOCK_128X128`]
// (idx 15, r112) are kept verbatim; r150 adds the remaining 19 names so
// the full table can be transcribed without bare numeric literals.
// ---------------------------------------------------------------------
/// `BLOCK_4X4` (§3 enumeration, av1-spec p.171) — block-size ordinal `0`.
pub const BLOCK_4X4: usize = 0;
/// `BLOCK_4X8` (§3 enumeration, av1-spec p.171) — block-size ordinal `1`.
pub const BLOCK_4X8: usize = 1;
/// `BLOCK_8X4` (§3 enumeration, av1-spec p.171) — block-size ordinal `2`.
pub const BLOCK_8X4: usize = 2;
/// `BLOCK_8X16` (§3 enumeration, av1-spec p.171) — block-size ordinal `4`.
pub const BLOCK_8X16: usize = 4;
/// `BLOCK_16X8` (§3 enumeration, av1-spec p.171) — block-size ordinal `5`.
pub const BLOCK_16X8: usize = 5;
/// `BLOCK_16X16` (§3 enumeration, av1-spec p.171) — block-size ordinal `6`.
pub const BLOCK_16X16: usize = 6;
/// `BLOCK_16X32` (§3 enumeration, av1-spec p.171) — block-size ordinal `7`.
pub const BLOCK_16X32: usize = 7;
/// `BLOCK_32X16` (§3 enumeration, av1-spec p.171) — block-size ordinal `8`.
pub const BLOCK_32X16: usize = 8;
/// `BLOCK_32X32` (§3 enumeration, av1-spec p.171) — block-size ordinal `9`.
pub const BLOCK_32X32: usize = 9;
/// `BLOCK_32X64` (§3 enumeration, av1-spec p.172) — block-size ordinal `10`.
pub const BLOCK_32X64: usize = 10;
/// `BLOCK_64X32` (§3 enumeration, av1-spec p.172) — block-size ordinal `11`.
pub const BLOCK_64X32: usize = 11;
/// `BLOCK_64X64` (§3 enumeration, av1-spec p.172) — block-size ordinal `12`.
pub const BLOCK_64X64: usize = 12;
/// `BLOCK_64X128` (§3 enumeration, av1-spec p.172) — block-size ordinal `13`.
pub const BLOCK_64X128: usize = 13;
/// `BLOCK_128X64` (§3 enumeration, av1-spec p.172) — block-size ordinal `14`.
pub const BLOCK_128X64: usize = 14;
/// `BLOCK_4X16` (§3 enumeration, av1-spec p.172) — block-size ordinal `16`.
pub const BLOCK_4X16: usize = 16;
/// `BLOCK_16X4` (§3 enumeration, av1-spec p.172) — block-size ordinal `17`.
pub const BLOCK_16X4: usize = 17;
/// `BLOCK_8X32` (§3 enumeration, av1-spec p.172) — block-size ordinal `18`.
pub const BLOCK_8X32: usize = 18;
/// `BLOCK_32X8` (§3 enumeration, av1-spec p.172) — block-size ordinal `19`.
pub const BLOCK_32X8: usize = 19;
/// `BLOCK_16X64` (§3 enumeration, av1-spec p.172) — block-size ordinal `20`.
pub const BLOCK_16X64: usize = 20;
/// `BLOCK_64X16` (§3 enumeration, av1-spec p.172) — block-size ordinal `21`.
pub const BLOCK_64X16: usize = 21;
/// `BLOCK_INVALID` (§3 constant table, av1-spec p.7) — sentinel value
/// (`22`, one past the last `BLOCK_SIZES` entry) marking partition
/// choices that are not allowed. Every rectangular-`bSize` column of
/// [`PARTITION_SUBSIZE`] holds this sentinel, as does every entry of a
/// non-`PARTITION_NONE` row whose split would produce an invalid
/// child block. The [`partition_subsize`] helper translates the
/// sentinel into `Option::None` so callers never see the raw `22`.
pub const BLOCK_INVALID: usize = 22;
/// `PARTITION_TYPES_TOTAL` — the first dimension of
/// [`PARTITION_SUBSIZE`] (`10`); covers every named `PARTITION_*`
/// ordinal `0..=9` (§6.10.4 enumeration: `PARTITION_NONE`,
/// `PARTITION_HORZ`, `PARTITION_VERT`, `PARTITION_SPLIT`,
/// `PARTITION_HORZ_A`, `PARTITION_HORZ_B`, `PARTITION_VERT_A`,
/// `PARTITION_VERT_B`, `PARTITION_HORZ_4`, `PARTITION_VERT_4`).
pub const PARTITION_TYPES_TOTAL: usize = 10;
/// `Partition_Subsize[ 10 ][ BLOCK_SIZES ]` (§9.3 additional tables,
/// av1-spec p.402-403) — for a partition type `p` and a (square)
/// `bSize`, returns the size of the sub-blocks produced by the
/// split. If the split produces blocks of different sizes the table
/// holds the largest sub-block size; the splitting code in §5.11.4
/// reads both `Partition_Subsize[ partition ][ bSize ]` (the main
/// sub-block size) and `Partition_Subsize[ PARTITION_SPLIT ][ bSize ]`
/// (the secondary sub-block size used by the `_A` / `_B` cases).
///
/// The first dimension is the [`PARTITION_TYPES_TOTAL`] partition
/// ordinal; the second dimension is the `BLOCK_SIZES` (`22`) `bSize`
/// ordinal. Rectangular `bSize` columns are filled with
/// [`BLOCK_INVALID`] per the spec note "The table will never get
/// accessed for rectangular block sizes" — accessing such a column
/// in a conformant decode path is impossible, but the table is
/// transcribed full-width to match the spec.
pub const PARTITION_SUBSIZE: [[usize; BLOCK_SIZES]; PARTITION_TYPES_TOTAL] = [
// PARTITION_NONE (0): the block is left intact; subSize == bSize
// for every square bSize.
[
BLOCK_4X4,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_128X128,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_HORZ (1): top+bottom split — square bSize halves
// vertically (e.g. BLOCK_16X16 -> BLOCK_16X8). BLOCK_4X4 is
// BLOCK_INVALID because it cannot be split further.
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X4,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_128X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_VERT (2): left+right split — square bSize halves
// horizontally (e.g. BLOCK_16X16 -> BLOCK_8X16).
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_4X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X128,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_SPLIT (3): quarter-split — square bSize halves on
// both axes (e.g. BLOCK_16X16 -> BLOCK_8X8).
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_4X4,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_HORZ_A (4): top is split horizontally, bottom is one
// block — the spec records the larger (bottom) sub-block size,
// which is BLOCK_HORZ's subSize (top-bottom half). splitSize
// (read from row 3) carries the smaller HORZ_A children.
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X4,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_128X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_HORZ_B (5): same subSize as PARTITION_HORZ_A; the
// §5.11.4 walk distinguishes which child gets the splitSize by
// ordering decode_block calls.
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X4,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_128X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_VERT_A (6): left is split vertically, right is one
// block — subSize is BLOCK_VERT's subSize.
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_4X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X128,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_VERT_B (7): mirror of PARTITION_VERT_A.
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_4X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X128,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_HORZ_4 (8): four horizontal stripes; subSize is the
// quarter-height extended block (e.g. BLOCK_16X16 -> BLOCK_16X4).
// First three columns and the larger BLOCK_64X64 column onward
// are BLOCK_INVALID because the quarter would fall under the
// §5.x minimum or above the largest extended rectangle.
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X4,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_32X8,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_64X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
// PARTITION_VERT_4 (9): four vertical stripes — quarter-width
// extended block (e.g. BLOCK_16X16 -> BLOCK_4X16).
[
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_4X16,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_8X32,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_16X64,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
BLOCK_INVALID,
],
];
/// `Partition_Subsize[ partition ][ b_size ]` (§9.3, av1-spec p.402)
/// — typed accessor returning the resolved sub-block size or `None`
/// when the table holds [`BLOCK_INVALID`].
///
/// The `None` branch covers three §5.11.4 / §6.10.4 cases:
///
/// 1. `b_size` is rectangular. The spec note at p.401 — "The table
/// will never get accessed for rectangular block sizes" — means a
/// conformant decoder never reaches this case; surfacing `None`
/// catches caller bugs instead of silently propagating `22`.
/// 2. `b_size == BLOCK_4X4` and the partition is anything other than
/// [`PARTITION_NONE`]. A 4×4 block has no further split.
/// 3. The partition cannot apply to `b_size` (e.g. `PARTITION_HORZ_4`
/// on `BLOCK_128X128` — there is no `BLOCK_128X1` extended block).
///
/// Out-of-range `partition` (>= [`PARTITION_TYPES_TOTAL`]) or `b_size`
/// (>= [`BLOCK_SIZES`]) also returns `None`; the §5.11.4 walk feeds
/// in-range values by construction so a `Some(_)` return is the
/// expected path.
#[inline]
#[must_use]
pub const fn partition_subsize(partition: usize, b_size: usize) -> Option<usize> {
if partition >= PARTITION_TYPES_TOTAL || b_size >= BLOCK_SIZES {
return None;
}
let v = PARTITION_SUBSIZE[partition][b_size];
if v == BLOCK_INVALID {
None
} else {
Some(v)
}
}
// ===================================================================
// Round 151 — §5.11.4 `decode_partition()` body (av1-spec p.61-62).
//
// Stitches together the §9.4 partition-default CDFs (r137-r145), the
// §8.3.2 [`partition_ctx`] / [`split_or_horz_cdf`] / [`split_or_vert_cdf`]
// helpers, the §9.3 [`PARTITION_SUBSIZE`] table + [`partition_subsize`]
// accessor (r150), and the §9.3 [`MI_WIDTH_LOG2`] / [`NUM_4X4_BLOCKS_WIDE`]
// tables into the recursive partition-tree walker.
//
// The walker emits a [`Vec<DecodedBlockRecord>`] of `(mi_row, mi_col,
// sub_size)` leaves in spec syntax order; the actual `decode_block()`
// body (§5.11.5) — which decodes coefficients, motion vectors, and
// reconstruction state — remains out of scope. The §6.10.4 `MiSizes[]`
// neighbour grid is filled at every leaf so the recursive children
// observe the correct `AvailU` / `AvailL` against the
// `Mi_Width_Log2[ MiSizes[r-1][c] ] < bsl` /
// `Mi_Height_Log2[ MiSizes[r][c-1] ] < bsl` derivation the spec gives
// for the §8.3.2 partition context (av1-spec p.362).
// ===================================================================
/// `TileGeometry` — the four §5.11.1 tile-extent bounds the §5.11.4
/// walker consults for the [`is_inside`](https://example.invalid)-style
/// `AvailU` / `AvailL` test (av1-spec §5.11.51), and for the top-level
/// `r >= MiRows || c >= MiCols` early return.
///
/// All four values are in §6.10 mi (4-sample) units. For a frame that
/// is a single tile the natural fill is
/// `{ mi_row_start: 0, mi_row_end: MiRows, mi_col_start: 0,
/// mi_col_end: MiCols }`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TileGeometry {
/// `MiRowStart` per §5.11.1 / §5.11.51.
pub mi_row_start: u32,
/// `MiRowEnd` per §5.11.1 / §5.11.51 (one past the last mi row).
pub mi_row_end: u32,
/// `MiColStart` per §5.11.1 / §5.11.51.
pub mi_col_start: u32,
/// `MiColEnd` per §5.11.1 / §5.11.51 (one past the last mi col).
pub mi_col_end: u32,
}
impl TileGeometry {
/// `is_inside( r, c )` per §5.11.51, returning whether the
/// candidate mi position is inside the tile bounds.
#[inline]
#[must_use]
pub const fn is_inside(&self, r: i32, c: i32) -> bool {
r >= self.mi_row_start as i32
&& r < self.mi_row_end as i32
&& c >= self.mi_col_start as i32
&& c < self.mi_col_end as i32
}
}
/// `RESTORE_NONE` (§3 / §6.10.15) — no loop restoration on the unit.
pub const RESTORE_NONE: u8 = 0;
/// `RESTORE_WIENER` (§3) — Wiener filter restoration.
pub const RESTORE_WIENER: u8 = 1;
/// `RESTORE_SGRPROJ` (§3) — self-guided projection restoration.
pub const RESTORE_SGRPROJ: u8 = 2;
/// `RESTORE_SWITCHABLE` (§3) — per-unit switchable restoration; the
/// §5.11.58 `restoration_type` S() then picks NONE / WIENER / SGRPROJ.
pub const RESTORE_SWITCHABLE: u8 = 3;
/// `SUPERRES_NUM` (§3) — numerator of the super-resolution ratio.
/// Re-exported locally for the §5.11.57 unit-window arithmetic (the
/// canonical definition lives in [`crate::frame_header::SUPERRES_NUM`]).
const SUPERRES_NUM: u32 = crate::frame_header::SUPERRES_NUM;
/// §3 `Round2( x, n )` — `(x + (1 << (n - 1))) >> n` for `n > 0`,
/// identity for `n == 0`. Used by the §5.11.57 unit-grid extents
/// (`Round2(FrameHeight, subY)` / `Round2(UpscaledWidth, subX)`).
#[inline]
fn round2(x: u32, n: u8) -> u32 {
if n == 0 {
x
} else {
(x + (1u32 << (n - 1))) >> n
}
}
/// `count_units_in_frame( unitSize, frameSize )` per §5.11.57 (av1-spec
/// p.105): `Max((frameSize + (unitSize >> 1)) / unitSize, 1)`.
#[inline]
fn count_units_in_frame(unit_size: u32, frame_size: u32) -> u32 {
core::cmp::max((frame_size + (unit_size >> 1)) / unit_size, 1)
}
/// Public wrapper over the §5.11.57 [`count_units_in_frame`] helper so
/// the encoder's [`crate::encoder::loop_restoration_write::write_lr`]
/// driver computes the identical unit-grid extent the decode-walker does.
#[inline]
#[must_use]
pub fn count_units_in_frame_pub(unit_size: u32, frame_size: u32) -> u32 {
count_units_in_frame(unit_size, frame_size)
}
/// Public wrapper over the §3 [`round2`] helper for the encoder's
/// §5.11.57 write side.
#[inline]
#[must_use]
pub fn round2_pub(x: u32, n: u8) -> u32 {
round2(x, n)
}
/// Public accessor for `Num_4x4_Blocks_Wide[ bSize ]` (§3) — used by the
/// encoder's §5.11.57 write driver. Returns `0` for an out-of-range
/// `b_size` (caller validates against `BLOCK_SIZES` first).
#[inline]
#[must_use]
pub fn num_4x4_blocks_wide_pub(b_size: usize) -> u32 {
NUM_4X4_BLOCKS_WIDE.get(b_size).copied().unwrap_or(0) as u32
}
/// Public accessor for `Num_4x4_Blocks_High[ bSize ]` (§3) — used by the
/// encoder's §5.11.57 write driver.
#[inline]
#[must_use]
pub fn num_4x4_blocks_high_pub(b_size: usize) -> u32 {
NUM_4X4_BLOCKS_HIGH.get(b_size).copied().unwrap_or(0) as u32
}
/// One decoded §5.11.58 loop-restoration unit's payload — the
/// `restoration_type` selection plus the Wiener taps (`RESTORE_WIENER`)
/// or self-guided set / `xqd` (`RESTORE_SGRPROJ`). For `RESTORE_NONE`
/// the coefficient fields stay zero.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LrUnit {
/// §5.11.58 `LrType[ plane ][ unitRow ][ unitCol ]` — one of
/// [`RESTORE_NONE`] / [`RESTORE_WIENER`] / [`RESTORE_SGRPROJ`].
pub restoration_type: u8,
/// §5.11.58 `LrWiener[ plane ][ unitRow ][ unitCol ][ pass ][ j ]`
/// for `pass ∈ 0..2`, `j ∈ 0..WIENER_COEFFS`. Chroma planes force
/// `[pass][0] = 0`. Zero unless `restoration_type == RESTORE_WIENER`.
pub wiener: [[i32; crate::loop_restoration::WIENER_COEFFS]; 2],
/// §5.11.58 `LrSgrSet[ plane ][ unitRow ][ unitCol ]` (`lr_sgr_set`,
/// `0..16`). Meaningful only for `RESTORE_SGRPROJ`.
pub sgr_set: usize,
/// §5.11.58 `LrSgrXqd[ plane ][ unitRow ][ unitCol ][ i ]` for
/// `i ∈ 0..2`. Zero unless `restoration_type == RESTORE_SGRPROJ`.
pub sgr_xqd: [i32; 2],
}
impl LrUnit {
/// The §5.11.58 "no restoration" payload — `RESTORE_NONE` with every
/// coefficient field zero. This is the value an unwritten cell of the
/// per-plane §7.17 loop-restoration grid carries (the §5.11.57
/// `read_lr` unit-window only fires `read_lr_unit` for the units a
/// superblock actually covers; cells outside every superblock's
/// window in a partial-tile walk stay `RESTORE_NONE`, which §7.17
/// treats as an identity pass-through).
pub const NONE: Self = Self {
restoration_type: RESTORE_NONE,
wiener: [[0; crate::loop_restoration::WIENER_COEFFS]; 2],
sgr_set: 0,
sgr_xqd: [0; 2],
};
}
/// A §5.11.57 `read_lr` unit decode result, tagged with its grid
/// coordinates (`plane`, `unit_row`, `unit_col`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodedLrUnit {
/// Plane index (`0` luma, `1`/`2` chroma).
pub plane: usize,
/// `unitRow` per §5.11.57.
pub unit_row: u32,
/// `unitCol` per §5.11.57.
pub unit_col: u32,
/// The decoded unit payload.
pub unit: LrUnit,
}
/// Frame-level geometry the §5.11.57 `read_lr` unit-window arithmetic
/// consumes (the §5.9.x quantities the spec references as globals).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LrParams {
/// `NumPlanes` (§5.9.4).
pub num_planes: usize,
/// `FrameRestorationType[ plane ]` (§5.9.20) for each of three
/// planes — `RESTORE_NONE` for any unused plane.
pub frame_restoration_type: [u8; 3],
/// `LoopRestorationSize[ plane ]` (§5.9.20) for each of three planes.
pub loop_restoration_size: [u32; 3],
/// `subsampling_x` (§5.5.2).
pub subsampling_x: u8,
/// `subsampling_y` (§5.5.2).
pub subsampling_y: u8,
/// `FrameHeight` (§5.9.5).
pub frame_height: u32,
/// `UpscaledWidth` (§5.9.5 / §5.9.8).
pub upscaled_width: u32,
/// `use_superres` (§5.9.8).
pub use_superres: bool,
/// `SuperresDenom` (§5.9.8).
pub superres_denom: u32,
/// `allow_intrabc` (§5.9.20) — §5.11.57 returns immediately when set.
pub allow_intrabc: bool,
}
/// `DecodedBlockRecord` — one `decode_block( r, c, subSize )` invocation
/// emitted by [`PartitionWalker::decode_partition`], in §5.11.4 syntax
/// order.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodedBlockRecord {
/// `MiRow` per §5.11.5 — the mi-unit row of the block's top-left
/// corner.
pub mi_row: u32,
/// `MiCol` per §5.11.5 — the mi-unit column of the block's
/// top-left corner.
pub mi_col: u32,
/// `MiSize` per §5.11.5 — the BLOCK_* ordinal selected by the
/// partition tree for this leaf, in `0..BLOCK_SIZES`. Never
/// [`BLOCK_INVALID`] (the [`partition_subsize`] accessor is gated
/// on `Some(_)`).
pub sub_size: usize,
}
/// §7.11.3.15 frame-level order-hint context for the §5.11.33 inter
/// frame walk
/// ([`PartitionWalker::reconstruct_inter_frame_into_curr_frame_with_order_hints`]).
/// The [`COMPOUND_DISTANCE`] (`enable_jnt_comp`) compound arm derives
/// its `(FwdWeight, BckWeight)` blend weights per leaf from these
/// frame-header scalars and the per-reference `OrderHints[ ref ]` table;
/// every other arm ignores them. Carried as an owned value (not a
/// borrowed slice) so the bridge can pass it by value through the grid
/// snapshot without a lifetime entanglement.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameInterOrderHints {
/// §5.5.1 `OrderHintBits` — `order_hint_bits_minus_1 + 1`, in
/// `1..=8`, or `0` when `enable_order_hint == 0` (the §7.4
/// `get_relative_dist` short-circuit returns `0` then, which the
/// DISTANCE arm reads as an equal-distance / averaging blend).
pub order_hint_bits: u32,
/// §5.9.2 `OrderHint` of the frame being decoded.
pub current_order_hint: i32,
/// `OrderHints[ ref ]` for `ref` in `INTRA_FRAME..=ALTREF_FRAME`
/// (`0..=7`) — the per-reference output order hints. Indexed by the
/// raw `RefFrame` value (slot 0 = `INTRA_FRAME` unused on the
/// compound path). Sized to `ALTREF_FRAME + 1`.
pub order_hints_by_ref: [i32; crate::uncompressed_header_tail::ALTREF_FRAME + 1],
}
impl FrameInterOrderHints {
/// Identity-zero context — `OrderHintBits = 0`, `OrderHint = 0`,
/// every `OrderHints[ ref ] = 0`. The §7.11.3.15 DISTANCE arm reads
/// this as an equal-distance averaging blend. Used by
/// [`PartitionWalker::reconstruct_inter_frame_into_curr_frame`] (the
/// no-order-hint convenience entry) — correct for frames with no
/// distance-weighted compound leaves.
pub const IDENTITY: Self = Self {
order_hint_bits: 0,
current_order_hint: 0,
order_hints_by_ref: [0; crate::uncompressed_header_tail::ALTREF_FRAME + 1],
};
}
/// `PartitionWalker` — the §5.11.4 decoder-side scaffold that walks
/// the partition tree for one superblock.
///
/// State carried across recursive [`Self::decode_partition`] calls:
///
/// * The §6.10.4 `MiSizes[ r ][ c ]` grid (filled at every leaf via
/// the block's [`NUM_4X4_BLOCKS_WIDE`] / [`NUM_4X4_BLOCKS_HIGH`]
/// footprint), so the recursive children's §8.3.2 [`partition_ctx`]
/// query observes the correct neighbour widths.
/// * The list of [`DecodedBlockRecord`] leaves the walker emitted, in
/// §5.11.4 syntax order.
///
/// The walker is constructed against a frame's full mi grid (the
/// `MiRows` × `MiCols` extent) and tile geometry, then driven by
/// [`Self::decode_partition`] on the superblock at the top of the
/// recursion. The §5.11.5 `decode_block()` body itself stays out of
/// scope — the walker only emits leaves; an upcoming round wires up
/// the per-block coefficient / motion-vector decode.
///
/// Out-of-tile children (`r >= MiRows || c >= MiCols`) short-circuit
/// per the §5.11.4 `return 0` line.
///
/// `MAX_SB4` is the maximum superblock side in 4×4-block (mi) units —
/// `Num_4x4_Blocks_Wide[ BLOCK_128X128 ] = 32` (av1-spec §5.11.2
/// `sbSize4`). `BD_STRIDE` is the per-axis length of the §6.10.3
/// `BlockDecoded[ plane ]` storage: a superblock spans at most
/// `MAX_SB4` cells, plus the §5.11.3 one-cell border on each side, so
/// `MAX_SB4 + 2 = 34`. The SB-local index `(y, x)` with `y, x ∈
/// -1..=(sbSize4 >> sub)` maps to `(y + 1, x + 1) ∈ 0..=33`, which
/// fits `BD_STRIDE = 34`.
const MAX_SB4: usize = 32;
const BD_STRIDE: usize = MAX_SB4 + 2;
// r421: `Clone` so the encoder's search-side rate twin can price
// candidate symbol sequences on a snapshot of the write-side mirror.
#[derive(Debug, Clone)]
pub struct PartitionWalker {
mi_rows: u32,
mi_cols: u32,
geometry: TileGeometry,
/// `enable_intra_edge_filter` per §5.5.2 (sequence header) — frame-
/// scope flag set at [`Self::decode_tile_syntax`] entry from
/// [`TileDecodeParams::enable_intra_edge_filter`]. Gates the
/// §7.11.2.4 step-4 directional intra pre-pass (filter corner +
/// intra edge filter + intra edge upsample) applied to `AboveRow[]`
/// / `LeftCol[]` before the directional kernel. Defaults to `false`
/// (the unfiltered/un-upsampled path) on a freshly constructed
/// walker.
enable_intra_edge_filter: bool,
/// §5.11.18 per-cell `AvailU` (`is_inside( MiRow - 1, MiCol )`,
/// av1-spec line 3915) for the current tile geometry — rebuilt at
/// [`Self::begin_tile`] (and at construction) and consumed by the
/// in-walk §5.11.33 OBMC dispatch
/// ([`Self::predict_inter_leaf_from_walk`]'s
/// [`crate::GridObmcContext`] view).
walk_avail_u: Vec<u8>,
/// §5.11.18 per-cell `AvailL` (`is_inside( MiRow, MiCol - 1 )`,
/// av1-spec line 3916) — sibling of [`Self::walk_avail_u`].
walk_avail_l: Vec<u8>,
/// In-walk §5.11.33 `predict_inter` scratch — per-plane `u16`
/// mirrors of `CurrFrame[ plane ]` (the §7.11.3 drivers operate
/// on `u16` planes; the walker's buffers are post-`Clip1` `i32`,
/// so both directions of the mirror are lossless). Lazily sized
/// on first use; only the leaf's plane rect is synced per
/// prediction, so stale samples outside the rect are never read.
inter_pred_scratch: [Vec<u16>; 3],
/// §7.11.3.8 `LocalWarpParams[ 0..6 ]` per cell — stamped at a
/// `WARPED_CAUSAL` leaf's origin by the §5.11.5 inter arm (the
/// §7.10.4 sample list + §7.11.3.8 least-squares fit run at the
/// in-walk `compute_prediction()` position). Pre-fill `0`; read
/// only at `WARPED_CAUSAL` origins.
local_warp_params: Vec<i32>,
/// §7.11.3.8 `LocalValid` per cell — sibling of
/// [`Self::local_warp_params`]. Pre-fill `0`.
local_warp_valid: Vec<u8>,
/// `MiSizes[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer. Entries are `BLOCK_INVALID` for
/// not-yet-decoded cells and a `BLOCK_*` ordinal for decoded
/// leaves (every cell of the leaf's `bh4 * bw4` footprint).
mi_sizes: Vec<usize>,
/// `Skips[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec p.378 / §5.11.5 line
/// `Skips[ r + y ][ c + x ] = skip`). Entries are `0` for
/// not-yet-decoded cells and `0` or `1` for cells in the
/// `bh4 * bw4` footprint of a block whose §5.11.11
/// [`Self::decode_skip`] has already fired.
skips: Vec<u8>,
/// `SkipModes[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec p.378 / §5.11.5 line
/// `SkipModes[ r + y ][ c + x ] = skip_mode`). Entries are `0` for
/// not-yet-decoded cells and `0` or `1` for cells in the
/// `bh4 * bw4` footprint of a block whose §5.11.10
/// [`Self::decode_skip_mode`] has already fired.
skip_modes: Vec<u8>,
/// `CurrentQIndex` (§5.11.12) — the running quantiser index the
/// next §5.11.12 `read_delta_qindex()` call adjusts. Per §6.10.4
/// the tile-walk pre-loads this from §5.9.12 `base_q_idx` at the
/// first block of the tile; the walker tracks it across
/// [`Self::decode_delta_qindex`] calls so subsequent superblocks
/// see the accumulated value. Held as `i32` because the
/// pre-Clip3 sum is a signed quantity (the spec writes
/// `CurrentQIndex + (reducedDeltaQIndex << delta_q_res)`).
current_q_index: i32,
/// §5.11.2 / §5.11.7 / §5.11.18 `ReadDeltas` lifecycle bit — set
/// (to the frame's `delta_q_present`) at every superblock entry
/// by the §5.11.2 tile loop, cleared by the FIRST block's
/// §5.11.7/§5.11.18 `ReadDeltas = 0` line after its
/// `read_delta_qindex()` / `read_delta_lf()` pair, so exactly one
/// block per superblock consumes the delta symbols.
read_deltas_pending: bool,
/// `DeltaLF[ i ]` (§5.11.2 / §5.11.13) — the four running
/// loop-filter-strength deltas. §5.11.2 `decode_tile()` resets
/// every slot to `0` at tile entry; §5.11.13 updates one (when
/// `delta_lf_multi == 0`, only `DeltaLF[0]`) or all
/// `frameLfCount` slots per superblock with the spec's
/// `Clip3(-MAX_LOOP_FILTER, MAX_LOOP_FILTER, DeltaLF[i] +
/// (reducedDeltaLfLevel << delta_lf_res))` update. Held as `i32`
/// because the pre-Clip3 sum is signed; the post-clip range is
/// `-MAX_LOOP_FILTER ..= MAX_LOOP_FILTER` (i.e. `-63..=63`).
/// Stored as a fixed-length array because `FRAME_LF_COUNT` is a
/// §3 spec constant.
current_delta_lf: [i32; FRAME_LF_COUNT],
/// `DeltaLFs[row][col][i]` (the §5.11.5 grid-fill `DeltaLFs[r+y][c+x][i]
/// = DeltaLF[i]`, av1-spec p.59) packed into a row-major
/// `mi_rows * mi_cols * FRAME_LF_COUNT` flat buffer at slot
/// `(r * mi_cols + c) * FRAME_LF_COUNT + i`. Each block's §5.11.5
/// grid-fill stamps the §5.11.13 [`Self::current_delta_lf`] accumulator
/// value over its `bh4 * bw4` footprint; the §7.14.4 deblock then reads
/// the per-mi snapshot. Entries are `0` for not-yet-decoded cells (the
/// §5.9.18 zero default, which is also the whole grid when
/// `delta_lf_present == 0`). Stored as `i8` (r453): the §5.11.13
/// update clamps every stamped value to `-MAX_LOOP_FILTER ..=
/// MAX_LOOP_FILTER` (`-63..=63`), so the spec-bounded width is a
/// byte — only the running accumulator needs the pre-Clip3 `i32`.
delta_lfs: Vec<i8>,
/// `cdef_idx[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec §5.11.55 / §5.11.56 / §6.10.40
/// p.179, av1-spec p.104). Entries are `-1` for cells that no
/// §5.11.56 [`Self::decode_cdef`] call has touched yet (matching
/// the §5.11.55 `clear_cdef()` initialisation sentinel; -1 means
/// "CDEF disabled for that block") and a value in `0..(1 <<
/// cdef_bits) <= 8` for cells in the `64x64` footprint of a block
/// whose §5.11.56 has fired. Stored as `i8` because the spec's
/// sentinel is the literal value `-1` and the `cdef_bits` literal
/// read is bounded by `f(2)` (so `1 << cdef_bits <= 8`).
cdef_idx: Vec<i8>,
/// `IsInters[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec p.378 / §5.11.5 line
/// `IsInters[ r + y ][ c + x ] = is_inter`). Entries are `0` for
/// not-yet-decoded cells and `0` (intra block) or `1` (inter
/// block) for cells in the `bh4 * bw4` footprint of a block whose
/// §5.11.20 [`Self::decode_is_inter`] has already fired. The
/// initial-zero state matches the spec's pre-write "intra block"
/// reading (an unwritten cell's `is_inter` is 0), which is the
/// natural identity for the §8.3.2 neighbour-intra ctx walk: a
/// pre-write neighbour contributes the same `LeftIntra =
/// AboveIntra = true` weight as an unavailable neighbour gated
/// out by `AvailU` / `AvailL`.
is_inters: Vec<u8>,
/// `SegmentIds[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec §5.11.9 / §6.10.4 — the segment
/// map the §5.11.9 `read_segment_id()` populates as each block
/// fires). Entries are `-1` for cells that no §5.11.9
/// [`Self::decode_segment_id`] call has touched yet (the §5.11.9
/// neighbour-lookup `prevUL` / `prevU` / `prevL` sentinel — the
/// spec writes `prevUL = -1` when the above-left neighbour is
/// unavailable, and §5.11.9's `pred` derivation treats that
/// sentinel as "no information"). Cells in the `bh4 * bw4`
/// footprint of a decoded block carry the block's `segment_id`
/// (a value in `0..MAX_SEGMENTS = 0..8`). Stored as `i32` so the
/// `-1` sentinel is the literal spec value rather than a
/// shoehorned encoding.
segment_ids: Vec<i32>,
/// `AboveSegPredContext[ MiCol ]` (av1-spec §5.11.19 / §8.3.2 — the
/// `seg_id_predicted` ctx walk reads the column-indexed value).
/// One slot per `mi_col` column; entries are `0` until a §5.11.19
/// `inter_segment_id` call has stamped a `seg_id_predicted` value
/// into the column. The initial-zero state matches the §8.3.1
/// tile-entry initialisation (`AboveSegPredContext[ i ] = 0`
/// for every column at tile start).
above_seg_pred_context: Vec<u8>,
/// `LeftSegPredContext[ MiRow ]` (av1-spec §5.11.19 / §8.3.2 — the
/// `seg_id_predicted` ctx walk reads the row-indexed value). One
/// slot per `mi_row` row; entries are `0` until a §5.11.19 call
/// has stamped a `seg_id_predicted` value into the row. The
/// initial-zero state matches the §8.3.1 tile-entry initialisation
/// (`LeftSegPredContext[ i ] = 0` for every row at tile start).
left_seg_pred_context: Vec<u8>,
/// `AboveLevelContext[ plane ][ x4 ]` (§6.10.2 / §8.3.2) — the
/// per-plane cumulative-magnitude column array the §8.3.2
/// `all_zero` ctx walk consults at a 4-sample granularity. Flat
/// plane-major layout: slot `plane * mi_cols + x4`. Entries are
/// `0` at tile entry (§5.11.2 `clear_above_context()` — the
/// walker is one-per-tile, so the construction-time zero-fill is
/// the tile-entry reset) and carry the §5.11.39 `culLevel`
/// (`0..=63`) of the most recent TU covering the column.
above_level_context: Vec<u8>,
/// `AboveDcContext[ plane ][ x4 ]` (§6.10.2 / §8.3.2) — the
/// per-plane DC-sign column array (`0` = no DC coded, `1` =
/// negative DC, `2` = positive DC) the §8.3.2 `dc_sign` /
/// chroma-`all_zero` ctx walks consult. Same layout / reset
/// story as [`Self::above_level_context`].
above_dc_context: Vec<u8>,
/// `LeftLevelContext[ plane ][ y4 ]` (§6.10.2 / §8.3.2) — the
/// per-plane row twin of [`Self::above_level_context`]. Flat
/// plane-major layout: slot `plane * mi_rows + y4`. §5.11.2
/// resets it per superblock row via `clear_left_context()`
/// (surfaced as [`Self::clear_txb_left_context`]).
left_level_context: Vec<u8>,
/// `LeftDcContext[ plane ][ y4 ]` (§6.10.2 / §8.3.2) — the
/// per-plane row twin of [`Self::above_dc_context`].
left_dc_context: Vec<u8>,
/// `YModes[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec p.378 / §5.11.5 line
/// `YModes[ r + y ][ c + x ] = YMode`). Entries are `0`
/// (= `DC_PRED`, the §3 intra-mode enumeration's ordinal-zero
/// value) for not-yet-decoded cells and the block's `YMode`
/// (`0..INTRA_MODES`) for cells in the `bh4 * bw4` footprint of a
/// block whose §5.11.7 / §5.11.22
/// [`Self::decode_intra_frame_y_mode`] has already fired. The
/// initial-zero state matches the §8.3.2 `intra_frame_y_mode` ctx
/// walk for an unavailable neighbour: the spec writes
/// `abovemode = Intra_Mode_Context[ AvailU ? YModes[ MiRow - 1 ][
/// MiCol ] : DC_PRED ]`, so a pre-write cell contributes the same
/// `Intra_Mode_Context[ DC_PRED ] = 0` weight as an unavailable
/// neighbour.
y_modes: Vec<u8>,
/// `UVModes[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec §5.11.22 line 7 `UVModes[ r + y ][
/// c + x ] = UVMode`). Entries are `0` (= `DC_PRED`) for cells no
/// §5.11.22 chroma read has touched and the block's `UVMode`
/// (`0..UV_INTRA_MODES_CFL_ALLOWED`, including `UV_CFL_PRED = 13`)
/// for cells in a decoded block's `bh4 * bw4` footprint. Consulted
/// by the §7.11.2.8 chroma `get_filter_type( plane )` neighbour
/// smooth-mode check (the chroma counterpart of [`Self::y_modes`]).
uv_modes: Vec<u8>,
/// `TxSizes[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec §5.11.5 / §5.11.16 — the
/// per-block §5.11.5 grid-fill writes `TxSizes[ r + y ][ c + x ] =
/// TxSize` after `read_block_tx_size()` returns). Entries hold the
/// `TxSize` ordinal (`TX_4X4 = 0..=TX_64X16 = 18` per §6.10.16).
/// Cells that no §5.11.16 [`Self::read_block_tx_size`] call has
/// stamped yet carry [`TX_4X4`] (`0`, the initial-value identity
/// for the §8.3.2 ctx walks that consult the grid — an
/// unfilled-cell neighbour contributes a TX_4X4 = 0-wide / 0-tall
/// summand which is the natural identity).
tx_sizes: Vec<u8>,
/// `InterTxSizes[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec §5.11.16 / §5.11.17 — both the
/// `read_block_tx_size` `else` arm (`InterTxSizes[..] = TxSize`)
/// and the `read_var_tx_size` recursive base case
/// (`InterTxSizes[..] = txSz`) stamp into this grid). Entries are
/// `TxSize` ordinals (`TX_4X4 = 0..=TX_64X16 = 18`); pre-write
/// cells carry [`TX_4X4`] (`0`), the §8.3.2 `tx_depth` /
/// `txfm_split` ctx-walk identity for an unavailable neighbour.
inter_tx_sizes: Vec<u8>,
/// `TxTypes[ row ][ col ]` packed into a row-major `mi_rows *
/// mi_cols` flat buffer (av1-spec §5.11.39 / §5.11.40 / §5.11.47 —
/// the §5.11.39 `all_zero == 1` short-circuit stamps `TxTypes[ y4
/// + j ][ x4 + i ] = DCT_DCT` over the luma TU footprint; the
/// §5.11.47 `transform_type` reader stamps the decoded
/// `Tx_Type_*_Inv_Set?[ inter_tx_type | intra_tx_type ]` per-TU
/// luma value; the §5.11.40 `compute_tx_type` derivation reads
/// the value back on chroma blocks via `TxTypes[ y4 ][ x4 ]`).
///
/// Stored as `u8` because the §3 `TxType` enumeration is
/// `0..=TX_TYPES-1 = 15`. Pre-fill is `DCT_DCT = 0`, the §5.11.40
/// `Lossless || txSzSqrUp > TX_32X32` fallback — also the natural
/// identity for an unfilled cell.
///
/// The grid is in 4×4-luma-sample (MI) units; an `MxN` luma TU at
/// `(mi_row, mi_col)` stamps `(M/4) * (N/4)` cells starting from
/// `(mi_row, mi_col)`. r183 LANDED.
tx_types: Vec<u8>,
/// `RefFrames[ row ][ col ][ 0..2 ]` packed into a row-major
/// `mi_rows * mi_cols * 2` flat buffer (av1-spec §5.11.18 / §5.11.23
/// / §8.3.2 — the §5.11.18 prologue derives `LeftRefFrame[ 0..2 ]`
/// from `RefFrames[ MiRow ][ MiCol - 1 ][ .. ]` and
/// `AboveRefFrame[ 0..2 ]` from `RefFrames[ MiRow - 1 ][ MiCol ][ .. ]`;
/// the §5.11.23 `read_ref_frames` reader stamps the decoded
/// `RefFrame[ 0..2 ]` over the block's `bh4 * bw4` footprint).
///
/// Slot 0 holds `RefFrame[ 0 ]` (range `INTRA_FRAME = 0..=ALTREF_FRAME
/// = 7`); slot 1 holds `RefFrame[ 1 ]` (range `-1 (NONE) ..=ALTREF_FRAME
/// = 7`). Stored as `i8` so the `-1` sentinel matches the spec
/// literal `NONE`.
///
/// Pre-fill: every cell starts at `[INTRA_FRAME = 0, NONE = -1]`,
/// the §5.11.18 prologue's "unavailable" fallback. This matches
/// what an unavailable neighbour (`!AvailU` / `!AvailL`) reads as
/// via the spec lines `LeftRefFrame[0] = AvailL ? ... : INTRA_FRAME`
/// / `LeftRefFrame[1] = AvailL ? ... : NONE`. So a pre-write cell
/// gated through `AvailU` / `AvailL` still produces the spec-
/// correct fallback for the §5.11.23 / §5.11.18 ctx walks.
ref_frames: Vec<i8>,
/// `PaletteSizes[ plane ][ row ][ col ]` packed into a row-major
/// `3 * mi_rows * mi_cols` flat buffer (av1-spec §5.11.46 /
/// §5.11.49). Slot `plane * area + r * mi_cols + c` holds the
/// `PaletteSize{Y,UV}` for `plane = 0` (luma), `plane = 1` (U
/// chroma), and `plane = 2` (V chroma). Entries are `0` for cells
/// that no §5.11.46 palette read has populated (matching the
/// §5.11.22 line `PaletteSizeY = 0` / `PaletteSizeUV = 0`
/// initialisation and the §5.11.49 `aboveN = 0` / `leftN = 0`
/// fallback when no neighbour palette has been read). Cells in the
/// `bh4 * bw4` footprint of a block whose §5.11.46 luma arm decoded
/// `has_palette_y == 1` carry the decoded `PaletteSizeY` (in
/// `2..=PALETTE_COLORS = 8`); same for the U / V planes against
/// `PaletteSizeUV`. Stored as `u8` because the maximum value is 8.
///
/// Per the §5.11.46 syntax the U and V planes always share the same
/// `PaletteSizeUV`; both `plane = 1` and `plane = 2` slots carry
/// the same value on the chroma palette path (the spec treats them
/// as a paired pair).
palette_sizes: Vec<u8>,
/// `PaletteColors[ plane ][ row ][ col ][ idx ]` packed into a
/// row-major `3 * mi_rows * mi_cols * PALETTE_COLORS` flat buffer
/// (av1-spec §5.11.46 / §5.11.49). Slot `((plane * area) + r *
/// mi_cols + c) * PALETTE_COLORS + idx` holds `palette_colors_{y,
/// u, v}[ idx ]` for the block anchored at `(r, c)` whose §5.11.46
/// arm decoded the entries. Unused slots (`idx >= palette_sizes[
/// plane ][ r ][ c ]`) are left at `0`. Stored as `u16` so 10-bit
/// (and 12-bit) `BitDepth` palette entries fit without truncation.
palette_colors: Vec<u16>,
/// `Mvs[ row ][ col ][ list ][ comp ]` packed into a row-major
/// `mi_rows * mi_cols * 4` flat buffer (av1-spec §5.11.5 / §5.11.31
/// / §7.10 — the `Mvs[ r + y ][ c + x ][ refList ] = Mv[ refList ]`
/// grid-fill stamps the per-block motion vectors after §5.11.31
/// `assign_mv` resolves them, and §7.10.2.8 / §7.10.2.9 read
/// neighbour MVs from this grid via `Mvs[ mvRow ][ mvCol ][ candList ]`).
/// Slot `(r * mi_cols + c) * 4 + list * 2 + comp` carries
/// component `comp` (0 = vertical, 1 = horizontal — per the spec's
/// `[ 0 ]` / `[ 1 ]` ordering) of the list-`list` motion vector
/// for the block anchored at `(r, c)`. Pre-fill is `0` for every
/// slot — the §7.10.2.7 gate `if (IsInters[mvRow][mvCol] == 0)
/// return` ensures the §5.11.20 `IsInters[]` grid suppresses any
/// read of an unwritten `Mvs[]` cell, so the pre-fill value is
/// unobservable on the conformant path. Stored as `i16` so the
/// §5.11.31 motion-vector range (`-(1<<14)..(1<<14)` per §6.10.27
/// `Mv` validity) fits without truncation.
mvs: Vec<i16>,
/// `CompGroupIdxs[ row ][ col ]` packed into a row-major
/// `mi_rows * mi_cols` flat buffer (av1-spec §5.11.29 / §8.3.2 —
/// the §5.11.29 `read_compound_type` body sets
/// `CompGroupIdxs[ r + y ][ c + x ] = comp_group_idx` over the
/// block's `bh4 * bw4` footprint, and the §8.3.2 `comp_group_idx`
/// paragraph reads back `CompGroupIdxs[ MiRow - 1 ][ MiCol ]` /
/// `CompGroupIdxs[ MiRow ][ MiCol - 1 ]` on the
/// `!AboveSingle` / `!LeftSingle` arms). Entries are binary
/// (`0` or `1`); pre-fill is `0` (the §5.11.29 default
/// `comp_group_idx = 0` initialiser, which is also the natural
/// neighbour identity for an unavailable cell).
comp_group_idxs: Vec<u8>,
/// `CompoundIdxs[ row ][ col ]` packed into a row-major
/// `mi_rows * mi_cols` flat buffer (av1-spec §5.11.29 / §8.3.2 —
/// the §5.11.29 `read_compound_type` body sets
/// `CompoundIdxs[ r + y ][ c + x ] = compound_idx` over the
/// block's `bh4 * bw4` footprint, and the §8.3.2 `compound_idx`
/// paragraph reads back `CompoundIdxs[ MiRow - 1 ][ MiCol ]` /
/// `CompoundIdxs[ MiRow ][ MiCol - 1 ]` on the
/// `!AboveSingle` / `!LeftSingle` arms). Entries are binary
/// (`0` or `1`); pre-fill is `1` (the §5.11.29 default
/// `compound_idx = 1` initialiser; an unavailable neighbour
/// contributes the natural identity weight via the `AboveSingle`
/// / `LeftSingle` gate which fires first on such cells).
compound_idxs: Vec<u8>,
/// `InterpFilters[ row ][ col ][ dir ]` packed into a row-major
/// `mi_rows * mi_cols * 2` flat buffer — two slots per `(row, col)`
/// cell, one per direction (slot `0` = horizontal, slot `1` =
/// vertical, per av1-spec §5.11.x grid stamp on p.63 / §8.3.2
/// `interp_filter` ctx walk on p.369). The §5.11.x reader stamps
/// `InterpFilters[ r + y ][ c + x ][ dir ] = interp_filter[ dir ]`
/// over the block's `bh4 * bw4` footprint after the per-block
/// resolution; the §8.3.2 `interp_filter` ctx walk reads back the
/// neighbour slots `InterpFilters[ MiRow - 1 ][ MiCol ][ dir ]` /
/// `InterpFilters[ MiRow ][ MiCol - 1 ][ dir ]` for the next inter
/// block's per-direction selection. Entries are in
/// `0..=BILINEAR = 0..=3`; pre-fill is `EIGHTTAP = 0` (the §5.11.x
/// `interp_filter[ dir ] = EIGHTTAP` fallback when
/// `needs_interp_filter == 0` — also the natural identity for
/// neighbour cells whose ref-frame doesn't match the current
/// block's, which the §8.3.2 walk rejects via the `aboveType = 3`
/// / `leftType = 3` sentinel anyway).
interp_filters: Vec<u8>,
/// §5.11.29 decoded `compound_type` per cell, packed row-major
/// `mi_rows * mi_cols`. The §5.11.23 inter cascade stamps the
/// §5.11.29 [`CompoundTypeReadout::compound_type`] over the block's
/// `bh4 * bw4` footprint; the §5.11.33 frame walk
/// ([`Self::reconstruct_inter_frame_into_curr_frame`]) reads the
/// leaf-origin cell to select the §7.11.3.1 step-14 combine arm.
/// Pre-fill is [`COMPOUND_AVERAGE`] (the mask-free §5.11.29 default —
/// also the value the §5.11.33 walk's `is_compound_leaf` gate
/// short-circuits to on a single-ref / intra cell it never reads).
/// Entries are one of the §6.10.24 [`COMPOUND_WEDGE`] /
/// [`COMPOUND_DIFFWTD`] / [`COMPOUND_AVERAGE`] / [`COMPOUND_INTRA`] /
/// [`COMPOUND_DISTANCE`] ordinals.
compound_types: Vec<u8>,
/// §5.11.29 decoded `wedge_index` per cell, packed row-major
/// `mi_rows * mi_cols`. Stamped from the §5.11.29
/// [`CompoundTypeReadout::wedge_index`] (`Some(_)` only on the
/// [`COMPOUND_WEDGE`] arm; the non-wedge arms leave the pre-fill
/// `0`). Read by the §5.11.33 frame walk only at a `COMPOUND_WEDGE`
/// leaf's origin (paired with [`Self::compound_wedge_signs`]).
/// Entries are in `0..WEDGE_TYPES = 0..16`.
compound_wedge_indices: Vec<u8>,
/// §5.11.29 decoded `wedge_sign` per cell, packed row-major
/// `mi_rows * mi_cols`. Stamped from the §5.11.29
/// [`CompoundTypeReadout::wedge_sign`] (`Some(_)` only on the
/// [`COMPOUND_WEDGE`] arm). Binary (`0` / `1`); pre-fill `0`. Read
/// by the §5.11.33 frame walk only at a `COMPOUND_WEDGE` leaf's
/// origin.
compound_wedge_signs: Vec<u8>,
/// §5.11.29 decoded `mask_type` per cell, packed row-major
/// `mi_rows * mi_cols`. Stamped from the §5.11.29
/// [`CompoundTypeReadout::mask_type`] (`Some(_)` only on the
/// [`COMPOUND_DIFFWTD`] arm; `0` = `DIFFWTD_38`, `1` =
/// `DIFFWTD_38_INV`). Pre-fill `0`. Read by the §5.11.33 frame walk
/// only at a `COMPOUND_DIFFWTD` leaf's origin.
compound_mask_types: Vec<u8>,
/// §5.11.28 decoded `interintra_mode` per cell, packed row-major
/// `mi_rows * mi_cols`. Stamped from the §5.11.28
/// [`InterIntraReadout::interintra_mode`] (`Some(_)` only when the
/// §5.11.28 inner arm fired). Pre-fill `0` (`II_DC_PRED`). Read by
/// the §5.11.33 frame walk only at an inter-intra leaf's origin
/// (`RefFrame[1] == INTRA_FRAME`). Entries are in
/// `0..INTERINTRA_MODES = 0..4`.
interintra_modes: Vec<u8>,
/// §5.11.28 decoded `wedge_interintra` per cell, packed row-major
/// `mi_rows * mi_cols`. Stamped from the §5.11.28
/// [`InterIntraReadout::wedge_interintra`]. Binary (`0` = non-wedge
/// §7.11.3.13 intra-variant mask, `1` = §7.11.3.11 luma-grid wedge
/// mask); pre-fill `0`. Read by the §5.11.33 frame walk only at an
/// inter-intra leaf's origin.
wedge_interintras: Vec<u8>,
/// §5.11.28 decoded inter-intra `wedge_index` per cell, packed
/// row-major `mi_rows * mi_cols`. Stamped from the §5.11.28
/// [`InterIntraReadout::wedge_index`] (`Some(_)` only when
/// `interintra == 1 && wedge_interintra == 1`). Pre-fill `0`. Read
/// by the §5.11.33 frame walk only at an inter-intra leaf's origin
/// whose [`Self::wedge_interintras`] cell is `1`. Distinct grid from
/// the compound [`Self::compound_wedge_indices`]; the inter-intra
/// `wedge_sign` is fixed at `0` per §5.11.28 so it is not carried.
interintra_wedge_indices: Vec<u8>,
/// §5.11.27 decoded `motion_mode` per cell, packed row-major
/// `mi_rows * mi_cols`. Stamped from the §5.11.27
/// [`DecodedInterBlockModeInfo::motion_mode`] over the block's
/// `bh4 * bw4` footprint. Pre-fill [`MOTION_MODE_SIMPLE`] (`0`, the
/// §5.11.27 short-circuit default). Entries are one of
/// [`MOTION_MODE_SIMPLE`] / [`MOTION_MODE_OBMC`] /
/// [`MOTION_MODE_WARPED_CAUSAL`]. Surfaced via
/// [`Self::motion_modes`] for callers driving the §7.11.3.5 warp /
/// §7.11.3.9 OBMC arms.
motion_modes: Vec<u8>,
/// `DecodedBlockRecord` leaves emitted by [`Self::decode_partition`]
/// in §5.11.4 syntax order.
blocks: Vec<DecodedBlockRecord>,
/// r475 — the frame-level mi origin of a TILE-SIZED walker (the
/// tile at the origin of its own frame): §5.11.57 `read_lr`
/// indexes the frame's restoration-unit grid at `( r + row, c +
/// col )`. `(0, 0)` for a frame-scope walker.
lr_origin: (u32, u32),
/// `CurrFrame[ plane ][ y ][ x ]` (av1-spec §7.12.3 step-3 / §7.11.x
/// prediction kernels / §7.14 loop filter / §7.15 CDEF / §7.16
/// upscaling / §7.17 loop restoration). Three per-plane sample
/// buffers (Y / Cb / Cr); slot `plane` holds the `(rows, cols)`
/// row-major flat buffer. The buffer is lazily allocated on the
/// first per-plane §7.12.3 step-3 merge call (see
/// [`Self::ensure_curr_frame_plane`]); the entry stays `None` until
/// the first merge fires on that plane.
///
/// Per-plane dimensions follow §5.9.5 / §5.11.34:
/// * luma (`plane == 0`): `rows = MiRows * MI_SIZE`, `cols = MiCols
/// * MI_SIZE`.
/// * chroma (`plane > 0`): `rows = (MiRows * MI_SIZE) >> sub_y`,
/// `cols = (MiCols * MI_SIZE) >> sub_x` per §5.11.34 line 19
/// `subX` / `subY`.
///
/// Initial fill is `0` for every sample — matching the natural
/// identity for the §7.12.3 step-3 additive merge before any
/// upstream §7.11.x prediction-sample writer fires (a TU with no
/// prediction sees `CurrFrame[..][..] = Clip1(0 + Residual[i][j])`,
/// the spec's `prediction == 0` arm). Stored as `i32` so the
/// pre-`Clip1` `prediction + Residual[i][j]` sum fits without
/// truncation across all `BitDepth ∈ {8, 10, 12}` modes.
curr_frame: [Option<CurrFramePlane>; 3],
/// r408 — §7.11.5 luma TU-overhang side store (see
/// [`LumaOverhang`]). Lazily allocated on the first clipped
/// plane-0 write.
luma_overhang: Option<Box<LumaOverhang>>,
/// `RefLrWiener[ plane ][ pass ][ WIENER_COEFFS ]` (§5.11.2 /
/// §5.11.58 — av1-spec p.60 / p.106). The per-plane, per-pass
/// running reference for the §5.11.58 `decode_signed_subexp_with_ref_bool`
/// Wiener-tap reads. §5.11.2 `decode_tile()` resets every slot to
/// `Wiener_Taps_Mid[ j ]` at tile entry; each §5.11.58 unit that
/// decodes a `RESTORE_WIENER` tap overwrites the matching slot with
/// the decoded value (so the next unit's subexp read references it).
/// Surfaced via [`Self::reset_lr_refs`].
ref_lr_wiener: [[[i32; crate::loop_restoration::WIENER_COEFFS]; 2]; 3],
/// `RefSgrXqd[ plane ][ 2 ]` (§5.11.2 / §5.11.58 — av1-spec p.60 /
/// p.106). The per-plane running reference for the §5.11.58
/// self-guided `xqd` subexp reads. §5.11.2 resets every slot to
/// `Sgrproj_Xqd_Mid[ i ]` at tile entry; each §5.11.58 unit that
/// decodes a `RESTORE_SGRPROJ` `xqd` overwrites the matching slot.
ref_sgr_xqd: [[i32; 2]; 3],
/// `LrType` / `LrWiener` / `LrSgrSet` / `LrSgrXqd` per-plane grids
/// (§5.11.58 — av1-spec p.106), the §7.17 loop-restoration input.
/// Slot `plane` is `None` until the first §5.11.57
/// [`Self::read_lr`] call decodes a unit for that plane; thereafter
/// it holds the row-major `unit_rows * unit_cols` grid of
/// [`LrUnit`] payloads. Surfaced via [`Self::lr_unit`] /
/// [`Self::lr_unit_grid_dims`].
lr_units: [Option<LrUnitGrid>; 3],
/// `BlockDecoded[ plane ][ y ][ x ]` (av1-spec §5.11.3 / §5.11.35 /
/// §6.10.3 — av1-spec p.60 / p.85 / p.405). One boolean per 4×4
/// sample block per plane within the **current superblock**, plus a
/// one-cell border on every side. The indices are superblock-local:
/// the §5.11.35 `subBlockMiRow = (row & sbMask)` /
/// `subBlockMiCol = (col & sbMask)` derivation reduces the absolute
/// mi position modulo the superblock side, and the §5.11.3
/// `clear_block_decoded_flags` reset walks SB-local `y, x ∈
/// -1..=(sbSize4 >> sub)`. The flat slot for plane-`plane`,
/// SB-local `(y, x)` is `plane * BD_STRIDE * BD_STRIDE + (y + 1) *
/// BD_STRIDE + (x + 1)` (the `+1` shift folds the `-1` border into
/// index 0).
///
/// Per §6.10.3: a `1` away from the border means the matching 4×4
/// block has been decoded; the border row/column (SB-local `-1`)
/// encodes the above-right / below-left availability the §5.11.35
/// `predict_intra` invocation reads. The grid is reset once per
/// superblock by [`Self::clear_block_decoded_flags`] (the §5.11.2
/// tile walk calls it immediately before
/// [`Self::decode_partition`]) and stamped per transform unit by the
/// §5.11.35 tail loop inside [`Self::transform_block_emit`].
///
/// Stored as `u8` (`0` / `1`) with a fixed `3 * BD_STRIDE *
/// BD_STRIDE` length — the superblock-local scope bounds it to a
/// constant size regardless of frame extent.
block_decoded: Vec<u8>,
/// `MaxLumaW` / `MaxLumaH` (§5.11.35 lines `MaxLumaW = startX +
/// stepX * 4` / `MaxLumaH = startY + stepY * 4`, av1-spec p.85).
/// Set on every plane-0 (luma) transform-block emit and read back
/// by the §7.11.5 `predict_chroma_from_luma` luma-subsample clamp
/// (the spec's `Min( lumaX, MaxLumaW - (1 << subX) )` and
/// `Min( lumaY, MaxLumaH - (1 << subY) )` extent clamps). Because the
/// §5.11.34 plane loop walks the whole luma plane of a block before
/// its chroma planes, the value in flight when a CfL chroma TU runs
/// is the most-recent luma TU's extent — the right/bottom edge of
/// the co-located luma region. Held as `u32` in per-plane (luma)
/// sample units; `0` on a fresh walker (no luma TU emitted yet).
max_luma_w: u32,
max_luma_h: u32,
/// Per-block §5.11.35 palette-prediction state — `PaletteSizeY` /
/// `PaletteSizeUV` plus the §5.11.49 decoded `ColorMapY` /
/// `ColorMapUV` for the block currently being decoded. Set by
/// [`Self::decode_block_syntax`] between the §5.11.49
/// `palette_tokens()` read and the §5.11.34 `residual()` call so
/// the per-TU `transform_block` dispatch can run the §5.11.35
/// `predict_palette` arm; reset (`0` / `None`) at every block
/// entry. The maps are handed back to the block's
/// [`DecodedBlock`] after `residual()` returns.
current_palette_size_y: u8,
current_palette_size_uv: u8,
current_color_map_y: Option<DecodedPaletteMap>,
current_color_map_uv: Option<DecodedPaletteMap>,
}
/// Per-plane `CurrFrame[plane]` sample buffer — owned `(rows, cols,
/// samples)` triple. The flat `samples` buffer is row-major with
/// `rows * cols` `i32` entries.
#[derive(Debug, Clone, PartialEq, Eq)]
struct CurrFramePlane {
rows: u32,
cols: u32,
samples: Vec<i32>,
}
/// r408 — §7.11.5 luma TU-overhang store. The spec's `CurrFrame[ 0 ]`
/// is conceptually unbounded: a §5.11.35 transform block whose
/// `startX < maxX` is decoded at its FULL `Tx_Width × Tx_Height`
/// extent, so its reconstruction can overhang the `MiCols * MI_SIZE`
/// padded grid by up to `Tx_Width - MI_SIZE` samples. The ONLY reader
/// of those beyond-grid samples is the §7.11.5 chroma-from-luma
/// process (its `Min( lumaX, MaxLumaW - (1 << subX) )` clamp reaches
/// the last luma TU's edge, which §7.11.2's own `maxX` clamp never
/// does), so the walker keeps the overhang in three side strips
/// instead of widening every plane buffer's stride: `right` covers
/// `x ∈ [cols, cols + LUMA_OH)`, `bottom` covers `y ∈ [rows, rows +
/// LUMA_OH)`, `corner` both. `LUMA_OH = 64` bounds the largest
/// possible overhang (`Tx_Width ≤ 64`).
#[derive(Debug, Clone, PartialEq)]
struct LumaOverhang {
rows: u32,
cols: u32,
/// `rows × LUMA_OH`, row-major.
right: Vec<i32>,
/// `LUMA_OH × cols`, row-major.
bottom: Vec<i32>,
/// `LUMA_OH × LUMA_OH`, row-major.
corner: Vec<i32>,
}
/// Maximum §5.11.35 luma TU overhang past the mi-padded grid.
const LUMA_OH: u32 = 64;
impl LumaOverhang {
fn new(rows: u32, cols: u32) -> Option<Box<Self>> {
let r = (rows as usize).checked_mul(LUMA_OH as usize)?;
let b = (LUMA_OH as usize).checked_mul(cols as usize)?;
let mut right = Vec::new();
right.try_reserve_exact(r).ok()?;
right.resize(r, 0);
let mut bottom = Vec::new();
bottom.try_reserve_exact(b).ok()?;
bottom.resize(b, 0);
Some(Box::new(Self {
rows,
cols,
right,
bottom,
corner: vec![0i32; (LUMA_OH as usize) * (LUMA_OH as usize)],
}))
}
/// Sample at `(y, x)` in luma-plane coordinates; `0` for a cell
/// past the `LUMA_OH` headroom (unreachable for conformant TUs).
fn get(&self, y: u32, x: u32) -> i32 {
if y < self.rows && x < self.cols {
return 0; // main-buffer region — caller bug; inert.
}
if y < self.rows {
let ox = x - self.cols;
if ox >= LUMA_OH {
return 0;
}
return self.right[(y as usize) * (LUMA_OH as usize) + (ox as usize)];
}
let oy = y - self.rows;
if oy >= LUMA_OH {
return 0;
}
if x < self.cols {
return self.bottom[(oy as usize) * (self.cols as usize) + (x as usize)];
}
let ox = x - self.cols;
if ox >= LUMA_OH {
return 0;
}
self.corner[(oy as usize) * (LUMA_OH as usize) + (ox as usize)]
}
fn slot(&mut self, y: u32, x: u32) -> Option<&mut i32> {
if y < self.rows && x < self.cols {
return None;
}
if y < self.rows {
let ox = x.checked_sub(self.cols)?;
if ox >= LUMA_OH {
return None;
}
return self
.right
.get_mut((y as usize) * (LUMA_OH as usize) + (ox as usize));
}
let oy = y - self.rows;
if oy >= LUMA_OH {
return None;
}
if x < self.cols {
return self
.bottom
.get_mut((oy as usize) * (self.cols as usize) + (x as usize));
}
let ox = x - self.cols;
if ox >= LUMA_OH {
return None;
}
self.corner
.get_mut((oy as usize) * (LUMA_OH as usize) + (ox as usize))
}
}
/// Per-plane §7.17 loop-restoration unit grid — the persistent home for
/// the §5.11.58 `LrType` / `LrWiener` / `LrSgrSet` / `LrSgrXqd` arrays
/// that §5.11.57 `read_lr` fills as it walks each superblock's
/// unit-window. Indexed row-major over the `unit_rows * unit_cols`
/// frame-level grid (`units[ unitRow * unit_cols + unitCol ]`); a cell no
/// superblock covered carries [`LrUnit::NONE`] (the §7.17 identity).
///
/// Lazily allocated on the first §5.11.57 [`PartitionWalker::read_lr`]
/// call that decodes a unit for the plane — the unit-grid extents
/// (`unitRows` / `unitCols`) come from the per-frame [`LrParams`] the
/// caller passes at decode time, not from the walker constructor.
#[derive(Debug, Clone, PartialEq, Eq)]
struct LrUnitGrid {
/// `unitRows` per §5.11.57 (`count_units_in_frame(unitSize,
/// Round2(FrameHeight, subY))`).
unit_rows: u32,
/// `unitCols` per §5.11.57 (`count_units_in_frame(unitSize,
/// Round2(UpscaledWidth, subX))`).
unit_cols: u32,
/// Row-major `unit_rows * unit_cols` flat grid of §5.11.58 payloads.
units: Vec<LrUnit>,
}
/// r282 — per-leaf grid-stamp bundle for
/// [`PartitionWalker::stamp_encoder_block_syntax`]. Field semantics
/// mirror the §5.11.5 grid-fill variables of the same names; see the
/// stamp method's docs for the per-grid gating rules.
pub(crate) struct EncoderBlockSyntaxStamp<'a> {
/// §5.11.5 `MiRow`.
pub mi_row: u32,
/// §5.11.5 `MiCol`.
pub mi_col: u32,
/// §5.11.5 `MiSize`.
pub sub_size: usize,
/// §5.11.11 `skip`.
pub skip: u8,
/// §5.11.10 `skip_mode` (r413) — `0` on every intra-frame block
/// and on every inter-frame block whose §5.11.10 read
/// short-circuits; mirrors the decode walker's `SkipModes[]`
/// grid-fill so the write mirror's §8.3.2 `skip_mode` ctx tracks
/// the decode walker's.
pub skip_mode: u8,
/// §5.11.8 `segment_id`.
pub segment_id: u8,
/// §5.11.7 `is_inter` (1 on the `use_intrabc` arm, else 0).
pub is_inter: u8,
/// §5.11.7 `YMode`.
pub y_mode: u8,
/// §5.11.7 `RefFrame[ 0..2 ]` — `[ INTRA_FRAME, NONE ] = [0, -1]`
/// on every intra-frame block. Stamped only when `is_inter != 0`.
pub ref_frame: [i8; 2],
/// §5.11.26 `Mv[ 0 ]` (`[ row, col ]`, 1/8-luma-sample units).
/// Stamped only when `is_inter != 0`.
pub mv: [i32; 2],
/// r412 — §5.11.26 `Mv[ 1 ]` (the compound second-list vector;
/// `[ 0, 0 ]` on single-reference and intra blocks, matching the
/// decode walker's slot-1 stamp). Stamped only when
/// `is_inter != 0`.
pub mv2: [i32; 2],
/// §5.11.7 `interp_filter[ 0..2 ]` (`BILINEAR` on the intrabc
/// arm). Stamped only when `is_inter != 0`.
pub interp_filter: [u8; 2],
/// §5.11.27 decoded `motion_mode` ordinal
/// ([`MOTION_MODE_SIMPLE`] / [`MOTION_MODE_OBMC`] /
/// [`MOTION_MODE_WARPED_CAUSAL`]). Stamped into the
/// [`PartitionWalker::motion_modes`] grid over the block footprint
/// (mirroring the §5.11.5 decode-walker grid-fill) only when
/// `is_inter != 0`; intra blocks leave the grid at its
/// `MOTION_MODE_SIMPLE` initial-zero state. Read by the §5.11.33
/// frame walk's OBMC / warp dispatch.
pub motion_mode: u8,
/// r415 — §5.11.29 `comp_group_idx` (`0` on single-reference /
/// skip-mode / COMPOUND_AVERAGE blocks; `1` selects the masked
/// group). Stamped into `CompGroupIdxs[]` over the footprint only
/// when `is_inter != 0`, mirroring the decode walker.
pub comp_group_idx: u8,
/// r415 — §5.11.29 `compound_idx` (the line-2 pre-set `1` while
/// `enable_jnt_comp == 0`). Stamped into `CompoundIdxs[]` only
/// when `is_inter != 0`.
pub compound_idx: u8,
/// r415 — §5.11.29 `compound_type` ordinal (COMPOUND_AVERAGE on
/// every non-masked block). Stamped into `CompoundTypes[]` only
/// when `is_inter != 0`.
pub compound_type: u8,
/// r415 — §5.11.29 `wedge_index` / `wedge_sign` / `mask_type`
/// side data (`0` when the sub-branch did not fire), stamped into
/// their §5.11.5 grids only when `is_inter != 0`.
pub wedge_index: u8,
pub wedge_sign: u8,
pub mask_type: u8,
/// r417 — §5.11.28 inter-intra side data (`interintra_mode` /
/// `wedge_interintra` / the interintra `wedge_index`; all `0`
/// when `interintra == 0` — the block class is recognisable from
/// `ref_frame[ 1 ] == INTRA_FRAME` with `is_inter == 1`), stamped
/// into the decode-walker-twin §5.11.5 grids only when
/// `is_inter != 0`.
pub interintra_mode: u8,
pub wedge_interintra: u8,
pub interintra_wedge_index: u8,
/// §5.11.46 `PaletteSizeY` (0 = no luma palette).
pub palette_size_y: u8,
/// §5.11.46 `palette_colors_y[ 0..PaletteSizeY ]`.
pub palette_colors_y: &'a [u16],
/// §5.11.46 `PaletteSizeUV` (0 = no chroma palette).
pub palette_size_uv: u8,
/// §5.11.46 `palette_colors_u[ 0..PaletteSizeUV ]`.
pub palette_colors_u: &'a [u16],
/// §5.11.46 `palette_colors_v[ 0..PaletteSizeUV ]`.
pub palette_colors_v: &'a [u16],
/// §5.11.56 anchor value — `Some(v)` only when the matching
/// writer emitted the `L(cdef_bits)` literal for this leaf.
pub cdef: Option<i8>,
/// §5.11.15 / §5.11.16 `TxSize` — the per-block transform size the
/// (bit-silent on `TxMode != TX_MODE_SELECT`) §5.11.16
/// `read_block_tx_size()` else-arm derives. Stamped into BOTH
/// `TxSizes[]` and `InterTxSizes[]` over the block footprint,
/// mirroring [`PartitionWalker::read_block_tx_size`]'s else-arm
/// grid-fill. Added r283 (the §5.11.34 residual write threading
/// keeps the mirror's tx grids in decode-walker parity).
pub tx_size: u8,
}
/// r412 — rect capture of the encoder-mirror mode-info grids, produced
/// by [`PartitionWalker::snapshot_encoder_stamp_rect`] and consumed by
/// [`PartitionWalker::restore_encoder_stamp_rect`]. Row-major over the
/// clipped `rows × cols` rect; the multi-slot grids (`ref_frames` ×2,
/// `interp_filters` ×2, `mvs` ×4, `delta_lfs` ×FRAME_LF_COUNT,
/// palette grids ×3 planes) pack their per-cell slots contiguously.
#[derive(Debug, Clone)]
pub(crate) struct EncoderStampSnapshot {
mi_row: u32,
mi_col: u32,
rows: u32,
cols: u32,
mi_sizes: Vec<usize>,
skips: Vec<u8>,
skip_modes: Vec<u8>,
segment_ids: Vec<i32>,
y_modes: Vec<u8>,
is_inters: Vec<u8>,
tx_sizes: Vec<u8>,
inter_tx_sizes: Vec<u8>,
motion_modes: Vec<u8>,
ref_frames: Vec<i8>,
interp_filters: Vec<u8>,
mvs: Vec<i16>,
delta_lfs: Vec<i8>,
palette_sizes: Vec<u8>,
palette_colors: Vec<u16>,
comp_group_idxs: Vec<u8>,
compound_idxs: Vec<u8>,
compound_types: Vec<u8>,
compound_wedge_indices: Vec<u8>,
compound_wedge_signs: Vec<u8>,
compound_mask_types: Vec<u8>,
interintra_modes: Vec<u8>,
wedge_interintras: Vec<u8>,
interintra_wedge_indices: Vec<u8>,
}
/// r460 — the [`PartitionWalker::snapshot_price_scope`] capture: one
/// block's worth of encoder-write-path state, restorable in O(block).
#[derive(Debug, Clone)]
pub(crate) struct PriceScopeSnapshot {
stamp: EncoderStampSnapshot,
tx_types: Vec<u8>,
cdef: Vec<(usize, i8)>,
/// Per plane `(above_c0, above_c1, left_r0, left_r1)`.
spans: [(usize, usize, usize, usize); 3],
above_ctx: Vec<(u8, u8)>,
left_ctx: Vec<(u8, u8)>,
seg_cols: (usize, usize),
seg_rows: (usize, usize),
above_seg: Vec<u8>,
left_seg: Vec<u8>,
current_q_index: i32,
current_delta_lf: [i32; FRAME_LF_COUNT],
read_deltas_pending: bool,
}
/// Per-tile frame-constant parameters threaded into the §5.11.2
/// `decode_tile()` superblock loop ([`PartitionWalker::decode_tile_syntax`])
/// and forwarded unchanged to every §5.11.4
/// [`PartitionWalker::decode_partition_syntax`] call.
///
/// These are the frame-header / sequence-header constants that do not
/// change within a tile; bundling them keeps the tile-loop entry point
/// from re-spelling `decode_partition_syntax`'s ~30-argument list at the
/// call site. Field semantics mirror the same-named `decode_partition_syntax`
/// parameters one-for-one.
#[derive(Debug, Clone, Copy)]
pub struct TileDecodeParams<'a> {
/// `FrameIsIntra` per §5.9.2 — `true` for KEY / INTRA_ONLY frames.
pub frame_is_intra: bool,
/// `subsampling_x` per §5.5.2.
pub subsampling_x: u8,
/// `subsampling_y` per §5.5.2.
pub subsampling_y: u8,
/// `NumPlanes` per §5.5.2 (`mono_chrome ? 1 : 3`).
pub num_planes: u8,
/// `SegIdPreSkip` per §5.9.14.
pub seg_id_pre_skip: bool,
/// `segmentation_enabled` per §5.9.14.
pub segmentation_enabled: bool,
/// r426 — per-segment `SEG_LVL_SKIP` active flags (§5.9.14
/// `FeatureEnabled[ s ][ SEG_LVL_SKIP ]`). The §5.11.11
/// `read_skip` short-circuit derives per BLOCK from the coded
/// segment id — a frame-level any-segment collapse forced
/// `skip = 1` onto blocks of SKIP-less segments on mixed tables.
pub seg_skip: [bool; MAX_SEGMENTS],
/// `LastActiveSegId` per §5.9.14.
pub last_active_seg_id: u8,
/// `LosslessArray[ segment_id ]` per §5.9.2.
pub lossless_array: &'a [bool; MAX_SEGMENTS],
/// `CodedLossless` per §5.9.2.
pub coded_lossless: bool,
/// `enable_cdef` per §5.9.11.
pub enable_cdef: bool,
/// `allow_intrabc` per §5.9.2.
pub allow_intrabc: bool,
/// `cdef_bits` per §5.9.19.
pub cdef_bits: u32,
/// `use_128x128_superblock` per §5.5.1.
pub use_128x128_superblock: bool,
/// `delta_q_res` per §5.9.17.
pub delta_q_res: u8,
/// `delta_lf_present` per §5.9.18.
pub delta_lf_present: bool,
/// `delta_lf_multi` per §5.9.18.
pub delta_lf_multi: bool,
/// `mono_chrome` per §5.5.2.
pub mono_chrome: bool,
/// `delta_lf_res` per §5.9.18.
pub delta_lf_res: u8,
/// `allow_screen_content_tools` per §5.9.2.
pub allow_screen_content_tools: bool,
/// `enable_filter_intra` per §5.5.1.
pub enable_filter_intra: bool,
/// `BitDepth` per §5.5.2.
pub bit_depth: u8,
/// `TxMode == TX_MODE_SELECT` per §5.9.21.
pub tx_mode_select: bool,
/// `reduced_tx_set` per §5.9.21.
pub reduced_tx_set: bool,
/// `enable_intra_edge_filter` per §5.5.2 — gates the §7.11.2.4
/// step-4 directional intra edge filter + upsample pre-pass.
/// Stored on the walker at [`PartitionWalker::decode_tile_syntax`]
/// entry and consulted by the §7.11.2.1 prediction write.
pub enable_intra_edge_filter: bool,
}
impl PartitionWalker {
/// Construct a fresh walker for a frame with the given mi-unit
/// extent and tile bounds. Every `MiSizes[]` cell starts at
/// [`BLOCK_INVALID`] (the §5.11.4 unfilled-cell sentinel).
///
/// Returns `None` if `mi_rows * mi_cols` overflows `usize` or
/// would exceed an arbitrary per-frame cap (the §5.9.5 parser
/// rejects such frame sizes earlier in a real decode anyway).
#[must_use]
pub fn new(mi_rows: u32, mi_cols: u32, geometry: TileGeometry) -> Option<Self> {
let area = (mi_rows as usize).checked_mul(mi_cols as usize)?;
// Cap at a large but not-overflowing value — the §6.10
// bitstream-conformance bounds keep a real frame's mi-grid
// well under this.
if area > (1 << 30) {
return None;
}
let mut mi_sizes: Vec<usize> = Vec::new();
mi_sizes.try_reserve_exact(area).ok()?;
mi_sizes.resize(area, BLOCK_INVALID);
let mut skips: Vec<u8> = Vec::new();
skips.try_reserve_exact(area).ok()?;
skips.resize(area, 0);
let mut skip_modes: Vec<u8> = Vec::new();
skip_modes.try_reserve_exact(area).ok()?;
skip_modes.resize(area, 0);
let mut cdef_idx: Vec<i8> = Vec::new();
cdef_idx.try_reserve_exact(area).ok()?;
// §5.11.55: every cell starts at -1, the "CDEF disabled for
// this block" sentinel. The per-superblock §5.11.55
// `clear_cdef(r, c)` call site below re-stamps the four (or
// one) 64x64 anchor cells; the fresh-walker pre-fill matches
// the spec's `cdef_idx[ r ][ c ] = -1` initialisation.
cdef_idx.resize(area, -1);
let mut is_inters: Vec<u8> = Vec::new();
is_inters.try_reserve_exact(area).ok()?;
is_inters.resize(area, 0);
let mut segment_ids: Vec<i32> = Vec::new();
segment_ids.try_reserve_exact(area).ok()?;
// §5.11.9 sentinel: every cell starts at -1 ("no neighbour
// information"). The first §5.11.9 [`Self::decode_segment_id`]
// call on a cell replaces the sentinel with a value in
// `0..MAX_SEGMENTS`.
segment_ids.resize(area, -1);
// §8.3.1 `AboveSegPredContext[ i ] = 0` / `LeftSegPredContext[ i
// ] = 0` for every i at tile entry. One slot per column / row;
// re-stamped by §5.11.19 calls as blocks decode.
let mut above_seg_pred_context: Vec<u8> = Vec::new();
above_seg_pred_context
.try_reserve_exact(mi_cols as usize)
.ok()?;
above_seg_pred_context.resize(mi_cols as usize, 0);
let mut left_seg_pred_context: Vec<u8> = Vec::new();
left_seg_pred_context
.try_reserve_exact(mi_rows as usize)
.ok()?;
left_seg_pred_context.resize(mi_rows as usize, 0);
// §6.10.2 / §8.3.2: `AboveLevelContext[ plane ][ i ] = 0` /
// `AboveDcContext[ plane ][ i ] = 0` for every column at tile
// entry (§5.11.2 `clear_above_context()`), and the `Left*`
// twins at superblock-row entry (`clear_left_context()` —
// the construction-time zero-fill covers the first row;
// [`Self::clear_txb_left_context`] covers subsequent rows).
// Three planes per axis, flat plane-major layout.
let cols3 = (mi_cols as usize).checked_mul(3)?;
let mut above_level_context: Vec<u8> = Vec::new();
above_level_context.try_reserve_exact(cols3).ok()?;
above_level_context.resize(cols3, 0);
let mut above_dc_context: Vec<u8> = Vec::new();
above_dc_context.try_reserve_exact(cols3).ok()?;
above_dc_context.resize(cols3, 0);
let rows3 = (mi_rows as usize).checked_mul(3)?;
let mut left_level_context: Vec<u8> = Vec::new();
left_level_context.try_reserve_exact(rows3).ok()?;
left_level_context.resize(rows3, 0);
let mut left_dc_context: Vec<u8> = Vec::new();
left_dc_context.try_reserve_exact(rows3).ok()?;
left_dc_context.resize(rows3, 0);
// §5.11.5 / §8.3.2: pre-fill `YModes[]` with `DC_PRED == 0`,
// the §3 intra-mode enumeration ordinal. An unavailable
// neighbour contributes the same `Intra_Mode_Context[ DC_PRED
// ] = 0` weight as a not-yet-decoded cell, so the initial-zero
// state is the natural identity for the
// `decode_intra_frame_y_mode` ctx walk.
let mut y_modes: Vec<u8> = Vec::new();
y_modes.try_reserve_exact(area).ok()?;
y_modes.resize(area, 0);
// §5.11.22 line 7: pre-fill `UVModes[]` with `DC_PRED == 0` (the
// §7.11.2.8 chroma `is_smooth` neighbour read treats a
// not-yet-decoded cell as non-smooth, matching the unavailable-
// neighbour fallback).
let mut uv_modes: Vec<u8> = Vec::new();
uv_modes.try_reserve_exact(area).ok()?;
uv_modes.resize(area, 0);
// §5.11.16 / §5.11.5: pre-fill `TxSizes[]` and `InterTxSizes[]`
// with `TX_4X4 = 0`. An unavailable neighbour contributes the
// same `Tx_Width[ TX_4X4 ] = 4` / `Tx_Height[ TX_4X4 ] = 4`
// weight to the §8.3.2 `tx_depth` / `txfm_split` ctx walks as
// a not-yet-decoded cell, so the initial-zero state is the
// natural identity for the walks consulting either grid.
let mut tx_sizes: Vec<u8> = Vec::new();
tx_sizes.try_reserve_exact(area).ok()?;
tx_sizes.resize(area, TX_4X4 as u8);
let mut inter_tx_sizes: Vec<u8> = Vec::new();
inter_tx_sizes.try_reserve_exact(area).ok()?;
inter_tx_sizes.resize(area, TX_4X4 as u8);
// §5.11.40 `TxTypes[][]` luma cache. Pre-fill is `DCT_DCT = 0`
// (the §5.11.40 `Lossless || txSzSqrUp > TX_32X32` fallback,
// which is also the natural identity for an unfilled cell).
let mut tx_types: Vec<u8> = Vec::new();
tx_types.try_reserve_exact(area).ok()?;
tx_types.resize(area, DCT_DCT as u8);
// §5.11.18 / §5.11.23: pre-fill `RefFrames[][][..]` with
// `[INTRA_FRAME = 0, NONE = -1]`, the §5.11.18 "unavailable
// neighbour" fallback. Two slots per `(row, col)` cell — slot 0
// is `RefFrame[0]`, slot 1 is `RefFrame[1]`.
let area2 = area.checked_mul(2)?;
let mut ref_frames: Vec<i8> = Vec::new();
ref_frames.try_reserve_exact(area2).ok()?;
ref_frames.resize(area2, 0);
// Stamp the NONE = -1 sentinel into slot 1 of every cell.
for cell in 0..area {
ref_frames[cell * 2 + 1] = -1;
}
// §5.11.46 / §5.11.49: pre-fill `PaletteSizes[plane][r][c]` with
// 0 (the §5.11.22 line 11-12 / §5.11.49 `aboveN = 0` / `leftN = 0`
// initialisation — a not-yet-decoded cell contributes the same
// "no palette" weight as an unavailable neighbour). Three planes
// per cell (Y / U / V).
let area3 = area.checked_mul(3)?;
let mut palette_sizes: Vec<u8> = Vec::new();
palette_sizes.try_reserve_exact(area3).ok()?;
palette_sizes.resize(area3, 0);
// §5.11.46 / §5.11.49: pre-fill `PaletteColors[plane][r][c][..]`
// with 0. Unused slots stay at 0; the §5.11.49 cache walk reads
// only the first `PaletteSizes[plane][..][..]` entries per cell
// so the pre-fill value is unobserved on the conformant path.
// r453 — the `PaletteColors[]` grid (the walker's LARGEST
// buffer: `3 * area * PALETTE_COLORS` u16) is materialized
// lazily by [`Self::ensure_palette_colors`] the first time a
// §5.11.46 palette actually lands; the readers treat the
// empty grid as the all-zero pre-fill.
let palette_colors: Vec<u16> = Vec::new();
// §5.11.5 / §5.11.31 / §7.10: pre-fill `Mvs[r][c][list][comp]`
// with `0`. Four `i16` slots per `(row, col)` cell — two lists
// × two components. The §7.10.2.7 gate (`IsInters[mvRow][mvCol]
// == 0` ⇒ return) ensures the pre-fill value is unobservable
// on the conformant path until `assign_mv` (next-arc) writes
// it.
let area4 = area.checked_mul(4)?;
let mut mvs: Vec<i16> = Vec::new();
mvs.try_reserve_exact(area4).ok()?;
mvs.resize(area4, 0);
// §5.11.29 / §8.3.2: pre-fill `CompGroupIdxs[r][c]` with `0`,
// the §5.11.29 default `comp_group_idx = 0` initialiser (also
// the natural identity for an unavailable neighbour — the
// §8.3.2 `comp_group_idx` ctx body only consults this grid on
// the `!AboveSingle` / `!LeftSingle` arms which fire on
// compound-coded neighbours, never on intra / single-pred or
// unavailable cells).
let mut comp_group_idxs: Vec<u8> = Vec::new();
comp_group_idxs.try_reserve_exact(area).ok()?;
comp_group_idxs.resize(area, 0);
// §5.11.29 / §8.3.2: pre-fill `CompoundIdxs[r][c]` with `1`,
// the §5.11.29 default `compound_idx = 1` initialiser. The
// §8.3.2 `compound_idx` ctx body only consults this grid on
// the `!AboveSingle` / `!LeftSingle` arms (same identity
// story as `CompGroupIdxs` above).
let mut compound_idxs: Vec<u8> = Vec::new();
compound_idxs.try_reserve_exact(area).ok()?;
compound_idxs.resize(area, 1);
// §5.11.29 / §5.11.28 / §5.11.27 inter side-data grids — the
// already-resolved decode outputs the §5.11.33 frame walk
// dispatches compound / inter-intra / warped leaves from. Each
// is read only at a leaf-origin cell whose `RefFrames[]` /
// `motion_modes` gate selects the matching §7.11.3 arm; the
// pre-fill values are the §5.11.29 / §5.11.28 / §5.11.27
// short-circuit defaults (never read on a single-ref / intra
// cell). See the field docs for the per-grid semantics.
let mut compound_types: Vec<u8> = Vec::new();
compound_types.try_reserve_exact(area).ok()?;
compound_types.resize(area, crate::inter_pred::COMPOUND_AVERAGE);
let mut compound_wedge_indices: Vec<u8> = Vec::new();
compound_wedge_indices.try_reserve_exact(area).ok()?;
compound_wedge_indices.resize(area, 0);
let mut compound_wedge_signs: Vec<u8> = Vec::new();
compound_wedge_signs.try_reserve_exact(area).ok()?;
compound_wedge_signs.resize(area, 0);
let mut compound_mask_types: Vec<u8> = Vec::new();
compound_mask_types.try_reserve_exact(area).ok()?;
compound_mask_types.resize(area, 0);
let mut interintra_modes: Vec<u8> = Vec::new();
interintra_modes.try_reserve_exact(area).ok()?;
interintra_modes.resize(area, 0);
let mut wedge_interintras: Vec<u8> = Vec::new();
wedge_interintras.try_reserve_exact(area).ok()?;
wedge_interintras.resize(area, 0);
let mut interintra_wedge_indices: Vec<u8> = Vec::new();
interintra_wedge_indices.try_reserve_exact(area).ok()?;
interintra_wedge_indices.resize(area, 0);
let mut motion_modes: Vec<u8> = Vec::new();
motion_modes.try_reserve_exact(area).ok()?;
motion_modes.resize(area, MOTION_MODE_SIMPLE);
// §5.11.5 `DeltaLFs[ r ][ c ][ i ]` per-mi snapshot grid —
// `area * FRAME_LF_COUNT` slots, pre-filled `0` (the §5.9.18
// zero default). Stamped per block via `stamp_delta_lfs`.
let mut delta_lfs: Vec<i8> = Vec::new();
delta_lfs.try_reserve_exact(area * FRAME_LF_COUNT).ok()?;
delta_lfs.resize(area * FRAME_LF_COUNT, 0);
// §5.11.x / §8.3.2: pre-fill `InterpFilters[r][c][0..2]` with
// `EIGHTTAP = 0`, the spec's `interp_filter[ dir ] = EIGHTTAP`
// fallback (set when `needs_interp_filter() == 0`). The §8.3.2
// ctx walk only consults this grid on cells whose neighbour's
// `RefFrames[..][..][0]` matches the current block's
// `RefFrame[0]`; intra / unavailable cells short-circuit to the
// `aboveType = 3` / `leftType = 3` sentinel and never read the
// grid value, so the pre-fill is unobserved on the conformant
// path for the first inter block.
let mut interp_filters: Vec<u8> = Vec::new();
interp_filters.try_reserve_exact(area2).ok()?;
interp_filters.resize(area2, EIGHTTAP);
// §6.10.3 `BlockDecoded[ plane ][ y ][ x ]` — a constant-size
// superblock-local grid (`3 * BD_STRIDE * BD_STRIDE`). The
// fresh-walker fill is `0`; the §5.11.3
// [`Self::clear_block_decoded_flags`] reset stamps the spec's
// border-vs-interior pattern before each superblock decodes.
let bd_len = 3 * BD_STRIDE * BD_STRIDE;
let mut block_decoded: Vec<u8> = Vec::new();
block_decoded.try_reserve_exact(bd_len).ok()?;
block_decoded.resize(bd_len, 0);
// §5.11.18 per-cell `AvailU` / `AvailL` for the construction
// geometry (rebuilt on every [`Self::begin_tile`]).
let mut walk_avail_u: Vec<u8> = Vec::new();
walk_avail_u.try_reserve_exact(area).ok()?;
let mut walk_avail_l: Vec<u8> = Vec::new();
walk_avail_l.try_reserve_exact(area).ok()?;
for r in 0..mi_rows {
for c in 0..mi_cols {
walk_avail_u.push(u8::from(geometry.is_inside(r as i32 - 1, c as i32)));
walk_avail_l.push(u8::from(geometry.is_inside(r as i32, c as i32 - 1)));
}
}
Some(Self {
mi_rows,
mi_cols,
geometry,
enable_intra_edge_filter: false,
walk_avail_u,
walk_avail_l,
inter_pred_scratch: [Vec::new(), Vec::new(), Vec::new()],
// r464 — materialised lazily by the first WARPED_CAUSAL
// stamp (`ensure_local_warp_params`): 24 bytes per mi, 18
// MB of zeros per walker at 12 MP that an intra frame
// never reads.
local_warp_params: Vec::new(),
local_warp_valid: {
let mut v: Vec<u8> = Vec::new();
v.try_reserve_exact(area).ok()?;
v.resize(area, 0);
v
},
mi_sizes,
skips,
skip_modes,
current_q_index: 0,
// Callers that drive `decode_partition_syntax` directly
// (without the §5.11.2 tile loop) get the historical
// behaviour of their first leaf consuming the deltas.
read_deltas_pending: true,
current_delta_lf: [0; FRAME_LF_COUNT],
delta_lfs,
cdef_idx,
is_inters,
segment_ids,
above_seg_pred_context,
left_seg_pred_context,
above_level_context,
above_dc_context,
left_level_context,
left_dc_context,
y_modes,
uv_modes,
tx_sizes,
inter_tx_sizes,
tx_types,
ref_frames,
palette_sizes,
palette_colors,
mvs,
comp_group_idxs,
compound_idxs,
interp_filters,
compound_types,
compound_wedge_indices,
compound_wedge_signs,
compound_mask_types,
interintra_modes,
wedge_interintras,
interintra_wedge_indices,
motion_modes,
blocks: Vec::new(),
lr_origin: (0, 0),
// §7.12.3 step-3 `CurrFrame[plane]` buffers — allocated
// lazily on the first per-plane merge call (see
// [`Self::ensure_curr_frame_plane`]).
curr_frame: [None, None, None],
luma_overhang: None,
// §5.11.2 tile-entry reset: RefLrWiener[plane][pass][j] =
// Wiener_Taps_Mid[j], RefSgrXqd[plane][pass] =
// Sgrproj_Xqd_Mid[pass]. The fresh-walker pre-fill matches
// the spec's `decode_tile()` initialisation; the
// per-superblock-row reset is surfaced via
// [`Self::reset_lr_refs`].
ref_lr_wiener: [[crate::loop_restoration::WIENER_TAPS_MID; 2]; 3],
ref_sgr_xqd: [crate::loop_restoration::SGRPROJ_XQD_MID; 3],
// §5.11.58 LrType/LrWiener/LrSgrSet/LrSgrXqd grids — lazily
// allocated on the first §5.11.57 [`Self::read_lr`] unit
// decode per plane (extents come from the per-frame
// [`LrParams`] at decode time).
lr_units: [None, None, None],
// §6.10.3 `BlockDecoded[]` — fresh-walker zero fill; the
// §5.11.3 [`Self::clear_block_decoded_flags`] reset stamps
// the border-vs-interior pattern per superblock.
block_decoded,
// §5.11.35 `MaxLumaW` / `MaxLumaH` — set per luma TU; `0`
// until the first plane-0 transform-block emit.
max_luma_w: 0,
max_luma_h: 0,
// §5.11.35 per-block palette-prediction state — reset at
// every `decode_block_syntax` entry.
current_palette_size_y: 0,
current_palette_size_uv: 0,
current_color_map_y: None,
current_color_map_uv: None,
})
}
/// §5.11.2 `decode_tile()` loop-restoration reference reset — sets
/// `RefLrWiener[ plane ][ pass ][ j ] = Wiener_Taps_Mid[ j ]` and
/// `RefSgrXqd[ plane ][ pass ] = Sgrproj_Xqd_Mid[ pass ]` for every
/// plane / pass. The walker pre-fills these at construction, so a
/// single-tile decode never needs to call this; multi-tile decodes
/// (or a re-used walker) call it at each tile's entry to reset the
/// running subexp references the §5.11.58 `read_lr_unit` reads.
/// r475 — declare this walker TILE-SIZED at frame mi origin
/// `(mi_row, mi_col)`: §5.11.57 `read_lr` then indexes the
/// frame's restoration-unit grid from that origin (the grid
/// itself keeps the frame's unit extents, which `LrParams`
/// carries). Every other §5.11 process is tile-scoped by the
/// spec, so the walk is the frame walk's.
pub fn set_lr_origin(&mut self, mi_row: u32, mi_col: u32) {
self.lr_origin = (mi_row, mi_col);
}
/// r475 — stitch a tile-sized walker's `CurrFrame[ plane ]` planes
/// into this frame-scope walker's planes at frame mi position
/// `(dst_row4, dst_col4)` (the planes are allocated at the frame
/// extent on first use). Tile boundaries are superblock aligned,
/// so the chroma rect origin is exact under any subsampling.
pub(crate) fn import_curr_frame_rect_from(
&mut self,
src: &Self,
dst_row4: u32,
dst_col4: u32,
sub_x: u8,
sub_y: u8,
) {
for plane in 0..3usize {
let Some(sp) = src.curr_frame[plane].as_ref() else {
continue;
};
let (ssx, ssy) = if plane == 0 { (0, 0) } else { (sub_x, sub_y) };
let rows = (self.mi_rows * (MI_SIZE as u32)) >> ssy;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> ssx;
self.ensure_curr_frame_plane(plane, rows, cols);
let Some(dp) = self.curr_frame[plane].as_mut() else {
continue;
};
let y0 = ((dst_row4 * (MI_SIZE as u32)) >> ssy) as usize;
let x0 = ((dst_col4 * (MI_SIZE as u32)) >> ssx) as usize;
let w = (sp.cols as usize).min((dp.cols as usize).saturating_sub(x0));
let h = (sp.rows as usize).min((dp.rows as usize).saturating_sub(y0));
let (sc, dc) = (sp.cols as usize, dp.cols as usize);
for y in 0..h {
dp.samples[(y0 + y) * dc + x0..(y0 + y) * dc + x0 + w]
.copy_from_slice(&sp.samples[y * sc..y * sc + w]);
}
}
}
/// r475 — import the §5.11.58 restoration units a tile-sized
/// walker decoded for the frame mi rect `[ mi_row0, mi_row1 ) ×
/// [ mi_col0, mi_col1 )`: the unit ranges the §5.11.57 window
/// arithmetic visits over that rect (the union over its
/// superblocks — `div_ceil` is monotone, so the union is one
/// range per axis).
#[allow(clippy::too_many_arguments)]
pub(crate) fn import_lr_units_from(
&mut self,
src: &Self,
mi_row0: u32,
mi_row1: u32,
mi_col0: u32,
mi_col1: u32,
params: &LrParams,
) {
for plane in 0..params.num_planes.min(3) {
if params.frame_restoration_type[plane] == RESTORE_NONE {
continue;
}
let Some(sg) = src.lr_units[plane].as_ref() else {
continue;
};
let sub_x = if plane == 0 { 0 } else { params.subsampling_x };
let sub_y = if plane == 0 { 0 } else { params.subsampling_y };
let unit_size = params.loop_restoration_size[plane];
if unit_size == 0 {
continue;
}
let needs_alloc = match self.lr_units.get(plane).and_then(Option::as_ref) {
Some(g) => g.unit_rows != sg.unit_rows || g.unit_cols != sg.unit_cols,
None => true,
};
if needs_alloc {
self.lr_units[plane] = Some(LrUnitGrid {
unit_rows: sg.unit_rows,
unit_cols: sg.unit_cols,
units: vec![LrUnit::NONE; (sg.unit_rows as usize) * (sg.unit_cols as usize)],
});
}
let mi_size_shift_y = (MI_SIZE as u32) >> sub_y;
let row_start = (mi_row0 * mi_size_shift_y).div_ceil(unit_size);
let row_end = core::cmp::min(
sg.unit_rows,
(mi_row1 * mi_size_shift_y).div_ceil(unit_size),
);
let mi_size_shift_x = (MI_SIZE as u32) >> sub_x;
let (numerator, denominator) = if params.use_superres {
(
mi_size_shift_x * params.superres_denom,
unit_size * SUPERRES_NUM,
)
} else {
(mi_size_shift_x, unit_size)
};
let col_start = (mi_col0 * numerator).div_ceil(denominator);
let col_end = core::cmp::min(sg.unit_cols, (mi_col1 * numerator).div_ceil(denominator));
let Some(dg) = self.lr_units[plane].as_mut() else {
continue;
};
for ur in row_start..row_end {
for uc in col_start..col_end {
let idx = (ur as usize) * (sg.unit_cols as usize) + uc as usize;
if let (Some(d), Some(&v)) = (dg.units.get_mut(idx), sg.units.get(idx)) {
*d = v;
}
}
}
}
}
pub fn reset_lr_refs(&mut self) {
self.ref_lr_wiener = [[crate::loop_restoration::WIENER_TAPS_MID; 2]; 3];
self.ref_sgr_xqd = [crate::loop_restoration::SGRPROJ_XQD_MID; 3];
}
/// `CompGroupIdxs[ r ][ c ]` accessor — returns the flat grid the
/// §5.11.29 `read_compound_type` body stamps and the §8.3.2
/// `comp_group_idx` ctx walk reads. Indexed `r * MiCols + c`.
/// Pre-fill is `0` (the §5.11.29 default `comp_group_idx`).
#[must_use]
pub fn comp_group_idxs(&self) -> &[u8] {
&self.comp_group_idxs
}
/// `CompoundIdxs[ r ][ c ]` accessor — returns the flat grid the
/// §5.11.29 `read_compound_type` body stamps and the §8.3.2
/// `compound_idx` ctx walk reads. Indexed `r * MiCols + c`.
/// Pre-fill is `1` (the §5.11.29 default `compound_idx`).
#[must_use]
pub fn compound_idxs(&self) -> &[u8] {
&self.compound_idxs
}
/// `InterpFilters[ r ][ c ][ dir ]` accessor — returns the flat
/// grid the §5.11.x interpolation-filter reader stamps and the
/// §8.3.2 `interp_filter` ctx walk reads. Two slots per cell, so
/// the flat index for `(r, c, dir)` is `(r * MiCols + c) * 2 + dir`
/// (`dir == 0` = horizontal, `dir == 1` = vertical). Pre-fill is
/// `EIGHTTAP = 0`.
#[must_use]
pub fn interp_filters(&self) -> &[u8] {
&self.interp_filters
}
/// `MiRows` accessor for tests / debugging.
#[must_use]
pub fn mi_rows(&self) -> u32 {
self.mi_rows
}
/// `MiCols` accessor for tests / debugging.
#[must_use]
pub fn mi_cols(&self) -> u32 {
self.mi_cols
}
/// Tile-geometry accessor.
#[must_use]
pub fn geometry(&self) -> TileGeometry {
self.geometry
}
/// View of the §6.10.4 `MiSizes[]` grid after the walk. Indexed
/// row-major: `mi_sizes()[ r * MiCols + c ]`. Cells that no leaf
/// covered carry [`BLOCK_INVALID`].
#[must_use]
pub fn mi_sizes(&self) -> &[usize] {
&self.mi_sizes
}
/// Row-major `CurrFrame[ plane ]` view (av1-spec §7.12.3 step-3 /
/// §7.11.x intra / §7.9 inter / §7.14 LF / §7.15 CDEF / §7.16
/// upscaling / §7.17 LR). Returns `None` until the first
/// §7.12.3 step-3 merge call on `plane` has fired (the buffer is
/// allocated lazily).
///
/// Indexed `samples()[ y * cols + x ]` against the `(rows, cols)`
/// pair returned by [`Self::curr_frame_dims`]. Sample values are
/// `i32` post-`Clip1` (so always in `0..=(1 << BitDepth) - 1` for
/// the `BitDepth` the §7.12.3 step-3 merge was driven with).
///
/// `plane` is `0` (Y), `1` (Cb), `2` (Cr); out-of-range `plane`
/// returns `None`.
#[must_use]
pub fn curr_frame(&self, plane: usize) -> Option<&[i32]> {
self.curr_frame
.get(plane)
.and_then(|p| p.as_ref().map(|cp| cp.samples.as_slice()))
}
/// r453 — MOVE the per-plane `CurrFrame[ plane ]` buffer out of
/// the walker once the tile walk is complete: the §7.14 / §7.15 /
/// §7.16 / §7.17 in-loop passes operate on caller-owned buffers
/// and nothing in the walker reads `CurrFrame` after the walk, so
/// the frame driver takes ownership instead of copying the
/// full-frame `i32` buffer. Returns `(rows, cols, samples)`; the
/// plane's slot is left unallocated (as before the first merge).
pub(crate) fn take_curr_frame_plane(&mut self, plane: usize) -> Option<(u32, u32, Vec<i32>)> {
self.curr_frame
.get_mut(plane)
.and_then(|p| p.take())
.map(|cp| (cp.rows, cp.cols, cp.samples))
}
/// `(rows, cols)` of the per-plane `CurrFrame[ plane ]` buffer.
/// Returns `None` if the buffer hasn't been allocated yet (no
/// merge call has fired on that plane). `plane` is `0` (Y), `1`
/// (Cb), `2` (Cr); out-of-range `plane` returns `None`.
#[must_use]
pub fn curr_frame_dims(&self, plane: usize) -> Option<(u32, u32)> {
self.curr_frame
.get(plane)
.and_then(|p| p.as_ref().map(|cp| (cp.rows, cp.cols)))
}
/// Ensure the per-plane `CurrFrame[ plane ]` buffer is allocated
/// at the §5.11.34-derived `(rows, cols)` extent. Idempotent: a
/// second call with the same `(rows, cols)` is a no-op; a call
/// with smaller dimensions than the existing allocation is a
/// no-op (the buffer is sized for the full frame at first
/// allocation, so subsequent per-TU merges always fit). The
/// initial fill is `0` per the §7.12.3 step-3 additive-merge
/// identity (`Clip1(0 + Residual[i][j])` before any §7.11.x
/// prediction writer has fired).
/// r408 — route a clipped plane-0 write into the §7.11.5
/// [`LumaOverhang`] side store (lazily allocated / re-shaped to
/// the current luma extents). Returns `None` when `(y, x)` is
/// inside the main buffer or past the `LUMA_OH` headroom.
fn luma_overhang_slot(&mut self, y: u32, x: u32) -> Option<&mut i32> {
let (rows, cols) = {
let b = self.curr_frame[0].as_ref()?;
(b.rows, b.cols)
};
if y < rows && x < cols {
return None;
}
let needs_alloc = match self.luma_overhang.as_ref() {
Some(oh) => oh.rows != rows || oh.cols != cols,
None => true,
};
if needs_alloc {
self.luma_overhang = LumaOverhang::new(rows, cols);
}
self.luma_overhang.as_mut()?.slot(y, x)
}
fn ensure_curr_frame_plane(&mut self, plane: usize, rows: u32, cols: u32) {
if plane >= 3 {
return;
}
if self.curr_frame[plane].is_some() {
return;
}
let len = (rows as usize).saturating_mul(cols as usize);
let mut samples: Vec<i32> = Vec::new();
if samples.try_reserve_exact(len).is_err() {
return;
}
samples.resize(len, 0);
self.curr_frame[plane] = Some(CurrFramePlane {
rows,
cols,
samples,
});
}
/// §7.11.2.1 general intra prediction (av1-spec p.241-243) — write
/// the per-TU prediction `pred[i][j]` into
/// `CurrFrame[plane][y + i][x + j]` for `(i, j) ∈ [0, h) × [0, w)`,
/// **before** the §7.12.3 step-3 residual merge
/// ([`Self::curr_frame_step3_merge`]) adds the inverse-transformed
/// residual on top. Together the two calls realise the §5.11.35
/// `reconstruct()` body for an intra transform block
/// (`CurrFrame = Clip1( pred + residual )`).
///
/// The §7.11.2.1 `AboveRow[ i ]` / `LeftCol[ i ]` neighbour arrays
/// (and the `AboveRow[ -1 ]` / `LeftCol[ -1 ]` corner) are derived
/// from the **already-reconstructed** samples in `CurrFrame[plane]`
/// per the spec's four-arm `haveAbove` / `haveLeft` dispatch:
///
/// * `haveAbove == 0 && haveLeft == 1` ⇒ `AboveRow[ i ] =
/// CurrFrame[plane][y][x-1]` (the left edge).
/// * `haveAbove == 0 && haveLeft == 0` ⇒ `AboveRow[ i ] =
/// (1 << (BitDepth-1)) - 1`; `LeftCol[ i ] = (1 << (BitDepth-1))
/// + 1` (the mid-grey defaults).
/// * otherwise ⇒ `aboveLimit = Min( maxX, x + (haveAboveRight ?
/// 2*w : w) - 1 )`, `AboveRow[ i ] = CurrFrame[plane][y-1][
/// Min(aboveLimit, x+i)]` (the `LeftCol` half mirrors with
/// `leftLimit` / `haveBelowLeft`).
///
/// Coordinates are in the per-plane sample space (post-subsampling
/// for chroma); `max_x` / `max_y` are `((MiCols * MI_SIZE) >> subX)
/// - 1` / `((MiRows * MI_SIZE) >> subY) - 1` per §7.11.2.1.
///
/// `mode` is the §6.10.x intra-prediction mode (`DC_PRED ..
/// PAETH_PRED`). The §7.11.2.3 recursive (filter-intra) arm, the
/// §7.11.2.10-12 edge upsample / filter steps, and a non-zero
/// `angle_delta` are **not** applied here — the directional arm runs
/// with `upsampleAbove = upsampleLeft = 0` and the block's signalled
/// `angle_delta`, covering the no-edge-filter directional sub-arm.
/// The neighbour buffers are head-extended (`[corner, corner,
/// AboveRow[0..w+h]]`) so the existing §7.11.2.{2,4,5,6} kernels —
/// [`predict_intra_dc_pred`], [`predict_intra_v_pred`],
/// [`predict_intra_h_pred`], [`predict_intra_paeth_pred`], the three
/// SMOOTH variants, and [`predict_intra_d_mode`] — can be dispatched
/// uniformly.
///
/// Lazily allocates the per-plane buffer (so a prediction-first TU
/// on a fresh plane initialises it). Out-of-buffer prediction writes
/// past the right / bottom frame edge are clipped silently. Returns
/// silently for `plane >= 3` or an out-of-range `mode`.
#[allow(clippy::too_many_arguments)]
fn predict_intra_into_curr_frame(
&mut self,
plane: u8,
start_x: u32,
start_y: u32,
tx_sz: usize,
mode: usize,
angle_delta: i32,
bit_depth: u8,
sub_x: u8,
sub_y: u8,
have_above: bool,
have_left: bool,
have_above_right: bool,
have_below_left: bool,
// §7.11.2.4 step-4 directional pre-pass inputs. `filter_type`
// is the §7.11.2.8 `get_filter_type( plane )` output the caller
// derived from the neighbour smooth-mode grids; it is only read
// when `enable_intra_edge_filter` is set and `mode` is one of
// the six directional D-modes.
enable_intra_edge_filter: bool,
filter_type: u8,
// §7.11.2.1 first dispatch arm — `use_filter_intra` is luma-only
// (`plane == 0`); when `Some(mode)` the §7.11.2.3 recursive
// prediction process replaces the `mode`-driven dispatch (the
// §7.11.2.4 step-4 directional pre-pass is skipped, filter-intra
// not being a directional mode). `None` ⇒ the normal mode
// dispatch below runs.
filter_intra_mode: Option<usize>,
) {
if (plane as usize) >= 3 || mode >= INTRA_MODES {
return;
}
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
if w == 0 || h == 0 || w > 64 || h > 64 {
return;
}
// Per-plane buffer extent — `(MiRows * MI_SIZE) >> subY` rows by
// `(MiCols * MI_SIZE) >> subX` cols (§7.11.2.1 `maxX` / `maxY`
// are these minus one). Allocate on first touch so a
// prediction-first TU initialises the plane.
let rows = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
self.ensure_curr_frame_plane(plane as usize, rows, cols);
let Some(buf) = self.curr_frame[plane as usize].as_ref() else {
return;
};
let buf_rows = buf.rows;
let buf_cols = buf.cols;
let max_x = buf_cols.saturating_sub(1);
let max_y = buf_rows.saturating_sub(1);
let read = |yy: u32, xx: u32| -> u16 {
let idx = (yy as usize) * (buf_cols as usize) + (xx as usize);
buf.samples[idx] as u16
};
let Some(pred) = Self::intra_predict_block_samples(
&read,
max_x,
max_y,
start_x,
start_y,
tx_sz,
mode,
angle_delta,
bit_depth,
have_above,
have_left,
have_above_right,
have_below_left,
enable_intra_edge_filter,
filter_type,
// §7.11.2.1 first dispatch arm — `use_filter_intra` is
// luma-only; the chroma callers' `Some(_)` collapses to
// the normal mode dispatch exactly as before the r417
// core extraction.
filter_intra_mode.filter(|_| plane == 0),
) else {
return;
};
let Some(buf) = self.curr_frame[plane as usize].as_mut() else {
return;
};
let cols_buf = buf.cols;
let rows_buf = buf.rows;
// r408: a §5.11.35 TU legally overhangs the mi-padded grid;
// §7.11.5 CfL reads the overhung luma samples back (spec
// CurrFrame is unbounded), so plane-0 out-of-buffer cells go
// to the [`LumaOverhang`] side store instead of being dropped.
let mut overhang: Vec<(u32, u32, i32)> = Vec::new();
for i in 0..h as u32 {
let dst_y = start_y + i;
for j in 0..w as u32 {
let dst_x = start_x + j;
if dst_y >= rows_buf || dst_x >= cols_buf {
if plane == 0 {
overhang.push((dst_y, dst_x, pred[(i as usize) * w + (j as usize)] as i32));
}
continue;
}
let idx = (dst_y as usize) * (cols_buf as usize) + (dst_x as usize);
buf.samples[idx] = pred[(i as usize) * w + (j as usize)] as i32;
}
}
for (y, x, v) in overhang {
if let Some(slot) = self.luma_overhang_slot(y, x) {
*slot = v;
}
}
}
/// r417 — buffer-parameterised §7.11.2 intra prediction for the
/// encoder's search scratch: the §5.11.33 `IsInterIntra` arm runs
/// `predict_intra` into `CurrFrame[ plane ]` BEFORE
/// `predict_inter` reads it back as the §7.11.3.14 blend's
/// `pred1`, and the encoder-side search must realise the SAME
/// §7.11.2 prediction into its own scratch planes before invoking
/// the shared inter-intra leaf driver. Runs the exact
/// [`Self::predict_intra_into_curr_frame`] core
/// ([`Self::intra_predict_block_samples`] — one code path, no
/// re-derivation), reading neighbour samples through `neigh`
/// (absolute per-plane sample coordinates, pre-clamped by the core
/// to `(max_y, max_x)`) and writing the `w × h` prediction into
/// `out` (row-major at `out_stride`). Writes past `(max_y, max_x)`
/// are dropped silently — the encoder's leaves are §5.11.4
/// fully-inside blocks, so nothing overhangs (the walker's
/// [`LumaOverhang`] side store has no encoder-search counterpart).
#[allow(clippy::too_many_arguments)]
pub(crate) fn predict_intra_into_u16_plane(
neigh: &dyn Fn(u32, u32) -> u16,
out: &mut [u16],
out_stride: usize,
max_x: u32,
max_y: u32,
start_x: u32,
start_y: u32,
tx_sz: usize,
mode: usize,
angle_delta: i32,
bit_depth: u8,
have_above: bool,
have_left: bool,
have_above_right: bool,
have_below_left: bool,
enable_intra_edge_filter: bool,
filter_type: u8,
filter_intra_mode: Option<usize>,
) {
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
let Some(pred) = Self::intra_predict_block_samples(
neigh,
max_x,
max_y,
start_x,
start_y,
tx_sz,
mode,
angle_delta,
bit_depth,
have_above,
have_left,
have_above_right,
have_below_left,
enable_intra_edge_filter,
filter_type,
filter_intra_mode,
) else {
return;
};
for i in 0..h as u32 {
let dst_y = start_y + i;
if dst_y > max_y {
continue;
}
for j in 0..w as u32 {
let dst_x = start_x + j;
if dst_x > max_x {
continue;
}
let idx = (dst_y as usize) * out_stride + (dst_x as usize);
if let Some(slot) = out.get_mut(idx) {
*slot = pred[(i as usize) * w + (j as usize)];
}
}
}
}
/// §7.11.2 block-prediction core shared by
/// [`Self::predict_intra_into_curr_frame`] (the §5.11.35 decode
/// walk, reading / writing the walker's own `CurrFrame[ plane ]`)
/// and [`Self::predict_intra_into_u16_plane`] (the r417
/// buffer-parameterised encoder-search variant): derive the
/// §7.11.2.1 `AboveRow[ ]` / `LeftCol[ ]` neighbour arrays through
/// `read` (absolute per-plane sample coordinates, clamped here to
/// `(max_y, max_x)`), run the §7.11.2.4 step-4 directional
/// pre-pass when it applies, and dispatch the §7.11.2.{2,3,4,5,6}
/// kernels. Returns the `w × h` row-major prediction, or `None`
/// on an out-of-domain configuration (the callers' silent-return
/// arm).
#[allow(clippy::too_many_arguments)]
fn intra_predict_block_samples(
read: &dyn Fn(u32, u32) -> u16,
max_x: u32,
max_y: u32,
start_x: u32,
start_y: u32,
tx_sz: usize,
mode: usize,
angle_delta: i32,
bit_depth: u8,
have_above: bool,
have_left: bool,
have_above_right: bool,
have_below_left: bool,
enable_intra_edge_filter: bool,
filter_type: u8,
filter_intra_mode: Option<usize>,
) -> Option<Vec<u16>> {
if mode >= INTRA_MODES {
return None;
}
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
if w == 0 || h == 0 || w > 64 || h > 64 {
return None;
}
let log2_w = w.trailing_zeros();
let log2_h = h.trailing_zeros();
// §7.11.2.1 availability (`haveAbove` / `haveLeft`) is supplied
// by the §5.11.35 caller as `(AvailU/AvailUChroma) || y > 0` /
// `(AvailL/AvailLChroma) || x > 0`; `haveAboveRight` /
// `haveBelowLeft` come from the §6.10.3 `BlockDecoded[]` border
// reads. Guard the corner reads against a TU that nominally has
// a neighbour but sits at the plane's `0` edge (defensive — the
// caller's gate should already exclude it).
let have_above = have_above && start_y > 0;
let have_left = have_left && start_x > 0;
let read = |yy: u32, xx: u32| -> u16 { read(yy.min(max_y), xx.min(max_x)) };
let half = 1u16 << (bit_depth - 1);
// §7.11.2.1 `AboveRow[ -1 ]` corner (offset `1` in the
// head-extended buffer; `LeftCol[ -1 ]` equals it).
let corner: u16 = if have_above && have_left {
read(start_y - 1, start_x - 1)
} else if have_above {
read(start_y - 1, start_x)
} else if have_left {
read(start_y, start_x - 1)
} else {
half
};
let span = w + h;
// Head-extended representation: index `-2` ⇒ offset 0, index
// `-1` ⇒ offset 1, index `k >= 0` ⇒ offset `k + 2`. The
// §7.11.2.4 directional kernel reads `above[ k + 2 ]`; offset 0
// is consulted on the `upsampleAbove == 1` arm. The buffer is
// sized `2 * span + 2` so the §7.11.2.11 in-place 2x upsample
// (which needs `>= 2 * num_px + 2` cells, `num_px <= span`) has
// room. The un-upsampled directional / DC / smooth / PAETH
// arms only read offsets `1 .. span + 1`.
let ext_cap = 2 * span + 2;
let mut above_ext = vec![0u16; ext_cap];
above_ext[0] = corner;
above_ext[1] = corner;
if !have_above && have_left {
let v = read(start_y, start_x - 1);
for slot in above_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else if !have_above && !have_left {
let v = ((1u32 << (bit_depth - 1)) - 1) as u16;
for slot in above_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else {
let above_limit =
max_x.min(start_x + (if have_above_right { 2 * w } else { w }) as u32 - 1);
for (i, slot) in above_ext.iter_mut().skip(2).take(span).enumerate() {
*slot = read(start_y - 1, above_limit.min(start_x + i as u32));
}
}
let mut left_ext = vec![0u16; ext_cap];
left_ext[0] = corner;
left_ext[1] = corner;
if !have_left && have_above {
let v = read(start_y - 1, start_x);
for slot in left_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else if !have_left && !have_above {
let v = ((1u32 << (bit_depth - 1)) + 1) as u16;
for slot in left_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else {
let left_limit =
max_y.min(start_y + (if have_below_left { 2 * h } else { h }) as u32 - 1);
for (i, slot) in left_ext.iter_mut().skip(2).take(span).enumerate() {
*slot = read(left_limit.min(start_y + i as u32), start_x - 1);
}
}
// §7.11.2.4 step-4 directional pre-pass (filter corner + intra
// edge filter + intra edge upsample). Computes `upsampleAbove`
// / `upsampleLeft` (default `0`) and rewrites `above_ext` /
// `left_ext` in place. Only runs for the six directional
// D-modes (`D45_PRED..=D67_PRED`); the degenerate V_PRED /
// H_PRED (`pAngle == 90` / `180`) and the non-directional modes
// skip it. The walker derives `maxX` / `maxY` as the per-plane
// buffer extents (`max_x` / `max_y` above) — the spec's
// `( maxX - x + 1 )` / `( maxY - y + 1 )` numPx clamps map to
// those.
let mut upsample_above: u8 = 0;
let mut upsample_left: u8 = 0;
// §7.11.2.1: `is_directional_mode` spans V_PRED..=D67_PRED — a
// V_PRED / H_PRED with a non-zero angle delta is directional
// (`pAngle = 90/180 ± 3 * delta`), runs the step-4 pre-pass,
// and projects through the §7.11.2.4 kernel like any D-mode.
// Only the exact `pAngle == 90` / `pAngle == 180` cases take
// the step-10/11 plain copies (with no pre-pass).
if (V_PRED..=D67_PRED).contains(&mode) {
let p_angle = MODE_TO_ANGLE[mode] + angle_delta * ANGLE_STEP;
if enable_intra_edge_filter && p_angle != 90 && p_angle != 180 {
// §7.11.2.4 step-4 filter corner (only `90 < pAngle <
// 180 && w + h >= 24`). Replaces both `AboveRow[-1]` and
// `LeftCol[-1]` (offset 1 in both buffers).
if p_angle > 90 && p_angle < 180 && (w + h) >= 24 {
let fc = filter_corner(left_ext[2], above_ext[1], above_ext[2]);
above_ext[1] = fc;
left_ext[1] = fc;
}
// §7.11.2.4 step-4 above-edge filter (haveAbove == 1).
if have_above {
let strength =
intra_edge_filter_strength_selection(w, h, filter_type, p_angle - 90);
let num_px = (w.min((max_x - start_x + 1) as usize))
+ (if p_angle < 90 { h } else { 0 })
+ 1;
let _ = intra_edge_filter(num_px, strength, &mut above_ext);
}
// §7.11.2.4 step-4 left-edge filter (haveLeft == 1).
if have_left {
let strength =
intra_edge_filter_strength_selection(w, h, filter_type, p_angle - 180);
let num_px = (h.min((max_y - start_y + 1) as usize))
+ (if p_angle > 180 { w } else { 0 })
+ 1;
let _ = intra_edge_filter(num_px, strength, &mut left_ext);
}
// §7.11.2.4 step-4 above-edge upsample selection + apply.
upsample_above = intra_edge_upsample_selection(w, h, filter_type, p_angle - 90);
if upsample_above == 1 {
let num_px = w + (if p_angle < 90 { h } else { 0 });
let _ = intra_edge_upsample(num_px, bit_depth, &mut above_ext);
}
// §7.11.2.4 step-4 left-edge upsample selection + apply.
upsample_left = intra_edge_upsample_selection(w, h, filter_type, p_angle - 180);
if upsample_left == 1 {
let num_px = h + (if p_angle > 180 { w } else { 0 });
let _ = intra_edge_upsample(num_px, bit_depth, &mut left_ext);
}
}
}
let above_row = &above_ext[2..2 + span];
let left_col = &left_ext[2..2 + span];
let mut pred = vec![0u16; w * h];
// §7.11.2.1 first dispatch arm: `plane == 0 && use_filter_intra`
// ⇒ the §7.11.2.3 recursive intra prediction process, fed the
// head-extended `above_ext` / `left_ext` (corner at offset 1).
// Filter-intra is luma-only and is gated by the §5.11.24
// `Block_Width[MiSize] <= 32 && Block_Height[MiSize] <= 32`
// reader constraint, so `w`/`h` are always within the
// `[4, 32]` recursive-kernel domain (`w >= 4 && h >= 2`).
let ok = if let Some(fim) = filter_intra_mode {
predict_intra_recursive(w, h, fim, bit_depth, &above_ext, &left_ext, &mut pred).is_ok()
} else {
match mode {
DC_PRED => predict_intra_dc_pred(
have_left as u8,
have_above as u8,
log2_w,
log2_h,
w,
h,
bit_depth,
above_row,
left_col,
&mut pred,
)
.is_ok(),
// §7.11.2.4 steps 10/11 — the exact-90/180 degenerate
// copies (angle_delta == 0). A non-zero delta makes
// V_PRED / H_PRED fully directional (see below).
V_PRED if angle_delta == 0 => {
predict_intra_v_pred(w, h, above_row, &mut pred).is_ok()
}
H_PRED if angle_delta == 0 => {
predict_intra_h_pred(w, h, left_col, &mut pred).is_ok()
}
m if (V_PRED..=D67_PRED).contains(&m) => predict_intra_directional(
w,
h,
MODE_TO_ANGLE[m] + angle_delta * ANGLE_STEP,
upsample_above,
upsample_left,
&above_ext,
&left_ext,
&mut pred,
)
.is_ok(),
SMOOTH_PRED => {
predict_intra_smooth_pred(log2_w, log2_h, w, h, above_row, left_col, &mut pred)
.is_ok()
}
SMOOTH_V_PRED => {
predict_intra_smooth_v_pred(log2_h, w, h, above_row, left_col, &mut pred)
.is_ok()
}
SMOOTH_H_PRED => {
predict_intra_smooth_h_pred(log2_w, w, h, above_row, left_col, &mut pred)
.is_ok()
}
PAETH_PRED => {
predict_intra_paeth_pred(w, h, above_row, left_col, corner, &mut pred).is_ok()
}
_ => false,
}
};
if !ok {
return None;
}
Some(pred)
}
/// §7.11.5 predict-chroma-from-luma process (av1-spec p.287) — the
/// CfL high-frequency contribution layered onto an already-written
/// `DC_PRED` chroma base. Invoked from [`Self::transform_block_emit`]
/// on the `isCfl` arm (`plane > 0 && UVMode == UV_CFL_PRED`),
/// immediately after the §7.11.2.1 `predict_intra` `DC_PRED` write
/// and before the §5.11.39 coeff read / §7.12.3 reconstruct.
///
/// The process reads the already-reconstructed luma plane
/// (`CurrFrame[0]`, populated by the §5.11.34 plane loop which walks
/// luma before chroma), subsamples it into `L[i][j]` with 3
/// fractional bits of precision, derives `lumaAvg`, and rewrites
/// each chroma sample as `Clip1(dc + Round2Signed(alpha * (L -
/// lumaAvg), 6))`. `alpha` is `CflAlphaU` (plane 1) / `CflAlphaV`
/// (plane 2), the §5.11.45 signed magnitude.
///
/// `MaxLumaW` / `MaxLumaH` (the §5.11.35 per-luma-TU extent tracked
/// on `self`) clamp the luma reads so a chroma TU that overhangs the
/// decoded luma region replicates the right/bottom luma edge rather
/// than reading undecoded samples. Both are in per-plane luma sample
/// units; the spec's `Min( lumaX, MaxLumaW - (1 << subX) )` /
/// `Min( lumaY, MaxLumaH - (1 << subY) )` clamps apply.
#[inline]
#[allow(clippy::too_many_arguments)]
fn predict_chroma_from_luma_into_curr_frame(
&mut self,
plane: u8,
start_x: u32,
start_y: u32,
tx_sz: usize,
sub_x: u8,
sub_y: u8,
alpha: i32,
bit_depth: u8,
) {
if plane == 0 || (plane as usize) >= 3 {
return;
}
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
if w == 0 || h == 0 {
return;
}
// §7.11.5 `L` / `lumaAvg` — read the reconstructed luma plane
// (`CurrFrame[0]`). If luma was never written (defensive — the
// §5.11.34 plane loop always walks luma first) there is nothing
// to predict from; leave the DC base in place.
let Some(luma) = self.curr_frame[0].as_ref() else {
return;
};
let luma_rows = luma.rows;
let luma_cols = luma.cols;
if luma_rows == 0 || luma_cols == 0 {
return;
}
// §7.11.5 luma-extent clamps. `MaxLumaW` / `MaxLumaH` are the
// §5.11.35 per-luma-TU `startX + stepX*4` / `startY + stepY*4`.
// r408: they legally EXCEED the mi-padded plane when the last
// luma TU overhangs the grid (§5.11.35 decodes any TU with
// `startX < maxX` at its full extent); the overhung samples
// live in the [`LumaOverhang`] side store, so no buffer fold
// is applied here.
let max_luma_w = self.max_luma_w;
let max_luma_h = self.max_luma_h;
// `MaxLumaW - (1 << subX)` can underflow if a degenerate caller
// never set the luma extent; guard with a saturating sub.
let clamp_x = max_luma_w.saturating_sub(1u32 << sub_x);
let clamp_y = max_luma_h.saturating_sub(1u32 << sub_y);
// r408: the §7.11.5 `Min( lumaX, MaxLumaW - (1 << subX) )`
// clamp legally reaches PAST the mi-padded luma plane (the
// last luma TU's overhang — spec `CurrFrame` is unbounded);
// those samples live in the [`LumaOverhang`] side store.
let overhang = self.luma_overhang.as_deref();
let read_luma = |yy: u32, xx: u32| -> i64 {
if yy < luma_rows && xx < luma_cols {
return luma.samples[(yy as usize) * (luma_cols as usize) + (xx as usize)] as i64;
}
overhang.map_or(0, |oh| oh.get(yy, xx)) as i64
};
let mut l = vec![0i64; w * h];
let mut luma_avg: i64 = 0;
for i in 0..h as u32 {
let mut luma_y = (start_y + i) << sub_y;
luma_y = luma_y.min(clamp_y);
for j in 0..w as u32 {
let mut luma_x = (start_x + j) << sub_x;
luma_x = luma_x.min(clamp_x);
// §7.11.5 `t = sum over dy<=subY, dx<=subX of
// CurrFrame[0][lumaY+dy][lumaX+dx]`.
let mut t: i64 = 0;
for dy in 0..=(sub_y as u32) {
for dx in 0..=(sub_x as u32) {
t += read_luma(luma_y + dy, luma_x + dx);
}
}
// §7.11.5 `v = t << (3 - subX - subY)`.
let v = t << (3 - (sub_x as u32) - (sub_y as u32));
l[(i as usize) * w + (j as usize)] = v;
luma_avg += v;
}
}
// §7.11.5 `lumaAvg = Round2( lumaAvg, Tx_Width_Log2 +
// Tx_Height_Log2 )` — the §3 `Round2` rounding-shift. Use the
// signed Round2 (lumaAvg is non-negative here, so it matches the
// unsigned `Round2`).
let log2_w = w.trailing_zeros();
let log2_h = h.trailing_zeros();
let luma_avg = round2_signed(luma_avg, log2_w + log2_h);
// §7.11.5 chroma rewrite — `CurrFrame[plane][..] = Clip1(dc +
// Round2Signed(alpha * (L - lumaAvg), 6))`. `Clip1` is `Clip3(0,
// (1 << BitDepth) - 1, x)`.
let max_val: i64 = (1i64 << bit_depth) - 1;
let Some(buf) = self.curr_frame[plane as usize].as_mut() else {
return;
};
let cols_buf = buf.cols;
for i in 0..h as u32 {
let dst_y = start_y + i;
if dst_y >= buf.rows {
break;
}
for j in 0..w as u32 {
let dst_x = start_x + j;
if dst_x >= cols_buf {
break;
}
let idx = (dst_y as usize) * (cols_buf as usize) + (dst_x as usize);
let dc = buf.samples[idx] as i64;
let l_ij = l[(i as usize) * w + (j as usize)];
let scaled = round2_signed((alpha as i64) * (l_ij - luma_avg), 6);
buf.samples[idx] = (dc + scaled).clamp(0, max_val) as i32;
}
}
}
/// §7.12.3 step-3 frame-buffer merge (av1-spec p.295) — write
/// `CurrFrame[plane][y + yy][x + xx] = Clip1(CurrFrame[..][..] +
/// Residual[i][j])` for `(i, j) ∈ [0, h) × [0, w)` with the
/// FLIPADST destination remap:
///
/// * `flipUD = 1` ⇔ `PlaneTxType ∈ { FLIPADST_DCT, FLIPADST_ADST,
/// V_FLIPADST, FLIPADST_FLIPADST }`. `yy = h - i - 1` then;
/// otherwise `yy = i`.
/// * `flipLR = 1` ⇔ `PlaneTxType ∈ { DCT_FLIPADST, ADST_FLIPADST,
/// H_FLIPADST, FLIPADST_FLIPADST }`. `xx = w - j - 1` then;
/// otherwise `xx = j`.
/// * `Clip1(x) = Clip3(0, (1 << BitDepth) - 1, x)` per §3 `Clip1`.
///
/// `residual` is the row-major `w * h` `Residual[][]` buffer
/// produced by [`crate::transform::inverse_transform_2d`] (`w =
/// Tx_Width[tx_sz]`, `h = Tx_Height[tx_sz]`). `start_x` /
/// `start_y` are the per-TU top-left in the per-plane sample
/// space (already post-subsampling for chroma).
///
/// Lazily allocates the per-plane buffer at `(MiRows * MI_SIZE)
/// >> sub_y` × `(MiCols * MI_SIZE) >> sub_x` if the merge is the
/// first one on `plane`. Out-of-buffer writes (e.g. a TU with a
/// `start_x + w > cols` overhang past the right / bottom frame
/// edge — possible at the frame boundary per §5.11.34's chunk
/// loop) are clipped silently; the in-buffer slice is still
/// merged. Returns silently for `plane >= 3`.
#[inline]
#[allow(clippy::too_many_arguments)]
fn curr_frame_step3_merge(
&mut self,
plane: u8,
start_x: u32,
start_y: u32,
tx_sz: usize,
plane_tx_type: usize,
residual: &[i64],
bit_depth: u8,
sub_x: u8,
sub_y: u8,
) {
if (plane as usize) >= 3 {
return;
}
let w = TX_WIDTH[tx_sz] as u32;
let h = TX_HEIGHT[tx_sz] as u32;
if (w as usize) * (h as usize) != residual.len() {
return;
}
// §7.12.3 step-3 flipUD / flipLR derivation (av1-spec lines
// 16292-16296).
let flip_ud = matches!(
plane_tx_type,
FLIPADST_DCT | FLIPADST_ADST | V_FLIPADST | FLIPADST_FLIPADST
);
let flip_lr = matches!(
plane_tx_type,
DCT_FLIPADST | ADST_FLIPADST | H_FLIPADST | FLIPADST_FLIPADST
);
// Per-plane buffer extent — `MiRows * MI_SIZE` luma samples
// (subsampled for chroma per §5.11.34 line 19). Allocate on
// first merge.
let rows = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
self.ensure_curr_frame_plane(plane as usize, rows, cols);
let Some(buf) = self.curr_frame[plane as usize].as_mut() else {
return;
};
let cols_buf = buf.cols;
let rows_buf = buf.rows;
let max = (1i32 << bit_depth) - 1;
// r408: plane-0 out-of-buffer cells merge into the §7.11.5
// [`LumaOverhang`] side store (`(dst_y, dst_x, residual)`
// gathered here, applied after the plane borrow ends).
let mut overhang: Vec<(u32, u32, i64)> = Vec::new();
// §7.12.3 step-3 inner loop. `for i in 0..h for j in 0..w`.
for i in 0..h {
for j in 0..w {
let yy = if flip_ud { h - i - 1 } else { i };
let xx = if flip_lr { w - j - 1 } else { j };
let dst_y = start_y + yy;
let dst_x = start_x + xx;
if dst_y >= rows_buf || dst_x >= cols_buf {
if plane == 0 {
overhang.push((
dst_y,
dst_x,
residual[(i as usize) * (w as usize) + (j as usize)],
));
}
continue;
}
let idx = (dst_y as usize) * (cols_buf as usize) + (dst_x as usize);
let cur = buf.samples[idx] as i64;
let res = residual[(i as usize) * (w as usize) + (j as usize)];
let sum = cur.saturating_add(res);
// `Clip1(x) = Clip3(0, (1 << BitDepth) - 1, x)`.
let clipped = if sum < 0 {
0
} else if sum > max as i64 {
max
} else {
sum as i32
};
buf.samples[idx] = clipped;
}
}
for (y, x, res) in overhang {
if let Some(slot) = self.luma_overhang_slot(y, x) {
let sum = (*slot as i64).saturating_add(res);
*slot = sum.clamp(0, max as i64) as i32;
}
}
}
/// §5.11.33 inter-intra end-to-end pixel reconstruction against the
/// walker's own tracked `CurrFrame[plane]` buffers (av1-spec p.82-83
/// `predict()` inter-intra arm + §7.11.3.14 blend on p.284).
///
/// This closes the §5.11.33 inter-intra arc inside the parser: the
/// walker already stamps the §7.11.2 intra prediction into its
/// per-plane `curr_frame` buffers via the §7.12.3 step-3 merge
/// ([`Self::curr_frame_step3_merge`]) — the intra half of the
/// §7.11.3.14 blend (`pred1 = CurrFrame[plane][y+dstY][x+dstX]`,
/// av1-spec p.284 line 15786). This method threads those buffers,
/// together with the §5.11.33 dispatcher's emitted `readout`, the
/// decoded per-block inter side (`leaf`), and the caller-supplied
/// §7.11.3.3 reference state (`plane_refs`, since the parser does not
/// own the decoded-picture buffer), through the shared
/// [`crate::reconstruct_inter_intra_from_dispatch`] driver, then
/// writes the §7.11.3.14-blended result back into the walker's
/// `curr_frame` buffers. The walker's verdict-only r302 surface
/// (`DecodedBlock::is_inter_intra`) therefore now has an inline
/// pixel-producing partner: a consumer that has the §7.11.3.3 frame
/// store can reconstruct one inter-intra block end-to-end without
/// owning a separate `CurrFrame` mirror.
///
/// `plane_refs` lists, per plane being reconstructed, the resolved
/// `FrameStore` slice + plane-space dims + subsampling (the §7.11.3
/// quantities the parser doesn't track). Each `plane_refs[k].plane`
/// must have a tracked `curr_frame` buffer; a plane whose buffer has
/// not been allocated yet (no step-3 merge has fired on it) is a
/// caller bug ([`crate::Error::PartitionWalkOutOfRange`]) — the intra
/// half of the blend must already be present.
///
/// The walker buffers are `i32` (post-`Clip1`, always in
/// `0..=(1 << BitDepth) - 1`); the §7.11.3.14 driver works in `u16`,
/// so this bridge materialises a `u16` mirror of each plane buffer,
/// drives the blend, and copies the (post-`Clip1`, in-range) result
/// back as `i32`. The mirror is the whole plane buffer (the driver
/// addresses it at the leaf's plane-space `(baseX, baseY)` origin via
/// `curr_stride`).
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for an empty
/// `plane_refs`, a `plane >= 3`, a plane with no tracked buffer, or
/// anything [`crate::reconstruct_inter_intra_from_dispatch`] itself
/// rejects (non-inter-intra readout, plane-coverage mismatch,
/// intra-half mode disagreement).
pub fn reconstruct_inter_intra_into_curr_frame(
&mut self,
readout: &ComputePredictionReadout,
leaf: &crate::InterIntraLeaf,
ref_frame_idx: &[u8],
bit_depth: u8,
plane_refs: &[crate::PlaneRefSpec<'_>],
) -> Result<(), crate::Error> {
if plane_refs.is_empty() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Materialise a `u16` mirror of each plane's `i32` `curr_frame`
// buffer (the intra half of the §7.11.3.14 blend the step-3
// merge already wrote). Each `plane_refs[k].plane` must have an
// allocated buffer; samples are post-`Clip1` so the `i32 → u16`
// narrowing is lossless.
let mut mirrors: Vec<(u8, Vec<u16>, usize, usize)> = Vec::new();
if mirrors.try_reserve(plane_refs.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
for spec in plane_refs {
let p = spec.plane as usize;
if p >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let Some(cp) = self.curr_frame[p].as_ref() else {
// No tracked buffer — the intra half of the blend has
// not been written yet (caller-bug ordering).
return Err(crate::Error::PartitionWalkOutOfRange);
};
let cols = cp.cols as usize;
let rows = cp.rows as usize;
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
mirrors.push((spec.plane, mirror, cols, rows));
}
// Build the per-plane reconstruction contexts over the mirrors.
// The mirror borrow and the `plane_refs` borrow are disjoint
// (the frame store lives in `plane_refs`, the curr buffer in the
// mirror), so the contexts can hold both.
{
let mut planes: Vec<crate::PlaneReconContext<'_>> = Vec::new();
if planes.try_reserve(plane_refs.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
for ((p, mirror, cols, _rows), spec) in mirrors.iter_mut().zip(plane_refs.iter()) {
planes.push(crate::PlaneReconContext {
plane: *p,
subsampling_x: spec.subsampling_x,
subsampling_y: spec.subsampling_y,
frame_store: spec.frame_store,
frame_width: spec.frame_width,
frame_height: spec.frame_height,
curr: mirror.as_mut_slice(),
curr_stride: *cols,
});
}
crate::reconstruct_inter_intra_from_dispatch(
readout,
leaf,
ref_frame_idx,
bit_depth,
// Translational inter half — this walker bridge
// carries no §5.9.24 global-motion context; the
// in-walk §5.11.33 route supplies the §7.11.3.1
// step-7 warp context for GLOBALMV-class leaves.
None,
&mut planes,
)?;
}
// §7.11.3.1 final step writes the blended samples in place; copy
// the (post-`Clip1`, in-range) `u16` result back into the
// walker's `i32` `curr_frame` buffers.
for (p, mirror, _cols, _rows) in &mirrors {
if let Some(cp) = self.curr_frame[*p as usize].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
}
Ok(())
}
/// §7.11.3.1 single-reference translational inter pixel
/// reconstruction against the walker's own tracked `CurrFrame[plane]`
/// buffer (av1-spec p.258 line 14402 final stitch: `isCompound == 0
/// && IsInterIntra == 0 ⇒ CurrFrame[plane][y+i][x+j] =
/// Clip1(preds[0][i][j])`).
///
/// This is the plain single-ref companion to
/// [`Self::reconstruct_inter_intra_into_curr_frame`]: rather than
/// blending an intra half already present in `CurrFrame[plane]`, the
/// single-ref final step **overwrites** the `w × h` footprint at the
/// block's plane-space origin with `Clip1(preds[0])` — there is no
/// intra-half prerequisite, so the bridge **allocates** the plane
/// buffer if it has not been touched yet (the full-frame extent the
/// §7.12.3 step-3 merge would otherwise lazily allocate, sized from
/// `MiRows × MiCols × MI_SIZE >> subsampling_*`).
///
/// The bridge threads the decoded per-block mode-info (`mode_info` =
/// the §7.11.3.1 step-5/-8 `(RefFrame[0], Mvs[..][0])`), the
/// caller-supplied §7.11.3.3 reference state (`ref_spec` — the parser
/// does not own the decoded-picture buffer), and the §7.11.3.2
/// interpolation-filter pair through the shared
/// [`crate::reconstruct_inter_block`] driver, then writes the
/// (post-`Clip1`, in-range) result back into the walker's `i32`
/// `curr_frame` buffer.
///
/// `x` / `y` are the block's top-left in `ref_spec.plane`-space
/// samples; `w` / `h` are its plane-space size. The walker buffer is
/// `i32` (post-`Clip1`, always in `0..=(1 << BitDepth) - 1`); the
/// driver works in `u16`, so the bridge mirrors the plane buffer to
/// `u16`, drives the block, and copies the result back as `i32`
/// (lossless in both directions). Only the `w × h` write footprint
/// changes; the rest of the mirror round-trips unchanged.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for a
/// `ref_spec.plane >= 3`, negative `x` / `y`, a `w`/`h` write region
/// that would overflow the (allocated) plane buffer, or anything
/// [`crate::reconstruct_inter_block`] itself rejects (an
/// `INTRA_FRAME`/out-of-range `RefFrame[0]`, an out-of-range resolved
/// `refIdx`).
#[allow(clippy::too_many_arguments)]
pub fn reconstruct_inter_block_into_curr_frame(
&mut self,
mode_info: crate::InterModeInfo,
ref_frame_idx: &[u8],
ref_spec: &crate::PlaneRefSpec<'_>,
x: i32,
y: i32,
w: usize,
h: usize,
bit_depth: u8,
interp_filter_x: u8,
interp_filter_y: u8,
) -> Result<(), crate::Error> {
let p = ref_spec.plane as usize;
if p >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Allocate the plane buffer if the single-ref block is the first
// writer on this plane (no §7.12.3 step-3 merge / intra writer
// has fired). The §7.12.3 step-3 merge sizes the buffer from
// `MiRows × MiCols × MI_SIZE >> subsampling_*`; mirror that so a
// later merge on the same plane is a no-op (see
// [`Self::ensure_curr_frame_plane`]).
let rows = (self.mi_rows * (MI_SIZE as u32)) >> ref_spec.subsampling_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> ref_spec.subsampling_x;
self.ensure_curr_frame_plane(p, rows, cols);
let Some(cp) = self.curr_frame[p].as_ref() else {
// Allocation failed (try_reserve) — surface as a caller-bug.
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
// Materialise a `u16` mirror of the whole `i32` plane buffer; the
// samples are post-`Clip1` so the `i32 → u16` narrowing is
// lossless.
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
// Drive the single-ref translational reconstruction; the final
// stitch overwrites the `w × h` footprint at `(x, y)`.
crate::reconstruct_inter_block(
mode_info,
ref_frame_idx,
ref_spec.frame_store,
ref_spec.plane,
x,
y,
w,
h,
bit_depth,
ref_spec.subsampling_x,
ref_spec.subsampling_y,
ref_spec.frame_width,
ref_spec.frame_height,
interp_filter_x,
interp_filter_y,
mirror.as_mut_slice(),
buf_cols,
)?;
// Copy the (post-`Clip1`, in-range) `u16` result back into the
// walker's `i32` `curr_frame` buffer.
if let Some(cp) = self.curr_frame[p].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
Ok(())
}
/// §7.11.3.1 single forward-reference **warped-motion** pixel
/// reconstruction against the walker's own tracked `CurrFrame[plane]`
/// buffer (av1-spec p.257-258, `motion_mode == WARPED_CAUSAL`) — the
/// warp counterpart of [`Self::reconstruct_inter_block_into_curr_frame`].
///
/// Rather than the `MOTION_MODE_SIMPLE` translational driver, this
/// bridge invokes [`crate::reconstruct_inter_block_warp`] with the
/// caller-supplied [`crate::WarpDriverParams`] so the §7.11.3.1
/// step-2/3/6/7 `useWarp` derivation picks between the LOCALWARP
/// (`useWarp == 1`) and GLOBAL_GLOBALMV (`useWarp == 2`) §7.11.3.5
/// `block_warp` arms. As with the SIMPLE bridge there is no intra-half
/// prerequisite, so it **allocates** the plane buffer (sized
/// `MiRows × MiCols × MI_SIZE >> subsampling_*`) if the warped block is
/// the first writer on this plane.
///
/// On a §7.11.3.1 step-7 fall-through to `useWarp == 0` (a sub-8×8
/// `w`/`h`, a chroma plane the §5.11.27 LOCALWARP arm leaves
/// translational, an invalid local fit, or `force_integer_mv`) the
/// underlying driver predicts translationally — so this entry is safe
/// to call for every `WARPED_CAUSAL` leaf and plane.
///
/// `x` / `y` are the block's top-left in `ref_spec.plane`-space
/// samples; `w` / `h` are its plane-space size. The walker buffer is
/// `i32` (post-`Clip1`); the driver works in `u16`, so the bridge
/// mirrors the plane buffer to `u16`, drives the block, and copies the
/// result back as `i32` (lossless in both directions). Only the
/// `w × h` write footprint changes.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for a
/// `ref_spec.plane >= 3`, negative `x` / `y`, a `w`/`h` write region
/// that would overflow the (allocated) plane buffer, or anything
/// [`crate::reconstruct_inter_block_warp`] itself rejects (an
/// `INTRA_FRAME`/out-of-range `RefFrame[0]`, an out-of-range resolved
/// `refIdx`).
#[allow(clippy::too_many_arguments)]
pub fn reconstruct_inter_block_warp_into_curr_frame(
&mut self,
mode_info: crate::InterModeInfo,
warp: &crate::WarpDriverParams,
ref_frame_idx: &[u8],
ref_spec: &crate::PlaneRefSpec<'_>,
x: i32,
y: i32,
w: usize,
h: usize,
bit_depth: u8,
interp_filter_x: u8,
interp_filter_y: u8,
) -> Result<(), crate::Error> {
let p = ref_spec.plane as usize;
if p >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Allocate the plane buffer if the warped block is the first
// writer on this plane (mirror the §7.12.3 step-3 merge extent so
// a later merge on the same plane is a no-op).
let rows = (self.mi_rows * (MI_SIZE as u32)) >> ref_spec.subsampling_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> ref_spec.subsampling_x;
self.ensure_curr_frame_plane(p, rows, cols);
let Some(cp) = self.curr_frame[p].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
// Materialise a `u16` mirror of the whole `i32` plane buffer; the
// samples are post-`Clip1` so the `i32 → u16` narrowing is
// lossless.
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
// Drive the single-ref warped reconstruction; the final stitch
// overwrites the `w × h` footprint at `(x, y)`.
crate::reconstruct_inter_block_warp(
mode_info,
warp,
ref_frame_idx,
ref_spec.frame_store,
ref_spec.plane,
x,
y,
w,
h,
bit_depth,
ref_spec.subsampling_x,
ref_spec.subsampling_y,
ref_spec.frame_width,
ref_spec.frame_height,
interp_filter_x,
interp_filter_y,
mirror.as_mut_slice(),
buf_cols,
)?;
// Copy the (post-`Clip1`, in-range) `u16` result back into the
// walker's `i32` `curr_frame` buffer.
if let Some(cp) = self.curr_frame[p].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
Ok(())
}
/// §7.11.3.1 single forward-reference **OBMC** (overlapped block
/// motion compensation) pixel reconstruction against the walker's own
/// tracked `CurrFrame[plane]` buffer (av1-spec p.258 line 14414,
/// `motion_mode == OBMC`) — the OBMC counterpart of
/// [`Self::reconstruct_inter_block_into_curr_frame`].
///
/// Rather than the bare `MOTION_MODE_SIMPLE` translational driver, this
/// bridge invokes [`crate::reconstruct_inter_block_obmc`] with the
/// caller-supplied [`crate::ObmcParams`] so the §7.11.3.9 above-pass /
/// left-pass neighbour walk overlays each qualifying neighbour's
/// §7.11.3.10 `overlap_blending` contribution onto the block's own
/// §7.11.3.1 translational prediction before the final stitch. As with
/// the SIMPLE / WARP bridges there is no intra-half prerequisite, so it
/// **allocates** the plane buffer (sized `MiRows × MiCols × MI_SIZE >>
/// subsampling_*`) if the OBMC block is the first writer on this plane.
///
/// The `obmc` bundle carries the §7.11.3.9 above / left neighbour
/// candidate lists the caller resolved from the §6.10.4 `RefFrames[]` /
/// `Mvs[]` / `MiSizes[]` mi-grid plus the block's `(MiRow, MiCol,
/// MiSize, AvailU, AvailL)` context. Empty neighbour lists (no
/// qualifying above / left neighbour) make the blend a no-op for that
/// axis, so this entry is always safe to call for an `OBMC` leaf even
/// when no neighbour actually overlaps (the result then equals the
/// plain translational prediction).
///
/// `x` / `y` are the block's top-left in `ref_spec.plane`-space
/// samples; `w` / `h` are its plane-space size. The walker buffer is
/// `i32` (post-`Clip1`); the driver works in `u16`, so the bridge
/// mirrors the plane buffer to `u16`, drives the block, and copies the
/// result back as `i32` (lossless in both directions). Only the
/// `w × h` write footprint changes.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for a
/// `ref_spec.plane >= 3`, negative `x` / `y`, a `w`/`h` write region
/// that would overflow the (allocated) plane buffer, or anything
/// [`crate::reconstruct_inter_block_obmc`] itself rejects (an
/// `INTRA_FRAME`/out-of-range `RefFrame[0]`, an out-of-range resolved
/// `refIdx`, an OBMC `ObmcParams` the §7.11.3.9 walk rejects).
#[allow(clippy::too_many_arguments)]
pub fn reconstruct_inter_block_obmc_into_curr_frame(
&mut self,
mode_info: crate::InterModeInfo,
obmc: &crate::ObmcParams<'_, '_>,
ref_frame_idx: &[u8],
ref_spec: &crate::PlaneRefSpec<'_>,
x: i32,
y: i32,
w: usize,
h: usize,
bit_depth: u8,
interp_filter_x: u8,
interp_filter_y: u8,
) -> Result<(), crate::Error> {
let p = ref_spec.plane as usize;
if p >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Allocate the plane buffer if the OBMC block is the first writer
// on this plane (mirror the §7.12.3 step-3 merge extent so a later
// merge on the same plane is a no-op).
let rows = (self.mi_rows * (MI_SIZE as u32)) >> ref_spec.subsampling_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> ref_spec.subsampling_x;
self.ensure_curr_frame_plane(p, rows, cols);
let Some(cp) = self.curr_frame[p].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
// Materialise a `u16` mirror of the whole `i32` plane buffer; the
// samples are post-`Clip1` so the `i32 → u16` narrowing is
// lossless.
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
// Drive the single-ref OBMC reconstruction; the final stitch
// overwrites the `w × h` footprint at `(x, y)`.
crate::reconstruct_inter_block_obmc(
mode_info,
obmc,
ref_frame_idx,
ref_spec.frame_store,
ref_spec.plane,
x,
y,
w,
h,
bit_depth,
ref_spec.subsampling_x,
ref_spec.subsampling_y,
ref_spec.frame_width,
ref_spec.frame_height,
interp_filter_x,
interp_filter_y,
mirror.as_mut_slice(),
buf_cols,
)?;
// Copy the (post-`Clip1`, in-range) `u16` result back into the
// walker's `i32` `curr_frame` buffer.
if let Some(cp) = self.curr_frame[p].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
Ok(())
}
/// §7.11.3.1 compound (≥2-ref) inter pixel reconstruction against the
/// walker's own tracked `CurrFrame[plane]` buffer (av1-spec p.258 line
/// 14402 final stitch: `isCompound == 1 ⇒ CurrFrame[plane][y+i][x+j] =
/// <step-14 combine>`).
///
/// This is the two-reference companion to
/// [`Self::reconstruct_inter_block_into_curr_frame`]: rather than the
/// single `preds[0]` overwrite, the §7.11.3.1 step-14 combine merges
/// `preds[0]` and `preds[1]` through the decoded `compound_type` arm
/// ([`crate::COMPOUND_AVERAGE`] / [`crate::COMPOUND_DISTANCE`] /
/// [`crate::COMPOUND_WEDGE`] / [`crate::COMPOUND_DIFFWTD`]) and
/// **overwrites** the `w × h` footprint with the combined, `Clip1`-ed
/// result. As with single-ref there is no intra-half prerequisite, so
/// the bridge **allocates** the plane buffer if it has not been touched
/// yet (the full-frame extent the §7.12.3 step-3 merge would otherwise
/// lazily allocate, sized from `MiRows × MiCols × MI_SIZE >>
/// subsampling_*`).
///
/// The bridge threads the decoded compound mode-info (`mode_info` = the
/// §7.11.3.1 step-5/-8 `(RefFrame[0], RefFrame[1], Mvs[..][0],
/// Mvs[..][1])` quad plus the `compound_type` and its side-data), the
/// §7.11.3.15 order-hint context (`order_hints` — used only by the
/// [`crate::COMPOUND_DISTANCE`] arm), the caller-supplied §7.11.3.3
/// reference state (`ref_spec`), and the §7.11.3.2 interpolation-filter
/// pair through the shared
/// [`crate::inter_pred::reconstruct_inter_block_compound`] driver, then
/// writes the (post-`Clip1`, in-range) result back into the walker's
/// `i32` `curr_frame` buffer.
///
/// The [`crate::COMPOUND_DIFFWTD`] arm's §7.11.3.12 mask is derived
/// inside the driver from the two formed `preds[]` at `plane == 0`. For
/// a single (luma) bridge call the bridge materializes the persistent
/// luma-grid mask buffer locally (sized `w × h`, the luma block extent
/// at `plane == 0`). To reconstruct chroma planes that **share** the
/// luma-derived mask (the spec-mandated `plane == 0`-only derivation),
/// use
/// [`Self::reconstruct_inter_block_compound_multiplane_into_curr_frame`],
/// which drives the §5.11.34 per-plane loop with one shared mask
/// buffer; this single-plane bridge reconstructs one plane per call.
///
/// `x` / `y` are the block's top-left in `ref_spec.plane`-space
/// samples; `w` / `h` are its plane-space size. The walker buffer is
/// `i32` (post-`Clip1`); the driver works in `u16`, so the bridge
/// mirrors the plane buffer to `u16`, drives the block, and copies the
/// result back as `i32` (lossless both directions). Only the `w × h`
/// write footprint changes.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for a
/// `ref_spec.plane >= 3`, negative `x` / `y`, a `w`/`h` write region
/// that would overflow the (allocated) plane buffer, or anything
/// [`crate::inter_pred::reconstruct_inter_block_compound`] itself
/// rejects (an `INTRA_FRAME`/out-of-range `RefFrame[0]`/`RefFrame[1]`,
/// an out-of-range resolved `refIdx`, an unsupported `compound_type`, a
/// `COMPOUND_WEDGE` leaf with `wedge == None`).
#[allow(clippy::too_many_arguments)]
pub fn reconstruct_inter_block_compound_into_curr_frame(
&mut self,
mode_info: crate::inter_pred::CompoundInterModeInfo,
order_hints: crate::inter_pred::CompoundOrderHintContext,
ref_frame_idx: &[u8],
ref_spec: &crate::PlaneRefSpec<'_>,
x: i32,
y: i32,
w: usize,
h: usize,
bit_depth: u8,
interp_filter_x: u8,
interp_filter_y: u8,
) -> Result<(), crate::Error> {
let p = ref_spec.plane as usize;
if p >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Allocate the plane buffer if this compound block is the first
// writer on the plane (compound overwrites the `w × h` footprint —
// no intra-half prerequisite, identical to the single-ref bridge).
let rows = (self.mi_rows * (MI_SIZE as u32)) >> ref_spec.subsampling_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> ref_spec.subsampling_x;
self.ensure_curr_frame_plane(p, rows, cols);
let Some(cp) = self.curr_frame[p].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
// Materialise a `u16` mirror of the whole `i32` plane buffer; the
// samples are post-`Clip1` so the `i32 → u16` narrowing is
// lossless.
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
// §7.11.3.12 DIFFWTD persistent luma-grid mask — for a single
// (luma) bridge call the mask buffer is `w × h`. The driver fills
// it at `plane == 0`; on the non-DIFFWTD arms it is unused, so it
// is only allocated when needed.
let mut diffwtd_mask: Vec<u8> = Vec::new();
let diffwtd_arg: Option<&mut [u8]> = if mode_info.compound_type == crate::COMPOUND_DIFFWTD {
if diffwtd_mask.try_reserve_exact(w * h).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
diffwtd_mask.resize(w * h, 0);
Some(diffwtd_mask.as_mut_slice())
} else {
None
};
// Drive the compound reconstruction; the step-14 combine + final
// stitch overwrites the `w × h` footprint at `(x, y)`.
crate::inter_pred::reconstruct_inter_block_compound(
mode_info,
order_hints,
ref_frame_idx,
ref_spec.frame_store,
ref_spec.plane,
x,
y,
w,
h,
bit_depth,
ref_spec.subsampling_x,
ref_spec.subsampling_y,
ref_spec.frame_width,
ref_spec.frame_height,
interp_filter_x,
interp_filter_y,
diffwtd_arg,
/* warp */ None,
mirror.as_mut_slice(),
buf_cols,
)?;
// Copy the (post-`Clip1`, in-range) `u16` result back into the
// walker's `i32` `curr_frame` buffer.
if let Some(cp) = self.curr_frame[p].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
Ok(())
}
/// §7.11.3.1 compound (≥2-ref) inter pixel reconstruction across all
/// of a block's planes (Y, Cb, Cr) with **one shared §7.11.3.12
/// `Mask`** (av1-spec p.258 lines 14386-14393: the
/// [`crate::COMPOUND_DIFFWTD`] / [`crate::COMPOUND_WEDGE`] mask is
/// derived at `plane == 0` only and reused verbatim for the chroma
/// planes via [`mask_blend`]'s `(sub_x, sub_y)` downsampling).
///
/// This is the multi-plane companion to
/// [`Self::reconstruct_inter_block_compound_into_curr_frame`]: rather
/// than one plane per call (which materializes a fresh `w × h` mask),
/// it drives the §5.11.34 per-plane loop
/// (`for plane in 0..1 + 2*has_chroma`) for a single decoded block,
/// allocating the §7.11.3.12 persistent mask once at the **luma**
/// extent (`Block_Width[MiSize] × Block_Height[MiSize]`) and threading
/// that same buffer into every plane's driver call. The `plane == 0`
/// call fills the mask from the luma `preds[]`; the chroma calls read
/// the already-filled luma-grid mask, downsampled. The
/// [`crate::COMPOUND_DIFFWTD`] arm is the one that *needs* the shared
/// buffer (its mask is data-dependent on the formed predictions); the
/// mask-free arms ([`crate::COMPOUND_AVERAGE`] /
/// [`crate::COMPOUND_DISTANCE`]) and the regenerable
/// [`crate::COMPOUND_WEDGE`] arm (mask is a pure function of the
/// decoded `(MiSize, wedge_index, wedge_sign)`) ignore it, so they
/// reconstruct identically whether driven one-plane-at-a-time or
/// through this multi-plane bridge.
///
/// Per-plane geometry follows the §5.11.34 `predict()` loop
/// (av1-spec p.83): for `subX = (plane > 0) ? subsampling_x : 0` (and
/// `subY` likewise), `baseX = (mi_col >> subX) * MI_SIZE`,
/// `baseY = (mi_row >> subY) * MI_SIZE`,
/// `predW = Block_Width[MiSize] >> subX`,
/// `predH = Block_Height[MiSize] >> subY`. The bridge handles the
/// common `someUseIntra == 0` shape (one `predict_inter` call per
/// plane covering the whole plane footprint); the §5.11.34
/// intra-neighbour sub-block split (`someUseIntra == 1`) is a separate
/// per-block concern (a block flagged for it would route each 4×4
/// chunk through the single-plane bridge instead).
///
/// `mode_info` / `order_hints` / `ref_frame_idx` are the decoded
/// §7.11.3.1 step-5/-8/-14 side-data (shared by every plane);
/// `plane_specs[plane]` carries the per-plane §7.11.3.3 reference
/// state (`subsampling_*`, `frame_store`, plane-resolved
/// `(frame_width, frame_height)`) — the `plane` field of each entry is
/// ignored (the loop index drives it). `num_planes` is `1` (luma-only)
/// or `3` (Y/Cb/Cr); chroma planes are skipped when `num_planes == 1`.
///
/// Like the single-plane bridges, the walker buffer is `i32`
/// (post-`Clip1`); the driver works in `u16`, so each plane is
/// mirrored, driven, and copied back (lossless). Each plane's buffer
/// is allocated if untouched (compound overwrites — no intra-half
/// prerequisite), sized from `MiRows × MiCols × MI_SIZE >>
/// subsampling_*` so a later §7.12.3 step-3 merge is a no-op.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for
/// `num_planes` not in `{1, 3}`, an out-of-range `mi_size`, a
/// `plane_specs` slice shorter than `num_planes`, or anything
/// [`crate::inter_pred::reconstruct_inter_block_compound`] itself
/// rejects on any plane (an `INTRA_FRAME`/out-of-range
/// `RefFrame[0]`/`RefFrame[1]`, an out-of-range resolved `refIdx`, an
/// unsupported `compound_type`, a `COMPOUND_WEDGE` leaf with
/// `wedge == None`).
#[allow(clippy::too_many_arguments)]
pub fn reconstruct_inter_block_compound_multiplane_into_curr_frame(
&mut self,
mode_info: crate::inter_pred::CompoundInterModeInfo,
order_hints: crate::inter_pred::CompoundOrderHintContext,
ref_frame_idx: &[u8],
plane_specs: &[crate::PlaneRefSpec<'_>],
mi_col: u32,
mi_row: u32,
mi_size: usize,
num_planes: usize,
bit_depth: u8,
interp_filter_x: u8,
interp_filter_y: u8,
) -> Result<(), crate::Error> {
if num_planes != 1 && num_planes != 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if plane_specs.len() < num_planes {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §7.11.3.12 persistent `Mask`, allocated once at the *luma*
// extent (`Block_Width[MiSize] × Block_Height[MiSize]`) and shared
// across the plane loop. Only the DIFFWTD arm reads/writes it; the
// mask-free + (internally-regenerated) WEDGE arms leave it
// untouched, so it is allocated only when DIFFWTD is in play.
let luma_w = block_width(mi_size);
let luma_h = block_height(mi_size);
let mut diffwtd_mask: Vec<u8> = Vec::new();
let needs_mask = mode_info.compound_type == crate::COMPOUND_DIFFWTD;
if needs_mask {
if diffwtd_mask.try_reserve_exact(luma_w * luma_h).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
diffwtd_mask.resize(luma_w * luma_h, 0);
}
for (plane, spec) in plane_specs.iter().enumerate().take(num_planes) {
// §5.11.34 `subX/subY = (plane > 0) ? subsampling_* : 0`.
let (sub_x, sub_y) = if plane == 0 {
(0u8, 0u8)
} else {
(spec.subsampling_x, spec.subsampling_y)
};
// §5.11.34 per-plane origin + footprint (someUseIntra == 0
// shape: one predict_inter call covering the whole plane
// block).
let base_x = ((mi_col >> sub_x) * (MI_SIZE as u32)) as i32;
let base_y = ((mi_row >> sub_y) * (MI_SIZE as u32)) as i32;
let pred_w = luma_w >> sub_x;
let pred_h = luma_h >> sub_y;
// Allocate this plane's buffer if untouched (compound
// overwrites — no intra-half prerequisite).
let rows = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
self.ensure_curr_frame_plane(plane, rows, cols);
let Some(cp) = self.curr_frame[plane].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
// `u16` mirror of the whole `i32` plane buffer (samples are
// post-`Clip1`, so the narrowing is lossless).
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
// The DIFFWTD arm receives the *shared* luma-grid mask; the
// `plane == 0` call fills it, the chroma calls read it
// (downsampled). Non-DIFFWTD arms get `None`.
let diffwtd_arg: Option<&mut [u8]> = if needs_mask {
Some(diffwtd_mask.as_mut_slice())
} else {
None
};
crate::inter_pred::reconstruct_inter_block_compound(
mode_info,
order_hints,
ref_frame_idx,
spec.frame_store,
plane as u8,
base_x,
base_y,
pred_w,
pred_h,
bit_depth,
sub_x,
sub_y,
spec.frame_width,
spec.frame_height,
interp_filter_x,
interp_filter_y,
diffwtd_arg,
/* warp */ None,
mirror.as_mut_slice(),
buf_cols,
)?;
if let Some(cp) = self.curr_frame[plane].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
}
Ok(())
}
/// §7.11.3.1 single-reference translational inter pixel
/// reconstruction across all of a block's planes (Y, Cb, Cr) in one
/// call (av1-spec p.258 line 14402 final stitch: `isCompound == 0 &&
/// IsInterIntra == 0 ⇒ CurrFrame[plane][y+i][x+j] =
/// Clip1(preds[0][i][j])`).
///
/// This is the single-ref companion to
/// [`Self::reconstruct_inter_block_compound_multiplane_into_curr_frame`]
/// and the multi-plane companion to
/// [`Self::reconstruct_inter_block_into_curr_frame`]: rather than one
/// plane per call, it drives the §5.11.34 `predict()` per-plane loop
/// (`for plane in 0..1 + 2*has_chroma`) for a single decoded block,
/// overwriting each plane's `predW × predH` footprint with
/// `Clip1(preds[0])`. There is no compound `Mask` (single-ref forms a
/// single prediction), so — unlike the compound multi-plane bridge —
/// no shared mask buffer is threaded; each plane is independent.
///
/// Per-plane geometry follows the §5.11.34 `predict()` loop
/// (av1-spec p.83 lines 5165-5190) in the common `someUseIntra == 0`
/// shape (one `predict_inter` call per plane covering the whole plane
/// footprint): for `subX = (plane > 0) ? subsampling_x : 0` (and
/// `subY` likewise), `baseX = (mi_col >> subX) * MI_SIZE`,
/// `baseY = (mi_row >> subY) * MI_SIZE`,
/// `predW = Block_Width[MiSize] >> subX`,
/// `predH = Block_Height[MiSize] >> subY`. The §5.11.34
/// intra-neighbour sub-block split (`someUseIntra == 1`) is a
/// separate per-block concern (a block flagged for it would route
/// each 4×4 chunk through the single-plane bridge instead).
///
/// `mode_info` / `ref_frame_idx` are the decoded §7.11.3.1
/// step-5/-8 side-data (the `(RefFrame[0], Mvs[..][0])` pair, shared
/// by every plane); `plane_specs[plane]` carries the per-plane
/// §7.11.3.3 reference state (`subsampling_*`, `frame_store`,
/// plane-resolved `(frame_width, frame_height)`) — the `plane` field
/// of each entry is ignored (the loop index drives it). `num_planes`
/// is `1` (luma-only) or `3` (Y/Cb/Cr); chroma planes are skipped
/// when `num_planes == 1`.
///
/// Like the single-plane bridges, the walker buffer is `i32`
/// (post-`Clip1`); the driver works in `u16`, so each plane is
/// mirrored, driven, and copied back (lossless). Each plane's buffer
/// is allocated if untouched (single-ref overwrites — no intra-half
/// prerequisite), sized from `MiRows × MiCols × MI_SIZE >>
/// subsampling_*` so a later §7.12.3 step-3 merge is a no-op.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for
/// `num_planes` not in `{1, 3}`, an out-of-range `mi_size`, a
/// `plane_specs` slice shorter than `num_planes`, or anything
/// [`crate::reconstruct_inter_block`] itself rejects on any plane (an
/// `INTRA_FRAME`/out-of-range `RefFrame[0]`, an out-of-range resolved
/// `refIdx`).
#[allow(clippy::too_many_arguments)]
pub fn reconstruct_inter_block_multiplane_into_curr_frame(
&mut self,
mode_info: crate::InterModeInfo,
ref_frame_idx: &[u8],
plane_specs: &[crate::PlaneRefSpec<'_>],
mi_col: u32,
mi_row: u32,
mi_size: usize,
num_planes: usize,
bit_depth: u8,
interp_filter_x: u8,
interp_filter_y: u8,
) -> Result<(), crate::Error> {
if num_planes != 1 && num_planes != 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if plane_specs.len() < num_planes {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let luma_w = block_width(mi_size);
let luma_h = block_height(mi_size);
for (plane, spec) in plane_specs.iter().enumerate().take(num_planes) {
// §5.11.34 `subX/subY = (plane > 0) ? subsampling_* : 0`.
let (sub_x, sub_y) = if plane == 0 {
(0u8, 0u8)
} else {
(spec.subsampling_x, spec.subsampling_y)
};
// §5.11.34 per-plane origin + footprint (someUseIntra == 0
// shape: one predict_inter call covering the whole plane
// block).
let base_x = ((mi_col >> sub_x) * (MI_SIZE as u32)) as i32;
let base_y = ((mi_row >> sub_y) * (MI_SIZE as u32)) as i32;
let pred_w = luma_w >> sub_x;
let pred_h = luma_h >> sub_y;
// Allocate this plane's buffer if untouched (single-ref
// overwrites — no intra-half prerequisite).
let rows = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
self.ensure_curr_frame_plane(plane, rows, cols);
let Some(cp) = self.curr_frame[plane].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
// `u16` mirror of the whole `i32` plane buffer (samples are
// post-`Clip1`, so the narrowing is lossless).
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
crate::reconstruct_inter_block(
mode_info,
ref_frame_idx,
spec.frame_store,
plane as u8,
base_x,
base_y,
pred_w,
pred_h,
bit_depth,
sub_x,
sub_y,
spec.frame_width,
spec.frame_height,
interp_filter_x,
interp_filter_y,
mirror.as_mut_slice(),
buf_cols,
)?;
if let Some(cp) = self.curr_frame[plane].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
}
Ok(())
}
/// §7.11.4 palette prediction reconstruction into the walker's own
/// `CurrFrame[plane]` buffer (av1-spec p.286) — the per-block bridge
/// that drives the §7.11.4 [`crate::predict_palette`] leaf across
/// every transform block of one plane of a palette-coded intra block,
/// writing the predicted samples into the walker's tracked
/// `CurrFrame[plane]`.
///
/// This is the palette-block companion to the
/// `reconstruct_*_inter_block_into_curr_frame` family: it consumes
/// the §5.11.49 `ColorMap{Y,UV}` index map surfaced on
/// [`DecodedBlock::color_map_y`] / [`DecodedBlock::color_map_uv`]
/// (r325) together with the §5.11.46 `PaletteColors{Y,U,V}[ ]` colour
/// table this walker stamped into its own grid (r-series
/// `palette_mode_info` reads), and produces the actual predicted
/// pixels the §5.11.35 `predict_palette( plane, startX, startY, x, y,
/// txSz )` call site writes.
///
/// The §5.11.35 residual loop walks the block's transform-block grid
/// in `(x, y)` 4×4-unit coordinates stepping by `stepX =
/// Tx_Width[txSz] >> MI_SIZE_LOG2` / `stepY = Tx_Height[txSz] >>
/// MI_SIZE_LOG2`; at each step it computes `startX = baseX + 4 * x`,
/// `startY = baseY + 4 * y` and invokes §7.11.4 with that transform
/// block's `(x, y)` map window. This bridge reproduces that walk for
/// one plane:
///
/// * `baseX = (MiCol >> subX) * MI_SIZE`, `baseY = (MiRow >> subY) *
/// MI_SIZE` — the plane-space top-left of the block.
/// * the transform-block grid spans the §5.11.49 map's `block_w` ×
/// `block_h` extent (the post-subsampling, post-`<4`-bump block
/// size the `palette_tokens` reader used), tiled by `Tx_Width` ×
/// `Tx_Height`.
/// * each transform block whose `startX`/`startY` is in-frame
/// (§5.11.35 line 13 `startX < maxX && startY < maxY`, with `maxX =
/// (MiCols * MI_SIZE) >> subX`, `maxY = (MiRows * MI_SIZE) >>
/// subY`) invokes [`crate::predict_palette`]; the predicted `w × h`
/// tile is stitched into `CurrFrame[plane]` at `(startX, startY)`.
///
/// The `palette` colour table is read from the walker's own §5.11.46
/// `PaletteColors[plane][MiRow][MiCol][ 0..PaletteSize ]` grid (the
/// state `palette_mode_info` stamped at decode time); the caller does
/// not pass it. The plane buffer is lazily allocated at the full
/// `(MiRows × MI_SIZE) >> subY` × `(MiCols × MI_SIZE) >> subX`
/// extent (mirroring [`Self::ensure_curr_frame_plane`]) if this is the
/// first writer on the plane.
///
/// ## Arguments
///
/// * `plane` — `0` (luma, reads `ColorMapY` + `palette_colors_y`),
/// `1` (U) or `2` (V); planes `1`/`2` both read `ColorMapUV` (the
/// chroma planes share one index map) but their own
/// `palette_colors_{u,v}` grid.
/// * `map` — the §5.11.49 [`DecodedPaletteMap`] for the plane
/// (`color_map_y` for `plane == 0`, `color_map_uv` for `plane !=
/// 0`).
/// * `mi_row` / `mi_col` — the block's top-left in mi-units (the
/// §5.11.5 `MiRow` / `MiCol`).
/// * `tx_sz` — the block's transform-block size ordinal
/// (`0..TX_SIZES_ALL`); palette blocks use one uniform TX size, so
/// the whole-block grid is tiled by `Tx_Width[tx_sz]` ×
/// `Tx_Height[tx_sz]`.
/// * `sub_x` / `sub_y` — the §5.11.34 plane subsampling (`0` for
/// `plane == 0`).
/// * `palette_size` — the §5.11.46 `PaletteSize{Y,UV}` (`2..=8`);
/// only the first `palette_size` entries of the grid palette are
/// used.
///
/// ## Returns
///
/// `Ok(())` on success. Returns [`crate::Error::PartitionWalkOutOfRange`]
/// for a caller-bug argument: `plane >= 3`, `tx_sz >= TX_SIZES_ALL`,
/// `palette_size` outside `1..=PALETTE_COLORS`, a `map` whose
/// `stride < block_w` or `data.len() < block_h * stride`, an
/// allocation failure, or anything [`crate::predict_palette`] itself
/// rejects (an out-of-window map index, a map too small for the
/// requested transform-block window).
#[allow(clippy::too_many_arguments)]
pub fn reconstruct_palette_block_into_curr_frame(
&mut self,
plane: usize,
map: &DecodedPaletteMap,
mi_row: u32,
mi_col: u32,
tx_sz: usize,
sub_x: u8,
sub_y: u8,
palette_size: usize,
) -> Result<(), crate::Error> {
if plane >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if tx_sz >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if palette_size == 0 || palette_size > PALETTE_COLORS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if map.stride < map.block_w || map.data.len() < map.block_h.saturating_mul(map.stride) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.46 `palette = palette_colors_{y,u,v}[ 0..PaletteSize ]`
// read from the walker's own grid at the block anchor.
let mut palette: [u16; PALETTE_COLORS] = [0; PALETTE_COLORS];
for (idx, slot) in palette.iter_mut().enumerate().take(palette_size) {
*slot = self.palette_color_at(plane, mi_row as i32, mi_col as i32, idx);
}
let palette = &palette[..palette_size];
// §5.11.35 `baseX = (MiCol >> subX) * MI_SIZE`,
// `baseY = (MiRow >> subY) * MI_SIZE`.
let base_x = (mi_col >> sub_x) * (MI_SIZE as u32);
let base_y = (mi_row >> sub_y) * (MI_SIZE as u32);
// §5.11.35 `maxX = (MiCols * MI_SIZE) >> subX`,
// `maxY = (MiRows * MI_SIZE) >> subY`.
let max_x = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
let max_y = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
// Lazily allocate the plane buffer at the full §7.12.3-step-3
// extent if untouched (palette is an intra writer; like the
// single-ref inter bridge there is no prior-writer prerequisite).
let rows = max_y;
let cols = max_x;
self.ensure_curr_frame_plane(plane, rows, cols);
let buf_cols = match self.curr_frame[plane].as_ref() {
Some(cp) => cp.cols as usize,
None => return Err(crate::Error::PartitionWalkOutOfRange),
};
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
// §5.11.35 `stepX = Tx_Width[txSz] >> MI_SIZE_LOG2`,
// `stepY = Tx_Height[txSz] >> MI_SIZE_LOG2` (in 4×4 units).
let step_x = (w >> MI_SIZE_LOG2).max(1);
let step_y = (h >> MI_SIZE_LOG2).max(1);
// The §5.11.49 map covers the block's `block_w × block_h` plane-
// space extent; the transform-block grid walks it in 4×4 units.
let units_w = map.block_w.div_ceil(4);
let units_h = map.block_h.div_ceil(4);
// Per-TU predict buffer.
let mut pred: [u16; 64 * 64] = [0; 64 * 64];
let mut y = 0usize;
while y < units_h {
let mut x = 0usize;
while x < units_w {
// §5.11.35 `startX = baseX + 4 * x`,
// `startY = baseY + 4 * y`.
let start_x = base_x + 4 * (x as u32);
let start_y = base_y + 4 * (y as u32);
// §5.11.35 line 13: skip transform blocks whose top-left
// is past the frame edge.
if start_x >= max_x || start_y >= max_y {
x += step_x;
continue;
}
// §7.11.4: predict this transform block's `w × h` tile.
crate::predict_palette(
tx_sz,
x,
y,
&map.data,
map.stride,
palette,
&mut pred[..h * w],
)?;
// Stitch the predicted tile into CurrFrame[plane], clipping
// any overhang past the right / bottom frame edge (the
// §5.11.35 map window can extend past `maxX`/`maxY` at the
// block's frame-boundary TUs; only in-frame samples are
// written).
if let Some(cp) = self.curr_frame[plane].as_mut() {
let copy_w = (max_x.saturating_sub(start_x) as usize).min(w);
let copy_h = (max_y.saturating_sub(start_y) as usize).min(h);
for i in 0..copy_h {
let dst_row = (start_y as usize + i) * buf_cols + start_x as usize;
let src_row = i * w;
for j in 0..copy_w {
cp.samples[dst_row + j] = pred[src_row + j] as i32;
}
}
}
x += step_x;
}
y += step_y;
}
Ok(())
}
/// §5.11.35 `predict_palette( plane, startX, startY, x, y, txSz )`
/// for ONE transform block of the block currently being decoded —
/// the in-walk §7.11.4 palette-prediction arm the per-TU
/// [`Self::transform_block_emit`] dispatch runs when
/// `PaletteSize{Y,UV} > 0` (av1-spec p.85 lines 5273-5274).
///
/// Reads the §5.11.46 colour table from the walker's stamped
/// `PaletteColors[plane][MiRow][MiCol]` grid and the §5.11.49
/// colour-index map from the per-block
/// [`Self::current_color_map_y`] / [`Self::current_color_map_uv`]
/// state [`Self::decode_block_syntax`] parked before invoking
/// `residual()`, then writes `palette[ map[ y*4 + i ][ x*4 + j ] ]`
/// over the TU's `Tx_Width × Tx_Height` footprint into
/// `CurrFrame[plane]` (clipping any right/bottom frame-edge
/// overhang). `(x, y)` are the §5.11.34 TU indices in 4×4 units
/// relative to the block origin — palette-coded blocks are capped
/// at `BLOCK_64X64` (§5.11.46), so the §5.11.34 chunk fold never
/// applies to them.
#[allow(clippy::too_many_arguments)]
fn predict_palette_tu_into_curr_frame(
&mut self,
plane: u8,
start_x: u32,
start_y: u32,
x: u32,
y: u32,
tx_sz: usize,
mi_row: u32,
mi_col: u32,
sub_x: u8,
sub_y: u8,
) -> Result<(), crate::Error> {
let plane_us = plane as usize;
if plane_us >= 3 || tx_sz >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let palette_size = if plane == 0 {
self.current_palette_size_y
} else {
self.current_palette_size_uv
} as usize;
if palette_size == 0 || palette_size > PALETTE_COLORS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.46 `palette = palette_colors_{y,u,v}[ 0..PaletteSize ]`
// from the walker's grid at the block anchor (plane 1 / plane 2
// read their own colour tables; both share `ColorMapUV`).
let mut palette: [u16; PALETTE_COLORS] = [0; PALETTE_COLORS];
for (idx, slot) in palette.iter_mut().enumerate().take(palette_size) {
*slot = self.palette_color_at(plane_us, mi_row as i32, mi_col as i32, idx);
}
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
let mut pred = vec![0u16; w * h];
{
let map = if plane == 0 {
self.current_color_map_y.as_ref()
} else {
self.current_color_map_uv.as_ref()
}
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
crate::predict_palette(
tx_sz,
x as usize,
y as usize,
&map.data,
map.stride,
&palette[..palette_size],
&mut pred,
)?;
}
// Stitch into CurrFrame[plane], clipping frame-edge overhang.
let max_x = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
let max_y = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
self.ensure_curr_frame_plane(plane_us, max_y, max_x);
let Some(cp) = self.curr_frame[plane_us].as_mut() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
let copy_w = (max_x.saturating_sub(start_x) as usize).min(w);
let copy_h = (max_y.saturating_sub(start_y) as usize).min(h);
for i in 0..copy_h {
let dst = (start_y as usize + i) * buf_cols + start_x as usize;
let src = i * w;
for j in 0..copy_w {
cp.samples[dst + j] = pred[src + j] as i32;
}
}
Ok(())
}
/// §5.11.33 `predict()` **frame-scope** inter pixel reconstruction
/// against the walker's own tracked `CurrFrame[plane]` buffers, driven
/// from the grids the §5.11 syntax walk
/// ([`Self::decode_tile_syntax`]) already populated (av1-spec p.82-83).
///
/// This is the frame-level counterpart of the per-block
/// [`Self::reconstruct_inter_block_into_curr_frame`] bridge: instead of
/// the caller threading one decoded block's `(RefFrame[0], Mvs[..][0],
/// InterpFilters)` quad, this method reads those quantities back out of
/// the walker's persisted `IsInters[]` / `RefFrames[]` / `Mvs[]` /
/// `InterpFilters[]` / `MiSizes[]` grids — the exact arrays the
/// §5.11.18 → §5.11.23 → §5.11.31 inter-syntax cascade stamped per
/// leaf — and drives every **single-reference translational** (`SIMPLE`
/// motion-mode, `RefFrame[1] == NONE`) inter leaf through the shared
/// frame walk [`crate::reconstruct_inter_frame`], stitching each leaf's
/// §7.11.3.1 motion-compensated prediction into `CurrFrame[plane]`.
///
/// This closes the §5.11.33 frame-walk loop on the single-ref path: the
/// §5.11 syntax walk reads the inter mode-info + MVs into the grids,
/// and this bridge then reconstructs the inter pixels from those grids
/// (plus the caller's §7.11.3.3 reference-frame store), so after both
/// passes the per-plane `curr_frame` buffers hold the reconstructed inter tile
/// (pre-§7.14 loop-filter / §7.15 CDEF / §7.17 loop-restoration). The
/// residual half (§5.11.34 `transform_tree` coefficient adds) is merged
/// into `CurrFrame[plane]` by the §7.12.3 step-3 path during the syntax
/// walk; this bridge supplies the §7.11.3.1 prediction half on top of
/// whatever the merge already wrote, exactly as the per-block bridge
/// does for one leaf.
///
/// ## Reference state (`plane_refs`)
///
/// One [`crate::PlaneRefSpec`] per plane to reconstruct (`0` Y, `1` Cb,
/// `2` Cr), each carrying the plane's §7.11.3.3 `FrameStore` slice
/// (per-plane resolved), `subsampling_*`, and per-plane
/// `(FrameWidth, FrameHeight)`. `ref_frame_idx` is the §6.8.2
/// `ref_frame_idx[ 0..REFS_PER_FRAME ]` mapping each `RefFrame -
/// LAST_FRAME` to a `FrameStore` slot. `bit_depth` is §7.11.3
/// `BitDepth`. The parser does not own the decoded-picture buffer, so
/// the reference state is caller-supplied.
///
/// ## Scope (single-reference translational arm)
///
/// Compound (`RefFrame[1] >= LAST_FRAME`), inter-intra (`RefFrame[1]
/// == INTRA_FRAME`), and warped (`motion_mode == WARPED_CAUSAL`) leaves
/// carry per-leaf side-data (`compound_type` / wedge / mask /
/// `interintra_mode` / local-warp fit) that the walker records on the
/// emitted [`DecodedBlockRecord`] list rather than in dedicated
/// per-cell grids. This frame bridge supplies the
/// [`crate::reconstruct_inter_frame`] side-data slices as neutral
/// (all-zero) buffers and runs **without** the warp arm
/// ([`crate::InterModeInfoGrid::warp`] = `None`), so the walk drives
/// every single-ref leaf translationally. On a frame whose leaves are
/// all single-ref `SIMPLE` (the common P-frame case) the neutral
/// side-data is never read (the underlying walk reads the wedge / mask /
/// interintra slices only at a compound / inter-intra leaf origin).
/// Driving the compound / inter-intra / warp leaves at frame scope is a
/// follow-up that threads the per-leaf side-data grids the same way the
/// per-block compound / warp bridges already accept it.
///
/// Each plane buffer is allocated (sized `MiRows × MiCols × MI_SIZE >>
/// subsampling_*`) if it has not been touched yet; the walker buffer is
/// `i32` (post-`Clip1`), the driver works in `u16`, so the bridge
/// mirrors each plane to `u16`, drives the frame, and copies the result
/// back as `i32` (lossless in both directions).
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for an empty
/// `plane_refs`, a `plane_refs[k].plane >= 3`, an allocation failure,
/// or anything [`crate::reconstruct_inter_frame`] itself rejects (an
/// out-of-range leaf `MiSize`, an `INTRA_FRAME`/out-of-range
/// `RefFrame[0]` on an inter leaf, an out-of-range resolved `refIdx`,
/// a `CurrFrame` plane too small for a write region).
pub fn reconstruct_inter_frame_into_curr_frame(
&mut self,
ref_frame_idx: &[u8],
bit_depth: u8,
plane_refs: &[crate::PlaneRefSpec<'_>],
) -> Result<(), crate::Error> {
// Delegate with identity-zero §7.11.3.15 order hints. The
// mask-free [`COMPOUND_AVERAGE`], the [`COMPOUND_WEDGE`], the
// [`COMPOUND_DIFFWTD`], and the inter-intra arms all reconstruct
// independently of the order-hint context; only the
// [`COMPOUND_DISTANCE`] (`enable_jnt_comp`) arm consumes it, so a
// frame carrying distance-weighted compound leaves should call
// [`Self::reconstruct_inter_frame_into_curr_frame_with_order_hints`]
// with the §5.9.2 `OrderHint` / §5.5.1 `OrderHintBits` /
// §7.4 `OrderHints[ ref ]` from the frame header instead.
self.reconstruct_inter_frame_into_curr_frame_with_order_hints(
ref_frame_idx,
bit_depth,
plane_refs,
FrameInterOrderHints::IDENTITY,
)
}
/// §5.11.33 frame-level inter reconstruction with the §7.11.3.15
/// order-hint context threaded through, so the [`COMPOUND_DISTANCE`]
/// (`enable_jnt_comp`) compound arm derives its `(FwdWeight,
/// BckWeight)` from the real per-reference output order hints rather
/// than the identity-zero fallback. Otherwise identical to
/// [`Self::reconstruct_inter_frame_into_curr_frame`]; see that
/// method's docs.
pub fn reconstruct_inter_frame_into_curr_frame_with_order_hints(
&mut self,
ref_frame_idx: &[u8],
bit_depth: u8,
plane_refs: &[crate::PlaneRefSpec<'_>],
order_hints: FrameInterOrderHints,
) -> Result<(), crate::Error> {
if plane_refs.is_empty() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let mi_rows = self.mi_rows;
let mi_cols = self.mi_cols;
let cells = (mi_rows as usize)
.checked_mul(mi_cols as usize)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// Snapshot the grid slices into owned buffers. `reconstruct_inter_frame`
// borrows the grid (`&InterModeInfoGrid`) and the plane buffers
// (`&mut [PlaneReconContext]`) at once; the grid slices live in
// `&self` while the plane mirrors live in fresh allocations, so
// copying the grids out of `self` lets both borrows coexist.
let mut mi_sizes_snap: Vec<usize> = Vec::new();
let mut is_inters_snap: Vec<u8> = Vec::new();
let mut ref_frames_snap: Vec<i8> = Vec::new();
let mut mvs_snap: Vec<i16> = Vec::new();
let mut interp_filters_snap: Vec<u8> = Vec::new();
if mi_sizes_snap.try_reserve_exact(cells).is_err()
|| is_inters_snap.try_reserve_exact(cells).is_err()
|| ref_frames_snap.try_reserve_exact(cells * 2).is_err()
|| mvs_snap.try_reserve_exact(cells * 4).is_err()
|| interp_filters_snap.try_reserve_exact(cells * 2).is_err()
{
return Err(crate::Error::PartitionWalkOutOfRange);
}
mi_sizes_snap.extend_from_slice(&self.mi_sizes[..cells]);
is_inters_snap.extend_from_slice(&self.is_inters[..cells]);
ref_frames_snap.extend_from_slice(&self.ref_frames[..cells * 2]);
mvs_snap.extend_from_slice(&self.mvs[..cells * 4]);
interp_filters_snap.extend_from_slice(&self.interp_filters[..cells * 2]);
// Snapshot the §5.11.29 / §5.11.28 compound / inter-intra
// side-data grids the §5.11.23 inter cascade stamped, so the
// frame walk dispatches a decoded compound (AVERAGE / DISTANCE /
// WEDGE / DIFFWTD) or inter-intra leaf through the matching
// §7.11.3 arm instead of driving it translationally. Each is
// read only at the matching leaf-origin cell; the stamped values
// collapse to the §5.11.29 / §5.11.28 short-circuit defaults on
// single-ref / intra cells the walk never reads.
let mut compound_types_snap: Vec<u8> = Vec::new();
let mut compound_wedge_indices_snap: Vec<u8> = Vec::new();
let mut compound_wedge_signs_snap: Vec<u8> = Vec::new();
let mut compound_mask_types_snap: Vec<u8> = Vec::new();
let mut interintra_modes_snap: Vec<u8> = Vec::new();
let mut wedge_interintras_snap: Vec<u8> = Vec::new();
let mut interintra_wedge_indices_snap: Vec<u8> = Vec::new();
if compound_types_snap.try_reserve_exact(cells).is_err()
|| compound_wedge_indices_snap
.try_reserve_exact(cells)
.is_err()
|| compound_wedge_signs_snap.try_reserve_exact(cells).is_err()
|| compound_mask_types_snap.try_reserve_exact(cells).is_err()
|| interintra_modes_snap.try_reserve_exact(cells).is_err()
|| wedge_interintras_snap.try_reserve_exact(cells).is_err()
|| interintra_wedge_indices_snap
.try_reserve_exact(cells)
.is_err()
{
return Err(crate::Error::PartitionWalkOutOfRange);
}
compound_types_snap.extend_from_slice(&self.compound_types[..cells]);
compound_wedge_indices_snap.extend_from_slice(&self.compound_wedge_indices[..cells]);
compound_wedge_signs_snap.extend_from_slice(&self.compound_wedge_signs[..cells]);
compound_mask_types_snap.extend_from_slice(&self.compound_mask_types[..cells]);
interintra_modes_snap.extend_from_slice(&self.interintra_modes[..cells]);
wedge_interintras_snap.extend_from_slice(&self.wedge_interintras[..cells]);
interintra_wedge_indices_snap.extend_from_slice(&self.interintra_wedge_indices[..cells]);
// §5.11.27 / §7.11.3.9 OBMC context: snapshot the per-cell
// `motion_modes` grid the §5.11.27 read stamped, and derive the
// §5.11.18 `AvailU` / `AvailL` per-cell availability grids from the
// walker's tile geometry (`AvailU = is_inside(MiRow - 1, MiCol)`,
// `AvailL = is_inside(MiRow, MiCol - 1)` — av1-spec lines
// 3915-3916). The frame walk then dispatches a decoded
// `motion_mode == OBMC` single-ref leaf to the §7.11.3.9-10 overlap
// path, resolving its neighbour lists internally from the grid.
let mut motion_modes_snap: Vec<u8> = Vec::new();
let mut avail_u_snap: Vec<u8> = Vec::new();
let mut avail_l_snap: Vec<u8> = Vec::new();
if motion_modes_snap.try_reserve_exact(cells).is_err()
|| avail_u_snap.try_reserve_exact(cells).is_err()
|| avail_l_snap.try_reserve_exact(cells).is_err()
{
return Err(crate::Error::PartitionWalkOutOfRange);
}
motion_modes_snap.extend_from_slice(&self.motion_modes[..cells]);
for r in 0..(mi_rows as usize) {
for c in 0..(mi_cols as usize) {
avail_u_snap.push(self.geometry.is_inside(r as i32 - 1, c as i32) as u8);
avail_l_snap.push(self.geometry.is_inside(r as i32, c as i32 - 1) as u8);
}
}
// Mirror each plane's `i32` `curr_frame` buffer to `u16`,
// allocating the plane if the residual merge has not yet touched it
// (a skip-coded P-frame block has no residual writer, so its plane
// can be untouched at this point). Samples are post-`Clip1` so the
// `i32 → u16` narrowing is lossless.
let mut mirrors: Vec<(u8, Vec<u16>, usize)> = Vec::new();
if mirrors.try_reserve(plane_refs.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
for spec in plane_refs {
let p = spec.plane as usize;
if p >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let rows = (mi_rows * (MI_SIZE as u32)) >> spec.subsampling_y;
let cols = (mi_cols * (MI_SIZE as u32)) >> spec.subsampling_x;
self.ensure_curr_frame_plane(p, rows, cols);
let Some(cp) = self.curr_frame[p].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let buf_cols = cp.cols as usize;
let mut mirror: Vec<u16> = Vec::new();
if mirror.try_reserve_exact(cp.samples.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
mirror.extend(cp.samples.iter().map(|&s| s as u16));
mirrors.push((spec.plane, mirror, buf_cols));
}
// Drive the frame walk. The grid snapshots and plane mirrors are
// disjoint owned buffers, so the `&grid` / `&mut planes` borrows
// coexist.
{
let grid = crate::InterModeInfoGrid {
mi_sizes: &mi_sizes_snap,
is_inters: &is_inters_snap,
ref_frames: &ref_frames_snap,
mvs: &mvs_snap,
interp_filters: &interp_filters_snap,
compound_types: &compound_types_snap,
wedge_indices: &compound_wedge_indices_snap,
wedge_signs: &compound_wedge_signs_snap,
mask_types: &compound_mask_types_snap,
interintra_modes: &interintra_modes_snap,
wedge_interintras: &wedge_interintras_snap,
interintra_wedge_indices: &interintra_wedge_indices_snap,
order_hint_bits: order_hints.order_hint_bits,
current_order_hint: order_hints.current_order_hint,
order_hints_by_ref: &order_hints.order_hints_by_ref,
mi_rows,
mi_cols,
bit_depth,
warp: None,
obmc: Some(crate::GridObmcContext {
motion_modes: &motion_modes_snap,
avail_u: &avail_u_snap,
avail_l: &avail_l_snap,
}),
};
let mut planes: Vec<crate::PlaneReconContext<'_>> = Vec::new();
if planes.try_reserve(plane_refs.len()).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
for ((p, mirror, cols), spec) in mirrors.iter_mut().zip(plane_refs.iter()) {
planes.push(crate::PlaneReconContext {
plane: *p,
subsampling_x: spec.subsampling_x,
subsampling_y: spec.subsampling_y,
frame_store: spec.frame_store,
frame_width: spec.frame_width,
frame_height: spec.frame_height,
curr: mirror.as_mut_slice(),
curr_stride: *cols,
});
}
crate::reconstruct_inter_frame(&grid, ref_frame_idx, &mut planes)?;
}
// Copy the (post-`Clip1`, in-range) `u16` result back into the
// walker's `i32` `curr_frame` buffers.
for (p, mirror, _cols) in &mirrors {
if let Some(cp) = self.curr_frame[*p as usize].as_mut() {
for (dst, &src) in cp.samples.iter_mut().zip(mirror.iter()) {
*dst = src as i32;
}
}
}
Ok(())
}
/// In-walk §5.11.33 `predict_inter` — motion-compensate ONE inter
/// leaf into `CurrFrame[ plane ]` at its spec decode position (the
/// §5.11.5 `compute_prediction()` call site, between `mode_info()`
/// and `residual()`).
///
/// The §5.11.5 inter arm invokes this when the caller supplies
/// [`InterFrameContext::pixels`]; the §7.12.3 step-3 residual merge
/// that follows in `residual()` then lands on the leaf's prediction
/// (the spec's `CurrFrame = Clip1( CurrFrame + Residual )`), and a
/// later intra block's §7.11.2 neighbour reads observe reconstructed
/// inter pixels.
///
/// Dispatch mirrors the frame-scope bridge
/// ([`Self::reconstruct_inter_frame_into_curr_frame_with_order_hints`]):
/// single-ref SIMPLE / OBMC, compound (AVERAGE / DISTANCE / WEDGE /
/// DIFFWTD), and inter-intra leaves all run through the shared
/// [`crate::inter_pred`] per-leaf walk against the walker's own
/// §5.11.5 grids (already stamped for this leaf — the §5.11.5
/// grid-fill precedes `compute_prediction()`). For an inter-intra
/// leaf the caller must have written the §5.11.33 intra half into
/// `CurrFrame[ plane ]` first (the §5.11.5 inter arm does).
///
/// A `WARPED_CAUSAL` leaf surfaces
/// [`crate::Error::InterBlockModeInfoUnsupported`] — the §7.11.3.8
/// local-warp fit is not yet derivable in-walk (the §7.10.4
/// sample list is not retained); wiring it is the warped-motion
/// follow-up.
///
/// The §7.11.3 drivers operate on `u16` planes while the walker's
/// `CurrFrame` is post-`Clip1` `i32`; the leaf's plane rect is
/// mirrored through [`Self::inter_pred_scratch`] in both directions
/// (lossless).
pub fn predict_inter_leaf_from_walk(
&mut self,
mi_row: u32,
mi_col: u32,
mi_size: usize,
ctx: &InterFrameContext<'_>,
) -> Result<(), crate::Error> {
if mi_size >= BLOCK_SIZES || mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let pixels = ctx.pixels.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let mi_rows = self.mi_rows;
let mi_cols = self.mi_cols;
let cells = (mi_rows as usize) * (mi_cols as usize);
if pixels.plane_refs.is_empty() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Per-plane leaf rect: `(base, w, h)` in plane samples, clipped
// to the plane's mi-grid extent — the §5.11.33 write region.
struct LeafRect {
plane: usize,
x0: usize,
y0: usize,
w: usize,
h: usize,
stride: usize,
}
let mut rects: Vec<LeafRect> = Vec::with_capacity(pixels.plane_refs.len());
for spec in pixels.plane_refs {
let p = spec.plane as usize;
if p >= 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let (sub_x, sub_y) = if spec.plane > 0 {
(spec.subsampling_x, spec.subsampling_y)
} else {
(0, 0)
};
let rows = ((mi_rows * (MI_SIZE as u32)) >> sub_y) as usize;
let cols = ((mi_cols * (MI_SIZE as u32)) >> sub_x) as usize;
self.ensure_curr_frame_plane(p, rows as u32, cols as u32);
// Size the scratch mirror once per plane (stale samples
// outside the synced rect are never read).
if self.inter_pred_scratch[p].len() != rows * cols {
self.inter_pred_scratch[p].resize(rows * cols, 0);
}
let plane_sz = get_plane_residual_size(mi_size, spec.plane, sub_x, sub_y)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let x0 = ((mi_col >> sub_x) as usize) * MI_SIZE;
let y0 = ((mi_row >> sub_y) as usize) * MI_SIZE;
let w = (num_4x4_blocks_wide(plane_sz) * 4).min(cols.saturating_sub(x0));
let h = (num_4x4_blocks_high(plane_sz) * 4).min(rows.saturating_sub(y0));
rects.push(LeafRect {
plane: p,
x0,
y0,
w,
h,
stride: cols,
});
}
// Mirror the leaf rects `i32 → u16` into the scratch planes.
for r in &rects {
let Some(cp) = self.curr_frame[r.plane].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let scratch = &mut self.inter_pred_scratch[r.plane];
for y in r.y0..r.y0 + r.h {
for x in r.x0..r.x0 + r.w {
scratch[y * r.stride + x] = cp.samples[y * r.stride + x] as u16;
}
}
}
// §5.9.24 global-motion tables in the [`crate::GridWarpContext`]
// shape (`GmType` as `u8` ordinals, the affine matrices
// flattened row-major by `RefFrame`).
let mut gm_types_u8 = [0u8; 8];
let mut gm_flat = [0i32; 48];
for r in 0..8usize {
gm_types_u8[r] = ctx.gm_type[r] as u8;
gm_flat[r * 6..r * 6 + 6].copy_from_slice(&ctx.gm_params[r]);
}
// §5.11.27 `is_scaled( refFrame )` by raw RefFrame value —
// the §7.11.3.1 step-7 `useWarp = 2` global gate consumes it.
let mut is_scaled_by_ref = [false; 8];
for (i, s) in ctx.is_scaled_per_ref.iter().enumerate() {
is_scaled_by_ref[crate::uncompressed_header_tail::LAST_FRAME + i] = *s;
}
// Drive the shared per-leaf walk. Field-split borrows: the grid
// views borrow the walker's grid vectors immutably while the
// plane contexts borrow `inter_pred_scratch` mutably.
{
if self.walk_avail_u.len() < cells || self.walk_avail_l.len() < cells {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let grid = crate::InterModeInfoGrid {
mi_sizes: &self.mi_sizes,
is_inters: &self.is_inters,
ref_frames: &self.ref_frames,
mvs: &self.mvs,
interp_filters: &self.interp_filters,
compound_types: &self.compound_types,
wedge_indices: &self.compound_wedge_indices,
wedge_signs: &self.compound_wedge_signs,
mask_types: &self.compound_mask_types,
interintra_modes: &self.interintra_modes,
wedge_interintras: &self.wedge_interintras,
interintra_wedge_indices: &self.interintra_wedge_indices,
order_hint_bits: pixels.order_hints.order_hint_bits,
current_order_hint: pixels.order_hints.current_order_hint,
order_hints_by_ref: &pixels.order_hints.order_hints_by_ref,
mi_rows,
mi_cols,
bit_depth: pixels.bit_depth,
// §7.11.3.1 step-6/7 warp context: the per-cell
// §7.11.3.8 fit the §5.11.5 inter arm stamped for
// WARPED_CAUSAL leaves, plus the §5.9.24 global-motion
// tables (the `useWarp == 2` GLOBALMV arm fires even on
// `motion_mode == SIMPLE` leaves when
// `GmType[ RefFrame[0] ] > TRANSLATION`).
warp: Some(crate::GridWarpContext {
motion_modes: &self.motion_modes,
local_warp_params: &self.local_warp_params,
local_warp_valid: &self.local_warp_valid,
y_modes: &self.y_modes,
gm_types: &gm_types_u8,
gm_params: &gm_flat,
force_integer_mv: ctx.force_integer_mv,
is_scaled: &is_scaled_by_ref,
}),
obmc: Some(crate::GridObmcContext {
motion_modes: &self.motion_modes,
avail_u: &self.walk_avail_u,
avail_l: &self.walk_avail_l,
}),
};
let mut scratch_iter = self.inter_pred_scratch.iter_mut();
let (s0, s1, s2) = (
scratch_iter.next().unwrap(),
scratch_iter.next().unwrap(),
scratch_iter.next().unwrap(),
);
let mut slots: [Option<&mut Vec<u16>>; 3] = [Some(s0), Some(s1), Some(s2)];
let mut planes: Vec<crate::PlaneReconContext<'_>> =
Vec::with_capacity(pixels.plane_refs.len());
for (spec, rect) in pixels.plane_refs.iter().zip(rects.iter()) {
let buf = slots[rect.plane]
.take()
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
planes.push(crate::PlaneReconContext {
plane: spec.plane,
subsampling_x: spec.subsampling_x,
subsampling_y: spec.subsampling_y,
frame_store: spec.frame_store,
frame_width: spec.frame_width,
frame_height: spec.frame_height,
curr: buf.as_mut_slice(),
curr_stride: rect.stride,
});
}
crate::inter_pred::reconstruct_inter_leaf_at(
&grid,
&pixels.ref_frame_idx,
&mut planes,
mi_row as usize,
mi_col as usize,
)?;
}
// Mirror the leaf rects back `u16 → i32`.
for r in &rects {
let scratch = &self.inter_pred_scratch[r.plane];
let Some(cp) = self.curr_frame[r.plane].as_mut() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
for y in r.y0..r.y0 + r.h {
for x in r.x0..r.x0 + r.w {
cp.samples[y * r.stride + x] = scratch[y * r.stride + x] as i32;
}
}
}
Ok(())
}
/// §7.11.3 intra-block-copy prediction for one `use_intrabc == 1`
/// leaf — writes the §7.11.3.4 translational prediction for every
/// visited plane into `CurrFrame[ plane ]`, reading from the SAME
/// frame's already-reconstructed samples (§7.11.3.4: "If refIdx is
/// equal to -1, ref is set equal to CurrFrame").
///
/// Mirrors the §5.11.33 `compute_prediction()` `is_inter` arm for
/// an intra-block-copy block. Every cell the §5.11.33
/// `someUseIntra` scan visits carries `RefFrames[ .. ][ 0 ] ==
/// INTRA_FRAME` (the §5.11.7 `use_intrabc == 1` stamp covers the
/// whole footprint, and on an intra frame every `is_inter` block
/// is an intra-block-copy block), so `someUseIntra == 1` always
/// fires: `predW = num4x4W * 4`, `predH = num4x4H * 4`, `candRow =
/// MiRow`, `candCol = MiCol` — ONE §7.11.3.1 `predict_inter`
/// invocation per plane at the full plane-block geometry, with the
/// block's own MV and the §5.11.7 fixed `interp_filter[ 0..2 ] =
/// BILINEAR` pair.
///
/// §7.11.3.1 step 9 sets `RefFrameWidth[ -1 ] = FrameWidth`,
/// `RefFrameHeight[ -1 ] = FrameHeight` and `RefUpscaledWidth[ -1 ]
/// = UpscaledWidth` so the §7.11.3.3 scaling is the identity —
/// §5.9.20 reads `allow_intrabc` only when `UpscaledWidth ==
/// FrameWidth`, so both ratios are exactly `1 << REF_SCALE_SHIFT`
/// regardless of the dimension values, and the mi-padded extents
/// stand in for the real frame dimensions here. Step 11 then
/// overrides the reference extents to `MiCols * MI_SIZE` and
/// `MiRows * MI_SIZE` so the §7.11.3.4 edge clamp reaches the
/// mi-padded decoded extent ("to avoid intrabc prediction being cropped to
/// the frame boundaries") — exactly the walker's `CurrFrame[
/// plane ]` allocation.
///
/// The §7.11.3.4 kernel reads a bounded window of `CurrFrame`
/// (rows `(startY >> 10) - 3 ..` for `intermediateHeight` rows,
/// cols `(startX >> 10) - 3 ..= ((startX + stepX * (w - 1)) >>
/// 10) + 4`, every access clamped to the plane edges). The window
/// between the clamped extremes is copied out first — clamping
/// against the window bounds is then sample-identical to clamping
/// against the plane bounds, because the clamp is monotone and the
/// window's edge rows / columns ARE the clamped extremes.
///
/// The final write applies the §7.11.3.1 single-ref arm
/// (`CurrFrame[ plane ][ y + i ][ x + j ] = Clip1( preds[ 0 ][ i ][
/// j ] )`), clipped to the in-plane rows / columns for §5.11.4
/// bottom/right-edge overhanging leaves (the prediction still runs
/// at the full block geometry).
#[allow(clippy::too_many_arguments)]
fn predict_intrabc_leaf_into_curr_frame(
&mut self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
has_chroma: bool,
subsampling_x: u8,
subsampling_y: u8,
bit_depth: u8,
mv: [i32; 2],
) -> Result<(), crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §6.10.27 conformance bound `Abs( Mv ) < (1 << 14)` keeps the
// i32 → i16 narrowing below lossless.
if mv[0].unsigned_abs() >= (1 << 14) || mv[1].unsigned_abs() >= (1 << 14) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let mv16 = [mv[0] as i16, mv[1] as i16];
// §7.11.3.2 with `isCompound = 0` (`RefFrame[ 1 ] = NONE` on
// the §5.11.7 intra-block-copy arm).
let rounding = crate::inter_pred::rounding_variables(bit_depth, false)?;
let clip_max: i32 = (1i32 << bit_depth) - 1;
let num_planes: u8 = 1 + if has_chroma { 2 } else { 0 };
for plane in 0..num_planes {
let (sub_x, sub_y) = if plane > 0 {
(subsampling_x, subsampling_y)
} else {
(0u8, 0u8)
};
let plane_sz = get_plane_residual_size(sub_size, plane, sub_x, sub_y)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// §5.11.33 `someUseIntra` arm: `predW = num4x4W * 4`,
// `predH = num4x4H * 4` (the full plane-block extent).
let w = NUM_4X4_BLOCKS_WIDE[plane_sz] * MI_SIZE;
let h = NUM_4X4_BLOCKS_HIGH[plane_sz] * MI_SIZE;
// §5.11.33 `baseX = (MiCol >> subX) * MI_SIZE`, `baseY =
// (MiRow >> subY) * MI_SIZE`.
let base_x = ((mi_col >> sub_x) as i32) * (MI_SIZE as i32);
let base_y = ((mi_row >> sub_y) as i32) * (MI_SIZE as i32);
// §7.11.3.1 step 11 reference extent — the mi-padded plane.
let plane_rows = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
let plane_cols = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
if plane_rows == 0 || plane_cols == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
self.ensure_curr_frame_plane(plane as usize, plane_rows, plane_cols);
// §7.11.3.3 — identity scaling (see the doc comment): the
// mi-padded extents stand in for FrameWidth / FrameHeight
// AND the refIdx = -1 dimensions.
let scale = crate::inter_pred::motion_vector_scaling(
plane,
subsampling_x,
subsampling_y,
self.mi_cols * (MI_SIZE as u32),
self.mi_rows * (MI_SIZE as u32),
self.mi_cols * (MI_SIZE as u32),
self.mi_rows * (MI_SIZE as u32),
base_x,
base_y,
mv16,
)?;
// §7.11.3.4 read window between the clamped extremes.
let last_x = plane_cols as i32 - 1;
let last_y = plane_rows as i32 - 1;
let scale_one = 1i64 << crate::inter_pred::SCALE_SUBPEL_BITS;
let intermediate_height = ((((h as i64) - 1) * (scale.step_y as i64) + scale_one - 1)
>> crate::inter_pred::SCALE_SUBPEL_BITS)
as i32
+ 8;
let y_raw0 = (scale.start_y >> crate::inter_pred::SCALE_SUBPEL_BITS) - 3;
let x_raw0 = (scale.start_x >> crate::inter_pred::SCALE_SUBPEL_BITS) - 3;
let x_raw1 = (((scale.start_x as i64 + (scale.step_x as i64) * ((w as i64) - 1))
>> crate::inter_pred::SCALE_SUBPEL_BITS) as i32)
+ 4;
let y0 = y_raw0.clamp(0, last_y);
let y1 = (y_raw0 + intermediate_height - 1).clamp(0, last_y);
let x0 = x_raw0.clamp(0, last_x);
let x1 = x_raw1.clamp(0, last_x);
let win_w = (x1 - x0 + 1) as usize;
let win_h = (y1 - y0 + 1) as usize;
let Some(cp) = self.curr_frame[plane as usize].as_ref() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
let stride = cp.cols as usize;
let mut win: Vec<u16> = Vec::new();
if win.try_reserve_exact(win_w * win_h).is_err() {
return Err(crate::Error::PartitionWalkOutOfRange);
}
for yy in y0..=y1 {
let row = (yy as usize) * stride;
for xx in x0..=x1 {
// `CurrFrame` samples are post-Clip1 (`0..=clip_max`),
// so the u16 narrowing is lossless.
win.push(cp.samples[row + xx as usize] as u16);
}
}
let mut pred = vec![0i32; w * h];
crate::inter_pred::block_inter_prediction(
plane,
subsampling_x,
subsampling_y,
&win,
win_w,
// r405 contract: `block_inter_prediction` takes the
// clamp extents in LUMA samples and re-derives the
// per-plane `lastX`/`lastY`. The window is already a
// per-plane construct, so lift its extents back:
// `(((n << s) + s) >> s) - 1 == n - 1` for either
// parity, landing the clamp exactly on the window
// edge rows/cols (which ARE the plane-clamp extremes).
(win_w as u32) << sub_x,
(win_h as u32) << sub_y,
scale.start_x - (x0 << crate::inter_pred::SCALE_SUBPEL_BITS),
scale.start_y - (y0 << crate::inter_pred::SCALE_SUBPEL_BITS),
scale.step_x,
scale.step_y,
w,
h,
crate::inter_pred::BILINEAR,
crate::inter_pred::BILINEAR,
rounding.inter_round0,
rounding.inter_round1,
&mut pred,
)?;
// §7.11.3.1 single-ref final write, clipped to the
// in-plane rows / columns (§5.11.4 overhang leaves).
let Some(cp) = self.curr_frame[plane as usize].as_mut() else {
return Err(crate::Error::PartitionWalkOutOfRange);
};
for i in 0..h {
let ty = base_y + i as i32;
if ty < 0 || ty > last_y {
continue;
}
let out_row = (ty as usize) * stride;
for j in 0..w {
let tx = base_x + j as i32;
if tx < 0 || tx > last_x {
continue;
}
cp.samples[out_row + tx as usize] = pred[i * w + j].clamp(0, clip_max);
}
}
}
Ok(())
}
/// View of the §6.10.4 `Skips[]` grid after the walk. Indexed
/// row-major: `skips()[ r * MiCols + c ]`. Cells that no leaf
/// covered carry `0` (the §5.11.11 [`Self::decode_skip`] writer
/// only fills the `bh4 * bw4` footprint of decoded blocks; the
/// initial-zero state matches the §5.11.5 `Skips[r+y][c+x] =
/// skip` line for a decoder whose §5.11.11 hasn't fired yet on
/// the block).
#[must_use]
pub fn skips(&self) -> &[u8] {
&self.skips
}
/// Helper to read `Skips[ r ][ c ]`. Returns `0` for out-of-grid
/// coordinates (the §8.3.2 skip-ctx walk already gates on
/// `AvailU` / `AvailL`, so an unavailable neighbour's
/// contribution is zero either way).
/// r464 — `Skips[ r ][ c ] != 0` for in-grid mi coordinates
/// (`false` outside the grid), the §7.15.1 per-cell predicate the
/// encoder's CDEF election evaluates on local unit windows.
#[doc(hidden)]
#[inline]
pub fn skip_at_mi(&self, r: u32, c: u32) -> bool {
self.skip_at(r as i32, c as i32) != 0
}
#[inline]
fn skip_at(&self, r: i32, c: i32) -> u8 {
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
self.skips[(r * self.mi_cols + c) as usize]
}
/// View of the §6.10.4 `SkipModes[]` grid after the walk. Indexed
/// row-major: `skip_modes()[ r * MiCols + c ]`. Cells that no leaf
/// covered carry `0` (the §5.11.10 [`Self::decode_skip_mode`]
/// writer only fills the `bh4 * bw4` footprint of decoded blocks;
/// the initial-zero state matches the §5.11.5 `SkipModes[r+y][c+x]
/// = skip_mode` line for a decoder whose §5.11.10 hasn't fired
/// yet on the block).
#[must_use]
pub fn skip_modes(&self) -> &[u8] {
&self.skip_modes
}
/// Current `CurrentQIndex` (§5.11.12) — the running quantiser
/// index. Always in `1..=255` after at least one §5.11.12 call
/// because the spec applies `Clip3(1, 255, ...)`; the fresh-walker
/// initial value is `0` (callers should set it to §5.9.12
/// `base_q_idx` via [`Self::set_current_q_index`] at the first
/// block of the tile per §6.10.4).
#[must_use]
pub fn current_q_index(&self) -> i32 {
self.current_q_index
}
/// Seed `CurrentQIndex` to the §5.9.12 `base_q_idx` (or restore
/// it at a tile boundary). Callers should call this once before
/// the first §5.11.12 [`Self::decode_delta_qindex`] in a tile,
/// per §6.10.4. No-op for tiles that never call
/// `decode_delta_qindex` (`delta_q_present == 0`).
pub fn set_current_q_index(&mut self, q_index: i32) {
self.current_q_index = q_index;
}
/// Current `DeltaLF[ i ]` row (§5.11.2 / §5.11.13) — the four
/// running loop-filter-strength deltas. Every entry is in
/// `-MAX_LOOP_FILTER ..= MAX_LOOP_FILTER` after at least one
/// §5.11.13 update (the spec's `Clip3` bound). Fresh walkers
/// start with all-zero entries, matching the §5.11.2
/// `decode_tile()` line `for ( i = 0; i < FRAME_LF_COUNT; i++ )
/// DeltaLF[ i ] = 0`.
#[must_use]
pub fn current_delta_lf(&self) -> &[i32; FRAME_LF_COUNT] {
&self.current_delta_lf
}
/// §5.11.5 `DeltaLFs[ r + y ][ c + x ][ i ] = DeltaLF[ i ]` grid-fill
/// (av1-spec p.59) — stamp the current §5.11.13
/// [`Self::current_delta_lf`] accumulator over a block's `bh4 * bw4`
/// mi footprint at origin `(mi_row, mi_col)` of size `sub_size`. Call
/// once per decoded block after its §5.11.13 `read_delta_lf` (the
/// accumulator the block carries into the §7.14.4 deblock). Out-of-grid
/// footprint cells (clipped at the frame edge, exactly like the other
/// §5.11.5 grid-fills) are skipped. An out-of-range `sub_size` is a
/// no-op (the caller validated it).
pub fn stamp_delta_lfs(&mut self, mi_row: u32, mi_col: u32, sub_size: usize) {
if sub_size >= BLOCK_SIZES {
return;
}
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
// §5.11.13 clamps every accumulator slot to ±MAX_LOOP_FILTER
// (±63) before it lands here, so the i8 narrowing is lossless.
let acc: [i8; FRAME_LF_COUNT] = core::array::from_fn(|i| self.current_delta_lf[i] as i8);
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let base = ((rr * self.mi_cols + cc) as usize) * FRAME_LF_COUNT;
self.delta_lfs[base..base + FRAME_LF_COUNT].copy_from_slice(&acc);
}
}
}
/// Read `DeltaLFs[ r ][ c ][ idx ]` (§7.14.4). Returns `0` for
/// out-of-grid coordinates or an out-of-range `idx` (the §5.9.18 zero
/// default — an unstamped / off-edge cell contributes no §7.14.4 delta).
#[inline]
#[must_use]
pub fn delta_lf_at(&self, r: i32, c: i32, idx: usize) -> i32 {
if r < 0 || c < 0 || idx >= FRAME_LF_COUNT {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
i32::from(self.delta_lfs[((r * self.mi_cols + c) as usize) * FRAME_LF_COUNT + idx])
}
/// View of the §5.11.5 `DeltaLFs[]` grid after the walk —
/// `delta_lfs()[ (r * MiCols + c) * FRAME_LF_COUNT + i ]`. Values
/// are the §5.11.13 post-Clip3 deltas (`-63..=63`, stored `i8`).
#[must_use]
pub fn delta_lfs(&self) -> &[i8] {
&self.delta_lfs
}
/// Reset the `DeltaLF[ i ]` accumulator to all-zero. Callers
/// should invoke this at tile entry per §5.11.2 (which mandates
/// `DeltaLF[ i ] = 0` for `i = 0..FRAME_LF_COUNT-1` before the
/// per-superblock partition walk). The constructor already
/// zero-initialises the row; this method exists for tile-loop
/// re-entry when one walker decodes multiple tiles.
pub fn reset_current_delta_lf(&mut self) {
self.current_delta_lf = [0; FRAME_LF_COUNT];
}
/// View of the §6.10.40 `cdef_idx[]` grid after the walk. Indexed
/// row-major: `cdef_idx()[ r * MiCols + c ]`. Cells that no
/// §5.11.56 [`Self::decode_cdef`] call has touched carry `-1` per
/// the §5.11.55 `clear_cdef()` initialisation (interpreted as
/// "CDEF disabled for that block").
#[must_use]
pub fn cdef_idx(&self) -> &[i8] {
&self.cdef_idx
}
/// View of the §5.11.27 `motion_modes[]` grid after the walk —
/// `motion_modes()[ r * MiCols + c ]`. Cells no inter leaf covered
/// carry [`MOTION_MODE_SIMPLE`] (`0`). See the field docs.
#[must_use]
pub fn motion_modes(&self) -> &[u8] {
&self.motion_modes
}
/// View of the §5.11.29 `compound_types[]` grid after the walk —
/// `compound_types()[ r * MiCols + c ]`. Cells no inter leaf
/// covered carry [`COMPOUND_AVERAGE`]. See the field docs.
#[must_use]
pub fn compound_types(&self) -> &[u8] {
&self.compound_types
}
/// View of the §5.11.29 compound `wedge_index` grid after the walk —
/// `compound_wedge_indices()[ r * MiCols + c ]`. Read only at a
/// [`COMPOUND_WEDGE`] leaf's origin. See the field docs.
#[must_use]
pub fn compound_wedge_indices(&self) -> &[u8] {
&self.compound_wedge_indices
}
/// View of the §5.11.29 compound `wedge_sign` grid after the walk —
/// `compound_wedge_signs()[ r * MiCols + c ]`. Read only at a
/// [`COMPOUND_WEDGE`] leaf's origin. See the field docs.
#[must_use]
pub fn compound_wedge_signs(&self) -> &[u8] {
&self.compound_wedge_signs
}
/// View of the §5.11.29 compound `mask_type` grid after the walk —
/// `compound_mask_types()[ r * MiCols + c ]`. Read only at a
/// [`COMPOUND_DIFFWTD`] leaf's origin. See the field docs.
#[must_use]
pub fn compound_mask_types(&self) -> &[u8] {
&self.compound_mask_types
}
/// View of the §5.11.28 `interintra_mode` grid after the walk —
/// `interintra_modes()[ r * MiCols + c ]`. Read only at an
/// inter-intra leaf's origin. See the field docs.
#[must_use]
pub fn interintra_modes(&self) -> &[u8] {
&self.interintra_modes
}
/// View of the §5.11.28 `wedge_interintra` grid after the walk —
/// `wedge_interintras()[ r * MiCols + c ]`. Read only at an
/// inter-intra leaf's origin. See the field docs.
#[must_use]
pub fn wedge_interintras(&self) -> &[u8] {
&self.wedge_interintras
}
/// View of the §5.11.28 inter-intra `wedge_index` grid after the
/// walk — `interintra_wedge_indices()[ r * MiCols + c ]`. Read only
/// at an inter-intra leaf's origin whose `wedge_interintras` cell is
/// `1`. See the field docs.
#[must_use]
pub fn interintra_wedge_indices(&self) -> &[u8] {
&self.interintra_wedge_indices
}
/// `clear_cdef( r, c )` per §5.11.55 (av1-spec p.104) — re-stamps
/// the `-1` sentinel at the four (when `use_128x128_superblock`)
/// or one 64×64 anchor cell(s) at the top of each superblock. The
/// §5.11.2 tile walk calls this immediately before
/// [`Self::decode_partition`].
///
/// ```text
/// clear_cdef( r, c ) {
/// cdef_idx[ r ][ c ] = -1
/// if ( use_128x128_superblock ) {
/// cdefSize4 = Num_4x4_Blocks_Wide[ BLOCK_64X64 ]
/// cdef_idx[ r ][ c + cdefSize4 ] = -1
/// cdef_idx[ r + cdefSize4][ c ] = -1
/// cdef_idx[ r + cdefSize4][ c + cdefSize4 ] = -1
/// }
/// }
/// ```
///
/// The fresh-walker constructor already initialises every cell to
/// `-1`; this method exists for tile-loop re-entry / superblock
/// boundaries where the same walker decodes contiguous
/// superblocks. Coordinates outside the frame's `MiRows` /
/// `MiCols` extent are silently skipped (the bottom/right
/// superblock can straddle the frame edge).
pub fn clear_cdef(&mut self, r: u32, c: u32, use_128x128_superblock: bool) {
// §5.11.55 first stamp: cdef_idx[ r ][ c ] = -1.
if r < self.mi_rows && c < self.mi_cols {
self.cdef_idx[(r * self.mi_cols + c) as usize] = -1;
}
if !use_128x128_superblock {
return;
}
// §5.11.55 `cdefSize4 = Num_4x4_Blocks_Wide[ BLOCK_64X64 ]`
// — the 64x64 anchor stride in 4x4-block units (= 16).
let cdef_size4 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as u32;
// §5.11.55 three additional stamps for the 128x128 case.
let stamps: [(u32, u32); 3] = [
(r, c + cdef_size4),
(r + cdef_size4, c),
(r + cdef_size4, c + cdef_size4),
];
for (rr, cc) in stamps {
if rr < self.mi_rows && cc < self.mi_cols {
self.cdef_idx[(rr * self.mi_cols + cc) as usize] = -1;
}
}
}
/// Flat slot for the §6.10.3 `BlockDecoded[ plane ][ y ][ x ]`
/// superblock-local cell. The SB-local indices `(y, x)` range over
/// `-1..=(sbSize4 >> sub)`; the `+1` shift folds the `-1` border
/// into index `0` so every valid `(plane, y, x)` maps inside the
/// fixed `3 * BD_STRIDE * BD_STRIDE` buffer. Returns `None` for an
/// out-of-range index (defensive — the §5.11.3 / §5.11.35 callers
/// stay within `-1..=MAX_SB4`).
#[inline]
fn block_decoded_slot(plane: usize, y: i32, x: i32) -> Option<usize> {
if plane >= 3 {
return None;
}
let yi = y + 1;
let xi = x + 1;
if yi < 0 || xi < 0 {
return None;
}
let (yi, xi) = (yi as usize, xi as usize);
if yi >= BD_STRIDE || xi >= BD_STRIDE {
return None;
}
Some(plane * BD_STRIDE * BD_STRIDE + yi * BD_STRIDE + xi)
}
/// `clear_block_decoded_flags( r, c, sbSize4 )` per §5.11.3
/// (av1-spec p.60) — the per-superblock `BlockDecoded[]` reset the
/// §5.11.2 tile walk runs immediately before
/// [`Self::decode_partition`].
///
/// ```text
/// clear_block_decoded_flags( r, c, sbSize4 ) {
/// for ( plane = 0; plane < NumPlanes; plane++ ) {
/// subX = (plane > 0) ? subsampling_x : 0
/// subY = (plane > 0) ? subsampling_y : 0
/// sbWidth4 = ( MiColEnd - c ) >> subX
/// sbHeight4 = ( MiRowEnd - r ) >> subY
/// for ( y = -1; y <= ( sbSize4 >> subY ); y++ )
/// for ( x = -1; x <= ( sbSize4 >> subX ); x++ ) {
/// if ( y < 0 && x < sbWidth4 )
/// BlockDecoded[ plane ][ y ][ x ] = 1
/// else if ( x < 0 && y < sbHeight4 )
/// BlockDecoded[ plane ][ y ][ x ] = 1
/// else
/// BlockDecoded[ plane ][ y ][ x ] = 0
/// }
/// BlockDecoded[ plane ][ sbSize4 >> subY ][ -1 ] = 0
/// }
/// }
/// ```
///
/// The top border (`y < 0`) is set to `1` for SB-local columns
/// `x < sbWidth4` (the part of the row above the superblock that
/// overlaps already-decoded blocks); the left border (`x < 0`) is
/// set to `1` for rows `y < sbHeight4`. Everything else — the
/// interior and the off-frame border tails — starts at `0`. The
/// final line forces the below-left corner cell back to `0` (the
/// `sbSize4 >> subY` row at `x == -1`); per §6.10.3 the below-left
/// of the bottom edge is never available.
///
/// ## Argument contract
///
/// * `r` / `c` — the superblock's top-left mi position (the §5.11.2
/// tile-walk loop induction variables).
/// * `sb_size4` — `Num_4x4_Blocks_Wide[ sbSize ]` (`16` for the
/// 64×64 superblock, `32` for 128×128). Clamped to `MAX_SB4` so a
/// caller-bug oversize value cannot index past the fixed buffer.
/// * `num_planes` — §5.5.2 `NumPlanes` (`1` for monochrome, `3`
/// otherwise). Clamped to `3`.
/// * `subsampling_x` / `subsampling_y` — §5.5.2 chroma subsampling
/// (`0` / `1`); applied only on the chroma planes (`plane > 0`).
pub fn clear_block_decoded_flags(
&mut self,
r: u32,
c: u32,
sb_size4: u32,
num_planes: u8,
subsampling_x: u8,
subsampling_y: u8,
) {
let sb_size4 = core::cmp::min(sb_size4, MAX_SB4 as u32) as i32;
let num_planes = core::cmp::min(num_planes, 3);
let mi_col_end = self.geometry.mi_col_end as i32;
let mi_row_end = self.geometry.mi_row_end as i32;
for plane in 0..num_planes as usize {
let (sub_x, sub_y) = if plane > 0 {
(subsampling_x as i32, subsampling_y as i32)
} else {
(0, 0)
};
// §5.11.3 `sbWidth4 = ( MiColEnd - c ) >> subX`,
// `sbHeight4 = ( MiRowEnd - r ) >> subY`.
let sb_width4 = (mi_col_end - c as i32) >> sub_x;
let sb_height4 = (mi_row_end - r as i32) >> sub_y;
let y_max = sb_size4 >> sub_y;
let x_max = sb_size4 >> sub_x;
for y in -1..=y_max {
for x in -1..=x_max {
// §5.11.3 lines: the top border (`y < 0`) within the
// in-frame column span and the left border (`x < 0`)
// within the in-frame row span are `1`; everything
// else (interior + off-frame border tails) is `0`.
// The two `1` arms in the spec collapse to a single
// boolean `or` (both yield the same value).
let val = u8::from((y < 0 && x < sb_width4) || (x < 0 && y < sb_height4));
if let Some(slot) = Self::block_decoded_slot(plane, y, x) {
self.block_decoded[slot] = val;
}
}
}
// §5.11.3 final line: `BlockDecoded[ plane ][ sbSize4 >>
// subY ][ -1 ] = 0`.
if let Some(slot) = Self::block_decoded_slot(plane, y_max, -1) {
self.block_decoded[slot] = 0;
}
}
}
/// `BlockDecoded[ plane ][ y ][ x ]` query — returns the §6.10.3
/// boolean for a superblock-local `(y, x)` cell (`y, x ∈
/// -1..=MAX_SB4`). Out-of-range indices return `false` (the
/// §5.11.35 caller derivation never queries outside the SB +
/// one-cell border). Used by the §5.11.35 above-right / below-left
/// availability derivation and by tests.
#[must_use]
pub fn block_decoded(&self, plane: usize, y: i32, x: i32) -> bool {
Self::block_decoded_slot(plane, y, x).is_some_and(|slot| self.block_decoded[slot] != 0)
}
/// `read_cdef()` per §5.11.56 (av1-spec p.104) — the per-leaf
/// CDEF-index read. CDEF (Constrained Directional Enhancement
/// Filter) operates on 64×64 anchor cells; multiple decode-block
/// leaves inside one 64×64 anchor share the same `cdef_idx`
/// value. The first leaf inside an anchor reads the
/// `L(cdef_bits)` literal and stamps it across the anchor cells
/// the leaf's footprint covers; subsequent leaves inside the same
/// anchor observe the stamped value (no symbol read, identity
/// stamp).
///
/// The spec body (av1-spec p.104) reads:
///
/// ```text
/// read_cdef( ) {
/// if ( skip || CodedLossless || !enable_cdef || allow_intrabc) {
/// return
/// }
/// cdefSize4 = Num_4x4_Blocks_Wide[ BLOCK_64X64 ]
/// cdefMask4 = ~(cdefSize4 - 1)
/// r = MiRow & cdefMask4
/// c = MiCol & cdefMask4
/// if ( cdef_idx[ r ][ c ] == -1 ) {
/// cdef_idx[ r ][ c ] L(cdef_bits)
/// w4 = Num_4x4_Blocks_Wide[ MiSize ]
/// h4 = Num_4x4_Blocks_High[ MiSize ]
/// for ( i = r; i < r + h4 ; i += cdefSize4 ) {
/// for ( j = c; j < c + w4 ; j += cdefSize4 ) {
/// cdef_idx[ i ][ j ] = cdef_idx[ r ][ c ]
/// }
/// }
/// }
/// }
/// ```
///
/// The §5.11.56 short-circuit set (any-true ⇒ no symbol read,
/// `cdef_idx` unchanged) maps directly to caller-side arguments:
///
/// * `skip` — the §5.11.11 [`Self::decode_skip`] result for the
/// current leaf (0 or 1).
/// * `coded_lossless` — the §6.8.2 frame-level `CodedLossless`
/// flag (the §5.9.13 `delta_q_*` chain plus `seg_qm_active`
/// logic that the frame parser already derives).
/// * `enable_cdef` — the §5.5.1 sequence-header bit. When
/// `enable_cdef == 0`, §5.9.19 also forces `cdef_bits = 0`, so
/// the short-circuit catches the case before any literal read.
/// * `allow_intrabc` — the §5.9.20 frame-header bit. When
/// intra-block-copy is enabled, CDEF is disabled per §5.9.20.
///
/// When the short-circuit set is all-false, the §5.11.56
/// `cdefMask4 = ~(cdefSize4 - 1)` masks the leaf's `(MiRow,
/// MiCol)` to the 64×64 anchor at `(r, c) = (MiRow &
/// cdefMask4, MiCol & cdefMask4)`. `cdefSize4 = 16` per
/// `Num_4x4_Blocks_Wide[ BLOCK_64X64 ]`, so `cdefMask4` zeroes
/// the low four bits.
///
/// If `cdef_idx[ r ][ c ] == -1` (first leaf in this anchor),
/// `L(cdef_bits)` reads the literal (0..=7, since `cdef_bits` is
/// `f(2)` so `1 << cdef_bits <= 8`), then the per-leaf grid-fill
/// loop stamps the value over the `w4 x h4` footprint, advancing
/// by `cdefSize4` so only the 64×64 anchor cells inside the
/// footprint receive the stamp. Otherwise no read happens (the
/// stamp is implicit — earlier leaves in this anchor already wrote
/// it).
///
/// Returns the `cdef_idx` value stamped at the anchor `(r, c)`
/// (the same value the spec writes into the grid), or
/// [`Error::PartitionWalkOutOfRange`] for caller-bug arguments
/// (`sub_size >= BLOCK_SIZES`, `mi_row >= MiRows`, `mi_col >=
/// MiCols`, `cdef_bits > 3`). [`Error::UnexpectedEnd`] surfaces
/// bitstream errors from the underlying `L(cdef_bits)` read.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
#[allow(clippy::too_many_arguments)]
pub fn decode_cdef(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip: u8,
coded_lossless: bool,
enable_cdef: bool,
allow_intrabc: bool,
cdef_bits: u32,
) -> Result<i8, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.9.19 caps `cdef_bits` at `f(2) ⇒ 0..=3`. A larger value
// is a caller bug.
if cdef_bits > 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.56 short-circuit set: any-true ⇒ no symbol read,
// `cdef_idx` unchanged (the §5.11.55 `-1` sentinel survives).
if skip != 0 || coded_lossless || !enable_cdef || allow_intrabc {
// Return whatever sentinel/value sits at the leaf's anchor
// (typically `-1` from §5.11.55, but could be a prior
// leaf's stamp if the short-circuit fires mid-anchor).
let cdef_size4 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as u32;
let mask: u32 = !(cdef_size4 - 1);
let r = mi_row & mask;
let c = mi_col & mask;
// r/c clipped to the anchor — already in-grid because
// `(mi_row, mi_col)` was.
return Ok(self.cdef_idx[(r * self.mi_cols + c) as usize]);
}
// §5.11.56 anchor derivation: cdefSize4 = 16, cdefMask4
// zeroes the low four bits of the leaf coordinate.
let cdef_size4 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as u32;
let mask: u32 = !(cdef_size4 - 1);
let r = mi_row & mask;
let c = mi_col & mask;
let anchor_value = self.cdef_idx[(r * self.mi_cols + c) as usize];
if anchor_value != -1 {
// Anchor already stamped by an earlier leaf in the same
// 64×64 cell — §5.11.56 outer `if` is false, so no read.
return Ok(anchor_value);
}
// §5.11.56 inner branch: anchor still -1. Read `L(cdef_bits)`.
// `cdef_bits == 0` returns 0 with no bit consumed.
let raw = decoder.read_literal(cdef_bits)?;
debug_assert!(
raw < (1u32 << cdef_bits),
"L(cdef_bits) yields 0..(1<<cdef_bits)"
);
// `cdef_bits <= 3` ⇒ `raw <= 7` ⇒ fits in i8.
let value = raw as i8;
// §5.11.56 grid-fill: stamp `value` over the 64×64 anchor
// cells inside the leaf's `(w4, h4)` footprint, advancing by
// `cdefSize4 = 16` so only one cell per 64×64 anchor receives
// the stamp. Sub-64 blocks visit only their containing
// anchor; super-64 blocks (BLOCK_128X128 with
// `use_128x128_superblock`) cover four anchors.
let w4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let h4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
let mut i = r;
while i < r + h4 {
if i >= self.mi_rows {
break;
}
let mut j = c;
while j < c + w4 {
if j >= self.mi_cols {
break;
}
self.cdef_idx[(i * self.mi_cols + j) as usize] = value;
j += cdef_size4;
}
i += cdef_size4;
}
Ok(value)
}
/// `read_lr_unit(plane, unitRow, unitCol)` per §5.11.58 (av1-spec
/// p.106) — decode one loop-restoration unit's filter selection plus
/// (when the unit is `RESTORE_WIENER` / `RESTORE_SGRPROJ`) its filter
/// coefficients.
///
/// The spec body (av1-spec p.106):
///
/// ```text
/// read_lr_unit(plane, unitRow, unitCol) {
/// if ( FrameRestorationType[plane] == RESTORE_WIENER ) {
/// use_wiener S()
/// restoration_type = use_wiener ? RESTORE_WIENER : RESTORE_NONE
/// } else if ( FrameRestorationType[plane] == RESTORE_SGRPROJ ) {
/// use_sgrproj S()
/// restoration_type = use_sgrproj ? RESTORE_SGRPROJ : RESTORE_NONE
/// } else {
/// restoration_type S()
/// }
/// LrType[plane][unitRow][unitCol] = restoration_type
/// if ( restoration_type == RESTORE_WIENER ) {
/// for ( pass = 0; pass < 2; pass++ ) {
/// firstCoeff = plane ? 1 : 0 // LrWiener[..][..][pass][0]=0 for chroma
/// for ( j = firstCoeff; j < 3; j++ ) {
/// v = decode_signed_subexp_with_ref_bool(
/// Wiener_Taps_Min[j], Wiener_Taps_Max[j] + 1,
/// Wiener_Taps_K[j], RefLrWiener[plane][pass][j])
/// LrWiener[plane][unitRow][unitCol][pass][j] = v
/// RefLrWiener[plane][pass][j] = v
/// }
/// }
/// } else if ( restoration_type == RESTORE_SGRPROJ ) {
/// lr_sgr_set L(SGRPROJ_PARAMS_BITS)
/// for ( i = 0; i < 2; i++ ) {
/// radius = Sgr_Params[lr_sgr_set][i*2]
/// min = Sgrproj_Xqd_Min[i]; max = Sgrproj_Xqd_Max[i]
/// if ( radius ) {
/// v = decode_signed_subexp_with_ref_bool(
/// min, max + 1, SGRPROJ_PRJ_SUBEXP_K, RefSgrXqd[plane][i])
/// } else {
/// v = 0
/// if ( i == 1 ) v = Clip3(min, max,
/// (1 << SGRPROJ_PRJ_BITS) - RefSgrXqd[plane][0])
/// }
/// LrSgrXqd[plane][unitRow][unitCol][i] = v
/// RefSgrXqd[plane][i] = v
/// }
/// }
/// }
/// ```
///
/// `frame_restoration_type` is the §5.9.20 `FrameRestorationType[
/// plane ]` ordinal (`0..=3`). The running `RefLrWiener` /
/// `RefSgrXqd` walker state advances exactly as the spec dictates so
/// successive units in the tile reference the previous unit's taps.
///
/// Returns the decoded [`LrUnit`] (the `restoration_type`, plus the
/// Wiener taps or sgr set / xqd payload). [`Error::PartitionWalkOutOfRange`]
/// for a `plane >= 3` or `frame_restoration_type > 3` caller bug;
/// [`Error::UnexpectedEnd`] for underlying bitstream errors.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
pub fn decode_lr_unit(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
plane: usize,
frame_restoration_type: u8,
) -> Result<LrUnit, crate::Error> {
use crate::loop_restoration::{
SGRPROJ_PARAMS_BITS, SGRPROJ_PRJ_BITS, SGRPROJ_PRJ_SUBEXP_K, SGRPROJ_XQD_MAX,
SGRPROJ_XQD_MIN, SGR_PARAMS, WIENER_COEFFS, WIENER_TAPS_K, WIENER_TAPS_MAX,
WIENER_TAPS_MIN,
};
if plane >= 3 || frame_restoration_type > RESTORE_SWITCHABLE {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.58 filter-selection S(): the read CDF + decode-to-
// restoration_type mapping depends on the plane's
// FrameRestorationType.
let restoration_type = match frame_restoration_type {
RESTORE_WIENER => {
// `use_wiener` — binary S() over TileUseWienerCdf.
let sym = decoder.read_symbol(cdfs.use_wiener_cdf())?;
debug_assert!(sym <= 1, "use_wiener S() yields 0 or 1");
if sym != 0 {
RESTORE_WIENER
} else {
RESTORE_NONE
}
}
RESTORE_SGRPROJ => {
// `use_sgrproj` — binary S() over TileUseSgrprojCdf.
let sym = decoder.read_symbol(cdfs.use_sgrproj_cdf())?;
debug_assert!(sym <= 1, "use_sgrproj S() yields 0 or 1");
if sym != 0 {
RESTORE_SGRPROJ
} else {
RESTORE_NONE
}
}
RESTORE_SWITCHABLE => {
// `restoration_type` — 3-symbol S() over
// TileRestorationTypeCdf (RESTORE_NONE / WIENER / SGRPROJ).
let sym = decoder.read_symbol(cdfs.restoration_type_cdf())?;
debug_assert!(sym <= 2, "restoration_type S() yields 0..=2");
sym as u8
}
// RESTORE_NONE: read_lr never calls read_lr_unit for a
// RESTORE_NONE plane (the §5.11.57 guard skips it), so this
// arm is unreachable in spec-faithful callers; treat it as a
// no-op selection rather than panicking.
_ => RESTORE_NONE,
};
match restoration_type {
RESTORE_WIENER => {
let mut wiener = [[0i32; WIENER_COEFFS]; 2];
let ref_wiener = &mut self.ref_lr_wiener[plane];
for (out_pass, ref_pass) in wiener.iter_mut().zip(ref_wiener.iter_mut()) {
// §5.11.58: chroma planes force LrWiener[..][pass][0]
// = 0 and start the subexp read at j = 1.
let first_coeff = if plane != 0 { 1 } else { 0 };
for j in first_coeff..WIENER_COEFFS {
let min = i64::from(WIENER_TAPS_MIN[j]);
let max = i64::from(WIENER_TAPS_MAX[j]);
let k = WIENER_TAPS_K[j];
let r = i64::from(ref_pass[j]);
let v = decoder.decode_signed_subexp_with_ref_bool(min, max + 1, k, r)?;
let v = v as i32;
out_pass[j] = v;
ref_pass[j] = v;
}
}
Ok(LrUnit {
restoration_type,
wiener,
sgr_set: 0,
sgr_xqd: [0; 2],
})
}
RESTORE_SGRPROJ => {
// `lr_sgr_set` — L(SGRPROJ_PARAMS_BITS).
let sgr_set = decoder.read_literal(SGRPROJ_PARAMS_BITS)? as usize;
debug_assert!(sgr_set < SGR_PARAMS.len(), "lr_sgr_set < 16");
let mut sgr_xqd = [0i32; 2];
for i in 0..2 {
let radius = SGR_PARAMS[sgr_set][i * 2];
let min = i64::from(SGRPROJ_XQD_MIN[i]);
let max = i64::from(SGRPROJ_XQD_MAX[i]);
let v: i32 = if radius != 0 {
let r = i64::from(self.ref_sgr_xqd[plane][i]);
decoder.decode_signed_subexp_with_ref_bool(
min,
max + 1,
SGRPROJ_PRJ_SUBEXP_K,
r,
)? as i32
} else if i == 1 {
// §5.11.58 derived value when radius == 0 on i==1.
let derived = (1i32 << SGRPROJ_PRJ_BITS) - self.ref_sgr_xqd[plane][0];
derived.clamp(min as i32, max as i32)
} else {
0
};
sgr_xqd[i] = v;
self.ref_sgr_xqd[plane][i] = v;
}
Ok(LrUnit {
restoration_type,
wiener: [[0; WIENER_COEFFS]; 2],
sgr_set,
sgr_xqd,
})
}
// RESTORE_NONE: no filter coefficients follow.
_ => Ok(LrUnit {
restoration_type,
wiener: [[0; WIENER_COEFFS]; 2],
sgr_set: 0,
sgr_xqd: [0; 2],
}),
}
}
/// `read_lr(r, c, bSize)` per §5.11.57 (av1-spec p.105) — the
/// per-superblock loop-restoration unit driver. For each plane that
/// uses restoration, computes the unit-grid window the superblock
/// `(r, c)` of size `bSize` covers and calls
/// [`Self::decode_lr_unit`] for every `(unitRow, unitCol)` inside it.
///
/// `params` carries the §5.9.x frame-level geometry the unit-window
/// arithmetic needs (frame/upscaled dimensions, per-plane
/// `LoopRestorationSize`, subsampling, the §5.11.57 `allow_intrabc`
/// short-circuit and the superres parameters). The returned vector
/// lists every decoded unit in §5.11.57 raster order
/// (`(plane, unitRow, unitCol, LrUnit)`), so the caller can route the
/// taps into the §7.17 restoration grid.
///
/// Returns an empty vector when `allow_intrabc` is set (§5.11.57
/// returns immediately) or every plane is `RESTORE_NONE`.
/// [`Error::PartitionWalkOutOfRange`] for a caller-bug `bSize`.
pub fn read_lr(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
r: u32,
c: u32,
b_size: usize,
params: &LrParams,
) -> Result<Vec<DecodedLrUnit>, crate::Error> {
if b_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.57: intra-block-copy frames carry no loop restoration.
if params.allow_intrabc {
return Ok(Vec::new());
}
let w = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let h = NUM_4X4_BLOCKS_HIGH[b_size] as u32;
let mut out: Vec<DecodedLrUnit> = Vec::new();
for plane in 0..params.num_planes {
let frt = params.frame_restoration_type[plane];
if frt == RESTORE_NONE {
continue;
}
let sub_x = if plane == 0 { 0 } else { params.subsampling_x };
let sub_y = if plane == 0 { 0 } else { params.subsampling_y };
let unit_size = params.loop_restoration_size[plane];
if unit_size == 0 {
continue;
}
// §5.11.57 unit-grid extents.
let unit_rows = count_units_in_frame(unit_size, round2(params.frame_height, sub_y));
let unit_cols = count_units_in_frame(unit_size, round2(params.upscaled_width, sub_x));
// Lazily allocate the per-plane §5.11.58 LrType/LrWiener/
// LrSgrSet/LrSgrXqd grid the §7.17 loop-restoration process
// consumes. A re-used walker whose stored extents disagree
// with this frame's is re-allocated fresh.
let needs_alloc = match self.lr_units.get(plane).and_then(Option::as_ref) {
Some(g) => g.unit_rows != unit_rows || g.unit_cols != unit_cols,
None => true,
};
if needs_alloc {
let area = (unit_rows as usize)
.checked_mul(unit_cols as usize)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let mut units: Vec<LrUnit> = Vec::new();
units
.try_reserve_exact(area)
.map_err(|_| crate::Error::PartitionWalkOutOfRange)?;
units.resize(area, LrUnit::NONE);
self.lr_units[plane] = Some(LrUnitGrid {
unit_rows,
unit_cols,
units,
});
}
// r475 — a tile-sized walker addresses the frame's unit
// grid from its origin.
let (r, c) = (r + self.lr_origin.0, c + self.lr_origin.1);
let mi_size_shift_y = (MI_SIZE as u32) >> sub_y;
let unit_row_start = (r * mi_size_shift_y).div_ceil(unit_size);
let unit_row_end =
core::cmp::min(unit_rows, ((r + h) * mi_size_shift_y).div_ceil(unit_size));
let mi_size_shift_x = (MI_SIZE as u32) >> sub_x;
let (numerator, denominator) = if params.use_superres {
(
mi_size_shift_x * params.superres_denom,
unit_size * SUPERRES_NUM,
)
} else {
(mi_size_shift_x, unit_size)
};
let unit_col_start = (c * numerator).div_ceil(denominator);
let unit_col_end =
core::cmp::min(unit_cols, ((c + w) * numerator).div_ceil(denominator));
let mut unit_row = unit_row_start;
while unit_row < unit_row_end {
let mut unit_col = unit_col_start;
while unit_col < unit_col_end {
let unit = self.decode_lr_unit(decoder, cdfs, plane, frt)?;
// §5.11.58: persist the decoded unit into the
// per-plane §7.17 grid at (unitRow, unitCol). The
// grid was allocated above with these exact extents,
// and the §5.11.57 unit-window arithmetic clamps
// unitRow / unitCol below unitRows / unitCols, so the
// index is always in range.
if let Some(grid) = self.lr_units[plane].as_mut() {
let idx = (unit_row as usize)
.wrapping_mul(grid.unit_cols as usize)
.wrapping_add(unit_col as usize);
if let Some(cell) = grid.units.get_mut(idx) {
*cell = unit;
}
}
out.push(DecodedLrUnit {
plane,
unit_row,
unit_col,
unit,
});
unit_col += 1;
}
unit_row += 1;
}
}
Ok(out)
}
/// `(unitRows, unitCols)` extents of the §5.11.58 loop-restoration
/// unit grid stored for `plane`, or `None` if no §5.11.57
/// [`Self::read_lr`] call has yet decoded a unit for that plane
/// (the grid is allocated lazily on first use). `plane >= 3` also
/// returns `None`.
#[must_use]
pub fn lr_unit_grid_dims(&self, plane: usize) -> Option<(u32, u32)> {
self.lr_units
.get(plane)?
.as_ref()
.map(|g| (g.unit_rows, g.unit_cols))
}
/// §7.17 loop-restoration grid lookup — the persisted §5.11.58
/// `LrType` / `LrWiener` / `LrSgrSet` / `LrSgrXqd` payload at
/// `(plane, unitRow, unitCol)`. Returns [`LrUnit::NONE`] (the §7.17
/// identity pass-through) when the plane's grid is unallocated, the
/// coordinates are out of the unit-grid extents, or the cell was
/// never covered by a superblock's §5.11.57 unit-window. `plane >=
/// 3` likewise returns [`LrUnit::NONE`].
///
/// This is the bridge the §7.17 loop-restoration process reads —
/// `rType = LrType[ plane ][ unitRow ][ unitCol ]` and the Wiener /
/// self-guided coefficient processes (§7.17.2 / §7.17.4) consume the
/// matching `LrWiener` / `LrSgrSet` / `LrSgrXqd` from the same cell.
#[must_use]
pub fn lr_unit(&self, plane: usize, unit_row: u32, unit_col: u32) -> LrUnit {
let Some(Some(grid)) = self.lr_units.get(plane) else {
return LrUnit::NONE;
};
if unit_row >= grid.unit_rows || unit_col >= grid.unit_cols {
return LrUnit::NONE;
}
let idx = (unit_row as usize) * (grid.unit_cols as usize) + (unit_col as usize);
grid.units.get(idx).copied().unwrap_or(LrUnit::NONE)
}
/// §7.15 CDEF reconstruction over the persisted §5.11.56 `cdef_idx`
/// grid — av1-spec p.319-324.
///
/// Bridges this walker's decode state — the per-anchor
/// [`Self::cdef_idx`] grid (the §5.11.56 selection, `-1` for "no
/// CDEF") and the [`Self::skips`] grid (the §5.11.11 per-4×4 skip
/// flag) — into the §7.15.1 [`crate::cdef::cdef_frame`] sample-filter
/// driver. The driver copies each 8×8 `CurrFrame[ plane ]` block from
/// `src_planes` into `dst_planes` (the `CdefFrame`), then — for any
/// anchor whose `cdef_idx != -1` with at least one non-skip 4×4 cell
/// — runs the §7.15.2 direction search and the §7.15.3 primary +
/// secondary filter per plane with the `cdef_params` strength
/// schedule selected by the anchor's index.
///
/// The two grids resolve through closures over the walker's
/// `cdef_idx[]` / `skips[]` storage, so an anchor that no §5.11.56
/// call stamped reads back as `-1` (the §5.11.55 `clear_cdef`
/// sentinel) and a cell no §5.11.11 writer touched reads back as
/// `skip = 0` (its initial state). When `cdef_params.cdef_bits == 0`
/// and every strength entry is `0` the filter is a pure copy, which
/// matches the §7.15.1 `idx == -1` early return after the block copy.
///
/// `cdef_params` is the §5.9.19 frame-level
/// [`crate::uncompressed_header_tail::CdefParams`]
/// (`cdef_damping` / `cdef_bits` / the per-index `cdef_y_*` /
/// `cdef_uv_*` strength arrays); `num_planes` / `bit_depth` /
/// `subsampling_x` / `subsampling_y` are the §5.5.2 frame geometry the
/// §7.15.2 / §7.15.3 sample loops index. `src_planes` is the
/// post-deblock `CurrFrame[ plane ]`; `dst_planes` receives the
/// `CdefFrame[ plane ]` the §7.17 bridge then consumes (via
/// [`Self::loop_restore_frame_from_grid`]'s `cdef_planes` argument).
///
/// CDEF runs before §7.16 superres upscaling per the §7.4 decode
/// order, so the buffers here are at the un-upscaled
/// `(MiRows * MI_SIZE) >> subY` × `(MiCols * MI_SIZE) >> subX` extent.
#[allow(clippy::too_many_arguments)]
pub fn cdef_frame_from_idx(
&self,
cdef_params: &crate::uncompressed_header_tail::CdefParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
src_planes: &[crate::loop_filter::PlaneBuffer<'_>],
dst_planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
) {
self.with_cdef_ctx(
cdef_params,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
|ctx| crate::cdef::cdef_frame(ctx, src_planes, dst_planes),
);
}
/// r475 — [`Self::cdef_frame_from_idx`] in place over the
/// deblocked planes ([`crate::cdef::cdef_frame_in_place`]).
pub(crate) fn cdef_frame_in_place_from_idx(
&self,
cdef_params: &crate::uncompressed_header_tail::CdefParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
) {
self.with_cdef_ctx(
cdef_params,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
|ctx| crate::cdef::cdef_frame_in_place(ctx, planes),
);
}
/// r475 — [`Self::cdef_frame_in_place_from_idx`] `threads`-wide:
/// the 64-luma-row bands are filtered in waves of `threads`; a
/// wave first copies every band's source window (the band ± 2
/// unfiltered rows — §7.15.3 `cdef_get_at` reaches at most 2
/// samples, bounded by §5.11.52) and the two rows the next wave's
/// first band will need from this wave's last band, then filters
/// the bands concurrently, each into its own row window. Byte-
/// identical to the serial filter.
#[allow(clippy::too_many_arguments)]
pub(crate) fn cdef_frame_parallel_from_idx(
&self,
cdef_params: &crate::uncompressed_header_tail::CdefParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
threads: usize,
) {
use crate::cdef::{cdef_band_in_place, CdefSrc};
use crate::loop_filter::{row_windows_mut, run_jobs, PlaneBuffer};
const BAND_MI: u32 = 16;
const HALO: usize = 2;
let num_planes = num_planes.min(planes.len() as u8) as usize;
if num_planes == 0 {
return;
}
let dims: Vec<(usize, usize)> = planes
.iter()
.map(|p| (p.rows as usize, p.cols as usize))
.collect();
// Bands: mi rows `[ r0, r1 )`.
let mut bands: Vec<(u32, u32)> = Vec::new();
let mut r = 0u32;
while r < self.mi_rows {
let r1 = (r + BAND_MI).min(self.mi_rows);
bands.push((r, r1));
r = r1;
}
let plane_rows = |plane: usize, r: u32| -> usize {
let sub_y = if plane == 0 {
0
} else {
u32::from(subsampling_y)
};
(((r * MI_SIZE as u32) >> sub_y) as usize).min(dims[plane].0)
};
// Unfiltered rows `[ by0 - 2, by0 )` of the next wave's first band.
let mut carry: Vec<Vec<i32>> = vec![Vec::new(); num_planes];
for wave in bands.chunks(threads.max(1)) {
// Copy phase: source windows per (band, plane).
let mut windows: Vec<Vec<(Vec<i32>, usize)>> = Vec::with_capacity(wave.len());
for (k, &(r0, r1)) in wave.iter().enumerate() {
let mut per_plane = Vec::with_capacity(num_planes);
for (plane, pb) in planes.iter().enumerate().take(num_planes) {
let (rows, cols) = dims[plane];
let (by0, by1) = (plane_rows(plane, r0), plane_rows(plane, r1));
let new0 = by0.saturating_sub(HALO);
let new1 = (by1 + HALO).min(rows);
let mut win: Vec<i32> = Vec::with_capacity((new1 - new0) * cols);
if k == 0 && by0 >= HALO && !carry[plane].is_empty() {
win.extend_from_slice(&carry[plane]);
win.extend_from_slice(&pb.samples[by0 * cols..new1 * cols]);
} else {
win.extend_from_slice(&pb.samples[new0 * cols..new1 * cols]);
}
per_plane.push((win, new0));
}
windows.push(per_plane);
}
let &(_, last_r1) = wave.last().expect("non-empty wave");
for (plane, pb) in planes.iter().enumerate().take(num_planes) {
let cols = dims[plane].1;
let by1 = plane_rows(plane, last_r1);
let c = &mut carry[plane];
c.clear();
if by1 >= HALO {
c.extend_from_slice(&pb.samples[(by1 - HALO) * cols..by1 * cols]);
}
}
// Filter phase: disjoint band windows per plane.
let mut per_plane_windows: Vec<Vec<&mut [i32]>> = Vec::with_capacity(num_planes);
let mut per_plane_row0: Vec<Vec<usize>> = Vec::with_capacity(num_planes);
for (plane, pb) in planes.iter_mut().enumerate().take(num_planes) {
let cols = dims[plane].1;
let ranges: Vec<(usize, usize)> = wave
.iter()
.map(|&(r0, r1)| (plane_rows(plane, r0), plane_rows(plane, r1)))
.collect();
per_plane_row0.push(ranges.iter().map(|&(a, _)| a).collect());
per_plane_windows.push(row_windows_mut(pb.samples, cols, &ranges));
}
// Transpose into per-band plane sets.
let mut band_sets: Vec<Vec<&mut [i32]>> = (0..wave.len()).map(|_| Vec::new()).collect();
for plane_wins in per_plane_windows.into_iter() {
for (k, w) in plane_wins.into_iter().enumerate() {
band_sets[k].push(w);
}
}
let mut jobs: Vec<Box<dyn FnOnce() + Send + '_>> = Vec::with_capacity(wave.len());
for ((k, &(r0, r1)), (set, srcs_owned)) in wave
.iter()
.enumerate()
.zip(band_sets.into_iter().zip(windows.iter()))
{
let dims = &dims;
let per_plane_row0 = &per_plane_row0;
let job = move || {
let mut srcs = [None, None, None];
for (plane, slot) in srcs.iter_mut().enumerate().take(num_planes) {
let (win, row0) = &srcs_owned[plane];
*slot = Some(CdefSrc {
samples: win,
cols: dims[plane].1,
row0: *row0,
plane_rows: dims[plane].0,
plane_cols: dims[plane].1,
});
}
let mut dst_row0 = [0usize; 3];
let mut dsts: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
for (plane, win) in set.into_iter().enumerate() {
let cols = dims[plane].1;
dst_row0[plane] = per_plane_row0[plane][k];
dsts.push(PlaneBuffer {
rows: (win.len() / cols.max(1)) as u32,
cols: cols as u32,
samples: win,
});
}
self.with_cdef_ctx(
cdef_params,
num_planes as u8,
bit_depth,
subsampling_x,
subsampling_y,
|ctx| cdef_band_in_place(ctx, &srcs, &mut dsts, dst_row0, r0, r1),
);
};
jobs.push(Box::new(job));
}
run_jobs(threads, jobs);
}
}
/// The §7.15 frame context over this walker's `cdef_idx[]` /
/// `Skips[]` grids, handed to `f`.
fn with_cdef_ctx(
&self,
cdef_params: &crate::uncompressed_header_tail::CdefParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
f: impl FnOnce(&crate::cdef::CdefFrameContext<'_>),
) {
use crate::cdef::CdefFrameContext;
let ctx = CdefFrameContext {
mi_rows: self.mi_rows,
mi_cols: self.mi_cols,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
cdef_params,
// §7.15.1 reads `cdef_idx[ baseR ][ baseC ]` at the 64×64
// anchor; the persisted grid carries `-1` for any untouched
// anchor, which the driver treats as "copy only".
cdef_idx: &|r, c| {
if r >= self.mi_rows || c >= self.mi_cols {
return -1;
}
self.cdef_idx[(r * self.mi_cols + c) as usize]
},
// §7.15.1 `skip` is the conjunction over the four 4×4 cells
// of the 8×8 block; the driver evaluates each cell, so this
// closure answers the per-cell `Skips[ r ][ c ]`.
skip: &|r, c| self.skip_at(r as i32, c as i32) != 0,
};
f(&ctx);
}
/// r429 — [`Self::cdef_frame_from_idx`] with the §5.11.56
/// `cdef_idx[]` anchors supplied by the caller instead of the
/// walker's own grid: `unit_idx` holds one strength id per 64×64
/// unit in raster order (`ceil(MiCols / 16)` units per row, `-1`
/// = the §5.11.55 sentinel — copy only). The skip grid is still
/// the walker's committed §5.11.11 state. This is the encoder's
/// per-unit CDEF *election* driver: candidate plans filter
/// through the identical §7.15 kernels BEFORE the tile is
/// re-emitted with the §5.11.56 literals that make the walker's
/// own grid carry the same ids.
#[allow(clippy::too_many_arguments)]
pub fn cdef_frame_with_unit_grid(
&self,
cdef_params: &crate::uncompressed_header_tail::CdefParams,
unit_idx: &[i8],
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
src_planes: &[crate::loop_filter::PlaneBuffer<'_>],
dst_planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
) {
use crate::cdef::{cdef_frame, CdefFrameContext};
let sb_cols = self.mi_cols.div_ceil(16);
let ctx = CdefFrameContext {
mi_rows: self.mi_rows,
mi_cols: self.mi_cols,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
cdef_params,
cdef_idx: &|r, c| {
if r >= self.mi_rows || c >= self.mi_cols {
return -1;
}
let unit = (r / 16) * sb_cols + (c / 16);
unit_idx.get(unit as usize).copied().unwrap_or(-1)
},
skip: &|r, c| self.skip_at(r as i32, c as i32) != 0,
};
cdef_frame(&ctx, src_planes, dst_planes);
}
/// §7.14 in-loop deblocking filter over the persisted §5.11 decode
/// grids — av1-spec p.307-313.
///
/// Bridges the per-mi grids this walker accumulates during the
/// §5.11.5 `decode_block` walk — `Skips[]`, `RefFrames[][][0]`,
/// `YModes[]`, `SegmentIds[]`, the §5.11.16/§5.11.17 luma
/// `TxSizes[]` / `InterTxSizes[]`, and `MiSizes[]` — into the §7.14
/// [`crate::loop_filter::loop_filter_frame`] sample-edge driver.
/// The driver visits every 4×4 luma (or subsampled chroma) edge in
/// the §7.14.1 `plane × pass × row × col` order, derives the
/// §7.14.4/§7.14.5 adaptive strength from these grids, and filters
/// the samples in place. Deblocking is the FIRST in-loop pass per
/// the §7.4 decode order, so `planes` here is the raw
/// post-reconstruction `CurrFrame[ plane ]`, and its filtered output
/// becomes the `src_planes` argument of
/// [`Self::cdef_frame_from_idx`].
///
/// The §7.14.2 `LoopfilterTxSizes[ plane ][ row >> subY ][ col >>
/// subX ]` lookup is reconstructed on the fly: for luma the per-mi
/// `InterTxSizes[]` cell (inter blocks) or `TxSizes[]` cell (intra
/// blocks) is read directly; for chroma the §5.11.37 `get_tx_size(
/// plane, lumaTxSz )` derivation is applied to the co-located luma
/// mi's `MiSize` + luma transform size. This matches the §5.11.35
/// `transform_block` stamp `LoopfilterTxSizes[ plane ][ (row >>
/// subY) + i ][ (col >> subX) + j ] = txSz` without the walker
/// owning a separate per-plane `LoopfilterTxSizes` mirror.
///
/// `lf_params` is the §5.9.11 frame-level
/// [`crate::uncompressed_header_tail::LoopFilterParams`]
/// (`loop_filter_level[]` / `loop_filter_sharpness` /
/// `loop_filter_delta_enabled` / the ref/mode delta arrays);
/// `seg_params` is the §5.9.14
/// [`crate::uncompressed_header_tail::SegmentationParams`] consulted
/// for the §7.14.5 `SEG_LVL_ALT_LF_*` per-segment offsets. The
/// remaining arguments are the §5.5.2 / §5.9.5 frame geometry the
/// driver indexes.
///
/// ## Per-mi `DeltaLFs` scope
///
/// The §7.14.4 `DeltaLFs[ row ][ col ][ idx ]` term is read from the
/// walker's persisted [`Self::delta_lfs`] grid — the per-mi snapshot
/// every decoded block's §5.11.5 grid-fill stamps from the §5.11.13
/// [`Self::current_delta_lf`] accumulator (via [`Self::stamp_delta_lfs`]).
/// When `delta_lf_present == 0` (the common case) the grid is all-zero
/// (the §5.9.18 default), so the §7.14.4 term contributes nothing; when
/// `delta_lf_present == 1` the per-mi deltas drive the §7.14.5 strength
/// selection. `delta_lf_multi` selects the §7.14.4 slot indexing (single
/// `DeltaLFs[..][0]` vs the per-`(plane, pass)` multi-slot read).
#[allow(clippy::too_many_arguments)]
pub fn loop_filter_frame_from_grid(
&self,
lf_params: &crate::uncompressed_header_tail::LoopFilterParams,
seg_params: &crate::uncompressed_header_tail::SegmentationParams,
delta_lf_multi: bool,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
frame_width: u32,
frame_height: u32,
planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
) {
self.loop_filter_rows_from_grid(
lf_params,
seg_params,
delta_lf_multi,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
frame_width,
frame_height,
planes,
0,
self.mi_rows,
);
}
/// r464 — [`Self::loop_filter_frame_from_grid`] over the edge rows
/// `mi_row_start..mi_row_end` only (see
/// [`crate::loop_filter::loop_filter_rows`]).
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn loop_filter_rows_from_grid(
&self,
lf_params: &crate::uncompressed_header_tail::LoopFilterParams,
seg_params: &crate::uncompressed_header_tail::SegmentationParams,
delta_lf_multi: bool,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
frame_width: u32,
frame_height: u32,
planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
mi_row_start: u32,
mi_row_end: u32,
) {
self.with_lf_ctx(
lf_params,
seg_params,
delta_lf_multi,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
frame_width,
frame_height,
|ctx| crate::loop_filter::loop_filter_rows(ctx, planes, mi_row_start, mi_row_end),
);
}
/// r475 — [`Self::loop_filter_frame_from_grid`] `threads`-wide by
/// 64-luma-row bands ([`crate::loop_filter::loop_filter_rows_window`]
/// states the independence the split relies on): per plane, pass
/// 0 over every band at once, then pass 1 over the even bands
/// (each on a window with 7-row skirts) and then the odd ones, so
/// the windows of concurrently filtered bands never overlap. Byte-
/// identical to the serial filter.
#[allow(clippy::too_many_arguments)]
pub(crate) fn loop_filter_frame_parallel_from_grid(
&self,
lf_params: &crate::uncompressed_header_tail::LoopFilterParams,
seg_params: &crate::uncompressed_header_tail::SegmentationParams,
delta_lf_multi: bool,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
frame_width: u32,
frame_height: u32,
planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
threads: usize,
) {
use crate::loop_filter::{loop_filter_rows_window, row_windows_mut, run_jobs, PlaneBuffer};
const BAND_MI: u32 = 16;
const SKIRT: usize = 7;
let num_planes = num_planes.min(planes.len() as u8);
for plane in 0..num_planes {
if plane > 0 && lf_params.loop_filter_level[(1 + plane) as usize] == 0 {
continue;
}
let sub_y = if plane == 0 {
0
} else {
u32::from(subsampling_y)
};
let pb = &mut planes[plane as usize];
let (rows, cols) = (pb.rows as usize, pb.cols as usize);
// Band `k`: mi rows `[16k, 16k + 16)`, plane rows `[by0, by1)`.
let mut bands: Vec<(u32, u32, usize, usize)> = Vec::new();
let mut r = 0u32;
while r < self.mi_rows {
let r1 = (r + BAND_MI).min(self.mi_rows);
let by0 = (((r * MI_SIZE as u32) >> sub_y) as usize).min(rows);
let by1 = (((r1 * MI_SIZE as u32) >> sub_y) as usize).min(rows);
if by1 > by0 {
bands.push((r, r1, by0, by1));
}
r = r1;
}
let run_phase = |pb: &mut PlaneBuffer<'_>, pass: u8, parity: Option<usize>| {
let selected: Vec<(u32, u32, usize, usize)> = bands
.iter()
.enumerate()
.filter(|(k, _)| match parity {
None => true,
Some(p) => k % 2 == p,
})
.map(|(_, b)| *b)
.collect();
let ranges: Vec<(usize, usize)> = selected
.iter()
.map(|&(_, _, by0, by1)| {
if pass == 0 {
(by0, by1)
} else {
(by0.saturating_sub(SKIRT), (by1 + SKIRT).min(rows))
}
})
.collect();
let windows = row_windows_mut(pb.samples, cols, &ranges);
let mut jobs: Vec<Box<dyn FnOnce() + Send + '_>> =
Vec::with_capacity(windows.len());
for (win, (&(mi0, mi1, _, _), &(w0, w1))) in
windows.into_iter().zip(selected.iter().zip(ranges.iter()))
{
let f = move || {
self.with_lf_ctx(
lf_params,
seg_params,
delta_lf_multi,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
frame_width,
frame_height,
|ctx| {
let mut window = PlaneBuffer {
rows: (w1 - w0) as u32,
cols: cols as u32,
samples: win,
};
loop_filter_rows_window(
ctx,
plane,
pass,
&mut window,
w0 as u32,
mi0,
mi1,
);
},
);
};
jobs.push(Box::new(f));
}
run_jobs(threads, jobs);
};
run_phase(pb, 0, None);
run_phase(pb, 1, Some(0));
run_phase(pb, 1, Some(1));
}
}
/// The §7.14 frame context over this walker's grids, handed to `f`.
#[allow(clippy::too_many_arguments)]
fn with_lf_ctx(
&self,
lf_params: &crate::uncompressed_header_tail::LoopFilterParams,
seg_params: &crate::uncompressed_header_tail::SegmentationParams,
delta_lf_multi: bool,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
frame_width: u32,
frame_height: u32,
f: impl FnOnce(&crate::loop_filter::LoopFilterFrameContext<'_>),
) {
use crate::loop_filter::LoopFilterFrameContext;
let ctx = LoopFilterFrameContext {
loop_filter_level: lf_params.loop_filter_level,
loop_filter_sharpness: lf_params.loop_filter_sharpness,
loop_filter_delta_enabled: lf_params.loop_filter_delta_enabled,
loop_filter_ref_deltas: lf_params.loop_filter_ref_deltas,
loop_filter_mode_deltas: lf_params.loop_filter_mode_deltas,
// §7.14.4 `delta_lf_multi` selects single- vs multi-slot
// `DeltaLFs[..][idx]` indexing; the per-mi snapshot is read
// from the walker's `delta_lfs` grid via the `delta_lf` closure.
delta_lf_multi,
mi_rows: self.mi_rows,
mi_cols: self.mi_cols,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
frame_width,
frame_height,
// §7.14.2 line 17073: `RefFrames[ row ][ col ][ 0 ] <=
// INTRA_FRAME`. The persisted grid carries `INTRA_FRAME = 0`
// for intra cells, so the `<= 0` compare folds to `== 0`.
is_intra: &|r, c| {
self.ref_frame_at(r as i32, c as i32, 0)
<= crate::uncompressed_header_tail::INTRA_FRAME as i8
},
// §7.14.2 line 17071: `Skips[ row ][ col ]`.
skip: &|r, c| self.skip_at(r as i32, c as i32) != 0,
// §7.14.4 line 17181: `RefFrames[ row ][ col ][ 0 ]`. The
// grid stores `i8` (`NONE = -1` only ever lands in slot 1),
// so slot 0 is always in `0..=ALTREF_FRAME`.
ref_frame: &|r, c| self.ref_frame_at(r as i32, c as i32, 0).max(0) as u8,
// §7.14.4 line 17183: `YModes[ row ][ col ]`.
mode: &|r, c| self.y_mode_at(r as i32, c as i32),
// §7.14.4 line 17179: `SegmentIds[ row ][ col ]` (`-1`
// signalled as `u8::MAX` for cells with no segment id).
segment_id: &|r, c| {
let s = self.segment_id_at(r as i32, c as i32);
if s < 0 {
u8::MAX
} else {
s as u8
}
},
// §7.14.4 `DeltaLFs[ row ][ col ][ idx ]` — the per-mi snapshot
// the §5.11.5 grid-fill stamped from the §5.11.13 accumulator.
// The value is clamped to `-MAX_LOOP_FILTER ..= MAX_LOOP_FILTER`
// (= -63..=63) by the §5.11.13 read, so the `i32 → i8` cast is
// lossless.
delta_lf: &|r, c, idx| self.delta_lf_at(r as i32, c as i32, idx) as i8,
// §7.14.5 line 17259: `seg_feature_active_idx( segment,
// feature )` with `feature ∈ {SEG_LVL_ALT_LF_Y_V ..= +V}`.
seg_feature_active: &|segment, feature| {
if segment as usize >= crate::uncompressed_header_tail::MAX_SEGMENTS
|| feature >= crate::uncompressed_header_tail::SEG_LVL_MAX
{
return false;
}
seg_params.segment_feature_active[segment as usize][feature]
},
// §7.14.5 line 17266: `FeatureData[ segment ][ feature ]`.
seg_feature_data: &|segment, feature| {
if segment as usize >= crate::uncompressed_header_tail::MAX_SEGMENTS
|| feature >= crate::uncompressed_header_tail::SEG_LVL_MAX
{
return 0;
}
seg_params.segment_feature_data[segment as usize][feature]
},
// §7.14.2 line 17067: `LoopfilterTxSizes[ plane ][ row >>
// subY ][ col >> subX ]`. Reconstructed from the per-mi
// luma transform grids + the §5.11.37 chroma derivation.
lf_tx_size: &|plane, sub_row, sub_col| {
self.loopfilter_tx_size_at(plane, sub_row, sub_col, subsampling_x, subsampling_y)
},
// §7.14.2 line 17065: `MiSizes[ row ][ col ]`.
mi_size: &|r, c| self.mi_size_at(r as i32, c as i32),
};
f(&ctx);
}
/// Reconstruct `LoopfilterTxSizes[ plane ][ subRow ][ subCol ]` from
/// the walker's per-mi luma transform grids — av1-spec §5.11.35 /
/// §5.11.37.
///
/// `subRow` / `subCol` are already in the per-plane subsampled 4×4
/// grid the §7.14.2 caller passes (`row >> subY` / `col >> subX`).
/// For luma the co-located mi is `(subRow, subCol)` directly; for
/// chroma it is `(subRow << subY, subCol << subX)` — the luma mi
/// whose §5.11.35 transform_block stamped this chroma cell.
///
/// The §5.11.35 stamp wrote `get_tx_size( plane, txSz )` for the
/// block's luma transform `txSz`; we recover that luma `txSz` from
/// `InterTxSizes[]` (inter cells) or `TxSizes[]` (intra cells) and
/// re-apply the §5.11.37 `get_tx_size` chroma mapping. Out-of-grid
/// or undecoded cells fall back to [`TX_4X4`] (the constructor
/// pre-fill).
fn loopfilter_tx_size_at(
&self,
plane: usize,
sub_row: u32,
sub_col: u32,
subsampling_x: u8,
subsampling_y: u8,
) -> usize {
let (luma_r, luma_c) = if plane == 0 {
(sub_row as i32, sub_col as i32)
} else {
(
(sub_row << subsampling_y) as i32,
(sub_col << subsampling_x) as i32,
)
};
// §5.11.16/§5.11.17: inter blocks carry their per-cell tx in
// InterTxSizes[]; intra blocks in TxSizes[]. The §5.11.35
// transform_block writes LoopfilterTxSizes from the same per-mi
// luma txSz the §5.11.37 chroma mapping consumes.
let luma_tx = if self.is_inter_at(luma_r, luma_c) != 0 {
self.inter_tx_size_at(luma_r, luma_c) as usize
} else {
self.tx_size_at(luma_r, luma_c) as usize
};
if plane == 0 {
return luma_tx;
}
// §5.11.37 get_tx_size( plane, lumaTx ) chroma mapping keyed by
// the co-located luma mi's MiSize.
let mi_size = self.mi_size_at(luma_r, luma_c);
if mi_size >= BLOCK_SIZES {
return TX_4X4;
}
get_tx_size(plane as u8, luma_tx, mi_size, subsampling_x, subsampling_y).unwrap_or(TX_4X4)
}
/// §7.17 loop-restoration reconstruction over the persisted §5.11.58
/// unit grid — av1-spec p.327-335.
///
/// Bridges the §5.11.57/§5.11.58 [`Self::read_lr`] decode result
/// (the per-plane `LrType` / `LrWiener` / `LrSgrSet` / `LrSgrXqd`
/// grid this walker stores) into the §7.17
/// [`crate::loop_restoration::loop_restoration_frame`] sample-filter
/// driver. The §7.17 prelude copies `cdef_planes` (the
/// `UpscaledCdefFrame`) into `lr_planes` (the `LrFrame`), then — when
/// `UsesLr` — walks the frame in `MI_SIZE` steps and applies the
/// Wiener (§7.17.4) / self-guided (§7.17.2/§7.17.3) arm selected by
/// each unit's `rType = LrType[ plane ][ unitRow ][ unitCol ]`.
///
/// The four per-unit closures the driver reads resolve through
/// [`Self::lr_unit`], so any cell the §5.11.57 unit-window never
/// covered (or any plane whose grid was never allocated — e.g. the
/// §5.11.57 `allow_intrabc` short-circuit) reads back as
/// [`LrUnit::NONE`], which §7.17 treats as a `RESTORE_NONE`
/// identity pass-through (the `cdef_planes` copy survives).
///
/// `lr_params` is the §5.9.20 frame-level
/// [`crate::uncompressed_header_tail::LrParams`] (`UsesLr`,
/// `FrameRestorationType[ plane ]`, `LoopRestorationSize[ plane ]`);
/// the remaining arguments are the §5.5.2 / §5.9.5 / §5.9.8 frame
/// geometry the driver indexes. `curr_planes` is the pre-CDEF
/// `UpscaledCurrFrame`, consulted by §7.17.6 for the cropped
/// above/below-stripe neighbour lines.
#[allow(clippy::too_many_arguments)]
pub fn loop_restore_frame_from_grid(
&self,
lr_params: &crate::uncompressed_header_tail::LrParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
mi_rows: u32,
mi_cols: u32,
frame_height: u32,
upscaled_width: u32,
curr_planes: &[crate::loop_filter::PlaneBuffer<'_>],
cdef_planes: &[crate::loop_filter::PlaneBuffer<'_>],
lr_planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
) {
self.with_lr_ctx(
lr_params,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
mi_rows,
mi_cols,
frame_height,
upscaled_width,
|ctx| {
crate::loop_restoration::loop_restoration_frame(
ctx,
curr_planes,
cdef_planes,
lr_planes,
)
},
);
}
/// r475 — [`Self::loop_restore_frame_from_grid`] in place over the
/// CDEF output with the captured pre-CDEF stripe-boundary rows
/// ([`crate::loop_restoration::loop_restoration_frame_in_place`]).
#[allow(clippy::too_many_arguments)]
pub(crate) fn loop_restore_frame_in_place_from_grid(
&self,
lr_params: &crate::uncompressed_header_tail::LrParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
mi_rows: u32,
mi_cols: u32,
frame_height: u32,
upscaled_width: u32,
planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
boundary: &[crate::loop_restoration::StripeBoundaryRows],
) {
self.with_lr_ctx(
lr_params,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
mi_rows,
mi_cols,
frame_height,
upscaled_width,
|ctx| crate::loop_restoration::loop_restoration_frame_in_place(ctx, planes, boundary),
);
}
/// r475 — [`Self::loop_restore_frame_in_place_from_grid`]
/// `threads`-wide: §7.17.6 reads CDEF samples only inside the
/// unit's own stripe and pre-CDEF samples only at the stripe's
/// skirts (the captured `boundary` rows), and a unit writes only
/// its own rectangle — so the stripes of a plane restore
/// concurrently, each from its own copy into its own row window.
/// Byte-identical to the serial filter.
#[allow(clippy::too_many_arguments)]
pub(crate) fn loop_restore_frame_parallel_from_grid(
&self,
lr_params: &crate::uncompressed_header_tail::LrParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
mi_rows: u32,
mi_cols: u32,
frame_height: u32,
upscaled_width: u32,
planes: &mut [crate::loop_filter::PlaneBuffer<'_>],
boundary: &[crate::loop_restoration::StripeBoundaryRows],
threads: usize,
) {
use crate::loop_filter::{row_windows_mut, run_jobs, PlaneBuffer};
use crate::loop_restoration::{plane_stripes, restore_stripe_in_place, PlaneStripe};
if !lr_params.uses_lr {
return;
}
let num_planes = num_planes.min(planes.len() as u8);
for plane in 0..num_planes {
let Some(bnd) = boundary.get(plane as usize) else {
continue;
};
let mut stripes: Vec<PlaneStripe> = Vec::new();
self.with_lr_ctx(
lr_params,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
mi_rows,
mi_cols,
frame_height,
upscaled_width,
|ctx| stripes = plane_stripes(ctx, plane),
);
if stripes.is_empty() {
continue;
}
let pb = &mut planes[plane as usize];
let cols = pb.cols as usize;
let ranges: Vec<(usize, usize)> = stripes.iter().map(|s| (s.y0, s.y1)).collect();
let windows = row_windows_mut(pb.samples, cols, &ranges);
let mut jobs: Vec<Box<dyn FnOnce() + Send + '_>> = Vec::with_capacity(windows.len());
for (win, stripe) in windows.into_iter().zip(stripes.iter()) {
let job = move || {
let mut out = PlaneBuffer {
rows: (stripe.y1 - stripe.y0) as u32,
cols: cols as u32,
samples: win,
};
let mut copy = Vec::new();
self.with_lr_ctx(
lr_params,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
mi_rows,
mi_cols,
frame_height,
upscaled_width,
|ctx| {
restore_stripe_in_place(
ctx, plane, stripe, bnd, &mut out, stripe.y0, &mut copy,
)
},
);
};
jobs.push(Box::new(job));
}
run_jobs(threads, jobs);
}
}
/// The §7.17 frame context over this walker's §5.11.58 unit grid,
/// handed to `f`.
#[allow(clippy::too_many_arguments)]
fn with_lr_ctx(
&self,
lr_params: &crate::uncompressed_header_tail::LrParams,
num_planes: u8,
bit_depth: u8,
subsampling_x: u8,
subsampling_y: u8,
mi_rows: u32,
mi_cols: u32,
frame_height: u32,
upscaled_width: u32,
f: impl FnOnce(&crate::loop_restoration::LoopRestorationFrameContext<'_>),
) {
use crate::loop_restoration::LoopRestorationFrameContext;
let ctx = LoopRestorationFrameContext {
mi_rows,
mi_cols,
num_planes,
bit_depth,
subsampling_x,
subsampling_y,
frame_height,
upscaled_width,
lr_params,
// §7.17.1 `rType = LrType[ plane ][ unitRow ][ unitCol ]`.
// The persisted §5.11.58 `restoration_type` carries the same
// 0=NONE/1=WIENER/2=SGRPROJ ordinal as the
// FrameRestorationType discriminants, so the dispatch maps
// straight through; any other value (only the unreachable
// RESTORE_SWITCHABLE=3) folds to the None identity arm.
lr_type: &|plane, unit_row, unit_col| match self
.lr_unit(plane as usize, unit_row, unit_col)
.restoration_type
{
RESTORE_WIENER => crate::uncompressed_header_tail::FrameRestorationType::Wiener,
RESTORE_SGRPROJ => crate::uncompressed_header_tail::FrameRestorationType::SgrProj,
_ => crate::uncompressed_header_tail::FrameRestorationType::None,
},
// §7.17.4 `LrWiener[ plane ][ unitRow ][ unitCol ][ pass ][ i ]`.
lr_wiener: &|plane, unit_row, unit_col, pass, i| {
self.lr_unit(plane as usize, unit_row, unit_col).wiener[pass as usize][i]
},
// §7.17.2 `LrSgrSet[ plane ][ unitRow ][ unitCol ]`.
lr_sgr_set: &|plane, unit_row, unit_col| {
self.lr_unit(plane as usize, unit_row, unit_col).sgr_set as u8
},
// §7.17.2 `LrSgrXqd[ plane ][ unitRow ][ unitCol ][ i ]`.
lr_sgr_xqd: &|plane, unit_row, unit_col, i| {
self.lr_unit(plane as usize, unit_row, unit_col).sgr_xqd[i]
},
};
f(&ctx);
}
/// Helper to read `SkipModes[ r ][ c ]`. Returns `0` for
/// out-of-grid coordinates (the §8.3.2 skip-mode ctx walk already
/// gates on `AvailU` / `AvailL`, so an unavailable neighbour's
/// contribution is zero either way).
#[inline]
fn skip_mode_at(&self, r: i32, c: i32) -> u8 {
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
self.skip_modes[(r * self.mi_cols + c) as usize]
}
/// View of the §6.10.4 `IsInters[]` grid after the walk. Indexed
/// row-major: `is_inters()[ r * MiCols + c ]`. Cells that no leaf
/// covered carry `0` (the §5.11.20 [`Self::decode_is_inter`]
/// writer only fills the `bh4 * bw4` footprint of decoded blocks).
#[must_use]
pub fn is_inters(&self) -> &[u8] {
&self.is_inters
}
/// Helper to read `IsInters[ r ][ c ]`. Returns `0` for out-of-grid
/// coordinates; callers must therefore use [`TileGeometry::is_inside`]
/// to derive `AvailU` / `AvailL` before consulting the grid for the
/// §8.3.2 `is_inter` ctx walk (an unavailable neighbour is treated
/// as `LeftIntra = AboveIntra = true` per §5.11.18:
/// `LeftRefFrame[0] = AvailL ? RefFrames[..][0] : INTRA_FRAME`).
#[inline]
fn is_inter_at(&self, r: i32, c: i32) -> u8 {
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
self.is_inters[(r * self.mi_cols + c) as usize]
}
/// View of the §6.10.4 `YModes[]` grid after the walk. Indexed
/// row-major: `y_modes()[ r * MiCols + c ]`. Cells that no leaf
/// covered carry `0` (= `DC_PRED`, the §3 intra-mode enumeration
/// ordinal-zero value); cells in the `bh4 * bw4` footprint of a
/// decoded block whose §5.11.7 / §5.11.22
/// [`Self::decode_intra_frame_y_mode`] call has fired carry the
/// block's `YMode` (in `0..INTRA_MODES`). The §5.11.5
/// `YModes[ r + y ][ c + x ] = YMode` writer fills only the
/// in-grid portion of a block straddling the bottom or right
/// edge.
#[must_use]
pub fn y_modes(&self) -> &[u8] {
&self.y_modes
}
/// View of the §5.11.22 `UVModes[]` grid after the walk. Indexed
/// row-major: `uv_modes()[ r * MiCols + c ]`. Cells that no chroma
/// read covered carry `0` (= `DC_PRED`); cells in a decoded
/// has-chroma block's `bh4 * bw4` footprint carry the block's
/// `UVMode` (in `0..UV_INTRA_MODES_CFL_ALLOWED`, including
/// `UV_CFL_PRED = 13`). Consulted by the §7.11.2.8 chroma
/// `get_filter_type` neighbour smooth-mode check.
#[must_use]
pub fn uv_modes(&self) -> &[u8] {
&self.uv_modes
}
/// Helper to read `YModes[ r ][ c ]` for the §8.3.2
/// `intra_frame_y_mode` neighbour-lookup. Returns `0`
/// (= `DC_PRED`) for out-of-grid coordinates, matching the
/// `AvailU ? YModes[ MiRow - 1 ][ MiCol ] : DC_PRED` /
/// `AvailL ? YModes[ MiRow ][ MiCol - 1 ] : DC_PRED` gating in
/// the spec's ctx derivation. Returns the stored value for
/// in-grid coordinates; if no leaf has touched the cell yet, the
/// stored value is also `0` (the constructor's pre-fill), which
/// is the same `DC_PRED` value the spec's "neighbour unavailable"
/// arm produces. Callers must therefore use
/// [`TileGeometry::is_inside`] to derive `AvailU` / `AvailL`
/// before consulting the grid — an unavailable-but-in-tile
/// neighbour is still treated as `DC_PRED` per the spec, and
/// this helper's `0` return for an out-of-grid coordinate
/// coincides with that.
#[inline]
fn y_mode_at(&self, r: i32, c: i32) -> u8 {
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
self.y_modes[(r * self.mi_cols + c) as usize]
}
/// Helper to read `UVModes[ r ][ c ]` for the §7.11.2.8 chroma
/// `is_smooth` neighbour-lookup. Returns `0` (= `DC_PRED`) for
/// out-of-grid coordinates and for cells no §5.11.22 chroma read
/// has stamped (the constructor pre-fill), matching the spec's
/// unavailable-neighbour / non-smooth fallback.
#[inline]
fn uv_mode_at(&self, r: i32, c: i32) -> u8 {
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
self.uv_modes[(r * self.mi_cols + c) as usize]
}
/// `get_filter_type( plane )` per §7.11.2.8 (av1-spec p.252) — the
/// `filterType` input to the §7.11.2.9 intra edge filter strength
/// selection and the §7.11.2.10 intra edge upsample selection.
/// Returns `1` if the block immediately above OR immediately to the
/// left uses a smooth intra mode (`SMOOTH_PRED` / `SMOOTH_V_PRED` /
/// `SMOOTH_H_PRED`), else `0`.
///
/// `mi_row` / `mi_col` are the current block's top-left mi-unit
/// coordinates; `avail_u` / `avail_l` are the §5.11.5 `AvailU` /
/// `AvailL` (for `plane == 0`) or `AvailUChroma` / `AvailLChroma`
/// (for `plane > 0`) availability flags the caller already derived.
/// `subsampling_x` / `subsampling_y` (0 / 1) drive the §7.11.2.8
/// chroma neighbour-coordinate adjustment.
///
/// The luma branch (`plane == 0`) reads the §6.10.4 `YModes[]` grid
/// at `(MiRow - 1, MiCol)` / `(MiRow, MiCol - 1)`. The chroma branch
/// (`plane > 0`) reads `UVModes[]` at the §7.11.2.8 sub-sampled
/// neighbour coordinates: `c++` if `subsampling_x && !(MiCol & 1)` /
/// `r--` if `subsampling_y && (MiRow & 1)` for the above neighbour,
/// and `c--` if `subsampling_x && (MiCol & 1)` / `r++` if
/// `subsampling_y && !(MiRow & 1)` for the left neighbour. `is_smooth`
/// also gates on `RefFrames[row][col][0] > INTRA_FRAME` returning
/// `0` (an inter neighbour is never smooth) via
/// [`Self::ref_frame_at`].
#[allow(clippy::too_many_arguments)]
fn get_filter_type(
&self,
plane: u8,
mi_row: u32,
mi_col: u32,
avail_u: bool,
avail_l: bool,
subsampling_x: u8,
subsampling_y: u8,
) -> u8 {
let is_smooth = |r: i32, c: i32| -> bool {
// §7.11.2.8 `is_smooth`: an inter neighbour
// (`RefFrame[0] > INTRA_FRAME`) is never smooth.
if self.ref_frame_at(r, c, 0) > 0 {
return false;
}
let mode = if plane == 0 {
self.y_mode_at(r, c) as usize
} else {
self.uv_mode_at(r, c) as usize
};
mode == SMOOTH_PRED || mode == SMOOTH_V_PRED || mode == SMOOTH_H_PRED
};
let sx = subsampling_x != 0;
let sy = subsampling_y != 0;
// §7.11.2.8 above-neighbour coordinates.
let mut ar = mi_row as i32 - 1;
let mut ac = mi_col as i32;
if plane > 0 {
if sx && (mi_col & 1) == 0 {
ac += 1;
}
if sy && (mi_row & 1) == 1 {
ar -= 1;
}
}
let above_smooth = avail_u && is_smooth(ar, ac);
// §7.11.2.8 left-neighbour coordinates.
let mut lr = mi_row as i32;
let mut lc = mi_col as i32 - 1;
if plane > 0 {
if sx && (mi_col & 1) == 1 {
lc -= 1;
}
if sy && (mi_row & 1) == 0 {
lr += 1;
}
}
let left_smooth = avail_l && is_smooth(lr, lc);
u8::from(above_smooth || left_smooth)
}
/// View of the §5.11.5 / §5.11.16 `TxSizes[]` grid after the walk.
/// Indexed row-major: `tx_sizes()[ r * MiCols + c ]`. Cells that
/// no §5.11.16 [`Self::read_block_tx_size`] call has stamped yet
/// carry [`TX_4X4`] (`0`, the constructor pre-fill). Cells inside
/// a decoded block's `bh4 * bw4` footprint carry the block's
/// `TxSize` ordinal (`0..TX_SIZES_ALL`).
#[must_use]
pub fn tx_sizes(&self) -> &[u8] {
&self.tx_sizes
}
/// View of the §5.11.16 / §5.11.17 `InterTxSizes[]` grid after the
/// walk. Indexed row-major: `inter_tx_sizes()[ r * MiCols + c ]`.
/// Cells that no §5.11.16 / §5.11.17 call has stamped yet carry
/// [`TX_4X4`] (`0`, the constructor pre-fill). Decoded-block
/// footprints carry the block's per-cell `TxSize` (which equals
/// the block-level `TxSize` for the §5.11.16 `else`-arm grid-fill,
/// and the per-sub-transform `txSz` for the §5.11.17 `read_var_tx_size`
/// recursive base case).
#[must_use]
pub fn inter_tx_sizes(&self) -> &[u8] {
&self.inter_tx_sizes
}
/// View of the §5.11.39 / §5.11.40 / §5.11.47 `TxTypes[][]` luma
/// transform-type cache after the walk. Indexed row-major in
/// `mi_rows * mi_cols` 4×4-luma-sample units:
/// `tx_types()[ r * MiCols + c ]`. Pre-fill is `DCT_DCT = 0` (the
/// §5.11.40 fallback); cells inside a decoded luma TU footprint
/// carry the decoded `Tx_Type_*_Inv_Set?[ inter_tx_type |
/// intra_tx_type ]` ordinal (or the §5.11.39 `all_zero == 1`
/// `DCT_DCT` stamp). r183 LANDED.
#[must_use]
pub fn tx_types(&self) -> &[u8] {
&self.tx_types
}
/// View of the §5.11.18 / §5.11.23 `RefFrames[][][..]` grid after
/// the walk. Indexed row-major with the slot last:
/// `ref_frames()[ (r * MiCols + c) * 2 + slot ]` where `slot` is
/// `0` (`RefFrame[ 0 ]`) or `1` (`RefFrame[ 1 ]`). Pre-fill is
/// `[INTRA_FRAME = 0, NONE = -1]` per the §5.11.18 unavailable-
/// neighbour fallback; cells inside a decoded inter-block's
/// `bh4 * bw4` footprint carry the §5.11.25 `read_ref_frames`
/// output.
#[must_use]
pub fn ref_frames(&self) -> &[i8] {
&self.ref_frames
}
/// Helper to read `RefFrames[ r ][ c ][ slot ]` for the §8.3.2
/// ref-frame ctx walks. Returns `INTRA_FRAME = 0` for slot 0 or
/// `NONE = -1` for slot 1 on out-of-grid coordinates, matching the
/// §5.11.18 `LeftRefFrame[0] = AvailL ? ... : INTRA_FRAME` /
/// `LeftRefFrame[1] = AvailL ? ... : NONE` fallback. Callers must
/// still gate the lookup through [`TileGeometry::is_inside`] to
/// derive `AvailU` / `AvailL` — an unavailable-but-in-tile
/// neighbour is treated as `[INTRA_FRAME, NONE]` by the spec, and
/// this helper's fallback for an out-of-grid coordinate coincides
/// with that.
#[inline]
fn ref_frame_at(&self, r: i32, c: i32, slot: usize) -> i8 {
debug_assert!(slot < 2, "RefFrames slot must be 0 or 1");
if r < 0 || c < 0 {
return if slot == 0 { 0 } else { -1 };
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return if slot == 0 { 0 } else { -1 };
}
self.ref_frames[((r * self.mi_cols + c) as usize) * 2 + slot]
}
/// View of the §5.11.46 / §5.11.49 `PaletteSizes[ plane ][ row ][ col ]`
/// grid after the walk. Indexed row-major with the plane outermost:
/// `palette_sizes()[ plane * MiRows * MiCols + r * MiCols + c ]`
/// where `plane = 0` is luma, `plane = 1` is U chroma, and `plane =
/// 2` is V chroma. Pre-fill is `0` (the §5.11.22 line 11-12
/// initialiser / §5.11.49 `aboveN = 0` / `leftN = 0` fallback);
/// cells inside the `bh4 * bw4` footprint of a §5.11.46 decoded
/// block carry the `PaletteSize{Y,UV}` value in `2..=PALETTE_COLORS
/// = 8`. The U and V planes always share the same `PaletteSizeUV`
/// per the §5.11.46 syntax (both `plane = 1` and `plane = 2` slots
/// carry the same value when the chroma arm fires).
#[must_use]
pub fn palette_sizes(&self) -> &[u8] {
&self.palette_sizes
}
/// View of the §5.11.46 / §5.11.49 `PaletteColors[ plane ][ row ][
/// col ][ idx ]` grid after the walk. Indexed row-major with the
/// plane outermost and palette-entry-index innermost: `palette_colors()[
/// ((plane * MiRows + r) * MiCols + c) * PALETTE_COLORS + idx ]`.
/// Pre-fill is `0` for every slot; cells inside the `bh4 * bw4`
/// footprint of a §5.11.46 decoded block carry the sorted palette
/// entries (range `0..(1 << BitDepth)`); slots `idx >=
/// palette_sizes()[ … ]` stay at `0`.
/// NOTE (r453): the grid is materialized lazily — on a walk where
/// no §5.11.46 palette landed the returned slice is EMPTY (the
/// all-zero pre-fill was never allocated).
#[must_use]
pub fn palette_colors(&self) -> &[u16] {
&self.palette_colors
}
/// r453 — materialize the `PaletteColors[]` grid on the first
/// real §5.11.46 palette write (see the constructor note).
/// r464 — materialise the lazily allocated per-mi local-warp grid
/// (see `PartitionWalker::new`).
fn ensure_local_warp_params(&mut self) {
if self.local_warp_params.is_empty() {
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
self.local_warp_params.resize(area * 6, 0);
}
}
fn ensure_palette_colors(&mut self) {
if self.palette_colors.is_empty() {
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
self.palette_colors.resize(3 * area * PALETTE_COLORS, 0);
}
}
/// `#[cfg(test)]` helper — stamp a §5.11.46 palette into a
/// single (`plane`, `mi_row`, `mi_col`) cell so cross-module
/// tests can prime the §5.11.49 cache state without rerunning
/// the full `decode_intra_block_mode_info` walker. Tests-only.
///
/// Out-of-grid coordinates or `entries.len() > PALETTE_COLORS`
/// return `Err(())` without mutating the walker.
#[cfg(test)]
pub(crate) fn stamp_palette_for_test(
&mut self,
plane: usize,
mi_row: u32,
mi_col: u32,
entries: &[u16],
) -> Result<(), ()> {
if plane >= 3 || entries.len() > PALETTE_COLORS {
return Err(());
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(());
}
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
let cell = plane * area + (mi_row as usize) * (self.mi_cols as usize) + (mi_col as usize);
self.ensure_palette_colors();
self.palette_sizes[cell] = entries.len() as u8;
let base = cell * PALETTE_COLORS;
for (i, &v) in entries.iter().enumerate() {
self.palette_colors[base + i] = v;
}
Ok(())
}
/// r282 — encoder-mirror grid stamp. The §5.11.4 write-side driver
/// ([`crate::encoder::partition_tree::write_partition_tree_syntax`])
/// keeps a `PartitionWalker` as its neighbour-state mirror: every
/// §8.3.2 context the matching reader derives from the walker's
/// grids (`Skips[]` for `skip`, `SegmentIds[]` for the §5.11.9
/// cascade, `YModes[]` for `intra_frame_y_mode`, `PaletteSizes[]` /
/// `PaletteColors[]` for `has_palette_y` + the §5.11.49 cache,
/// `cdef_idx[]` anchors for §5.11.56, and the `IsInters[]` /
/// `RefFrames[]` / `Mvs[]` / `InterpFilters[]` cells the §7.10.2
/// `find_mv_stack` scan reads) must be derivable on the write side
/// from identically-stamped grids — that is what keeps the encoder
/// and the §5.11.5 [`Self::decode_block_syntax`] walker in
/// sync-sentinel lockstep.
///
/// The stamp set mirrors the reader's per-leaf grid writes
/// one-to-one:
///
/// * `MiSizes` / `Skips` / `SegmentIds` / `YModes` — always
/// stamped over the `bh4 * bw4` footprint (the §5.11.5 grid-fill
/// loops + the per-element stamps inside `decode_skip` /
/// `decode_segment_id` / `decode_intra_frame_y_mode`).
/// * `IsInters` / `RefFrames` / `InterpFilters` / `Mvs` — the
/// `is_inter != 0` (§5.11.7 `use_intrabc == 1`) arm's stamps;
/// gated exactly as the reader gates them (the intra `else` arm
/// leaves these grids untouched on the decode walker too —
/// `IsInters` is stamped `0` unconditionally because the reader's
/// pre-fill is `0` and re-stamping the identity keeps the loop
/// total).
/// * `PaletteSizes` / `PaletteColors` — the §5.11.46 / §5.11.49
/// stamps; only non-zero sizes are written (matching the
/// reader's "only stamp non-zero entries" identity on the
/// zero-initialised grids). The UV size stamps planes 1 and 2
/// with `palette_colors_u` / `palette_colors_v` respectively.
/// * `TxSizes` / `InterTxSizes` — the §5.11.16 else-arm grid-fill
/// (always stamped; on the supported `TxMode != TX_MODE_SELECT`
/// configuration the reader's stamp is unconditional too).
/// * `cdef_idx` — the §5.11.56 anchor walk, performed only when
/// `cdef` is `Some` (i.e. the writer emitted the `L(cdef_bits)`
/// literal for this leaf; the short-circuit and
/// anchor-already-stamped arms leave the grid untouched, exactly
/// like [`Self::decode_cdef`]).
///
/// Coordinates past the frame extent are clipped exactly like the
/// reader's grid-fill loops. Not exposed publicly: the stamp is an
/// encoder-internal mirror primitive, not a decoded-state surface.
pub(crate) fn stamp_encoder_block_syntax(&mut self, s: &EncoderBlockSyntaxStamp<'_>) {
let bw4 = NUM_4X4_BLOCKS_WIDE[s.sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[s.sub_size] as u32;
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
for dr in 0..bh4 {
let rr = s.mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = s.mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr * self.mi_cols + cc) as usize;
self.mi_sizes[cell] = s.sub_size;
self.skips[cell] = s.skip;
self.skip_modes[cell] = s.skip_mode;
self.segment_ids[cell] = s.segment_id as i32;
self.y_modes[cell] = s.y_mode;
self.is_inters[cell] = s.is_inter;
// §5.11.16 else-arm grid-fill mirror (r283): the decode
// walker's `read_block_tx_size` stamps both grids over
// the footprint on its (bit-silent here) else arm.
self.tx_sizes[cell] = s.tx_size;
self.inter_tx_sizes[cell] = s.tx_size;
if s.is_inter != 0 {
// §5.11.7 `use_intrabc == 1` footer stamps (mirrors
// the `decode_block_syntax` intrabc-arm grid-fill).
self.ref_frames[cell * 2] = s.ref_frame[0];
self.ref_frames[cell * 2 + 1] = s.ref_frame[1];
self.interp_filters[cell * 2] = s.interp_filter[0];
self.interp_filters[cell * 2 + 1] = s.interp_filter[1];
// §6.10.27 conformance bound `Abs( Mv ) < (1 << 14)`
// keeps the i32 → i16 cast lossless.
self.mvs[(cell * 2) * 2] = s.mv[0] as i16;
self.mvs[(cell * 2) * 2 + 1] = s.mv[1] as i16;
self.mvs[(cell * 2 + 1) * 2] = s.mv2[0] as i16;
self.mvs[(cell * 2 + 1) * 2 + 1] = s.mv2[1] as i16;
// §5.11.27 `motion_mode` grid-fill (mirrors the
// decode walker's per-footprint stamp). Drives the
// §5.11.33 frame walk's OBMC / warp dispatch.
self.motion_modes[cell] = s.motion_mode;
// r415 — §5.11.29 / §8.3.2 grid-fill twins of the
// decode walker's `CompGroupIdxs` / `CompoundIdxs`
// / `CompoundTypes` (+ wedge/diffwtd side data)
// stamps.
self.comp_group_idxs[cell] = s.comp_group_idx;
self.compound_idxs[cell] = s.compound_idx;
self.compound_types[cell] = s.compound_type;
self.compound_wedge_indices[cell] = s.wedge_index;
self.compound_wedge_signs[cell] = s.wedge_sign;
self.compound_mask_types[cell] = s.mask_type;
// r417 — §5.11.28 inter-intra grid-fill twins of
// the decode walker's stamps.
self.interintra_modes[cell] = s.interintra_mode;
self.wedge_interintras[cell] = s.wedge_interintra;
self.interintra_wedge_indices[cell] = s.interintra_wedge_index;
}
if s.palette_size_y > 0 {
self.ensure_palette_colors();
self.palette_sizes[cell] = s.palette_size_y;
let base = cell * PALETTE_COLORS;
for (i, &v) in s
.palette_colors_y
.iter()
.enumerate()
.take(PALETTE_COLORS.min(s.palette_size_y as usize))
{
self.palette_colors[base + i] = v;
}
}
if s.palette_size_uv > 0 {
self.ensure_palette_colors();
for (plane, colors) in
[(1usize, s.palette_colors_u), (2usize, s.palette_colors_v)]
{
let plane_cell = plane * area + cell;
self.palette_sizes[plane_cell] = s.palette_size_uv;
let base = plane_cell * PALETTE_COLORS;
for (i, &v) in colors
.iter()
.enumerate()
.take(PALETTE_COLORS.min(s.palette_size_uv as usize))
{
self.palette_colors[base + i] = v;
}
}
}
}
}
// §5.11.56 anchor stamp — only when the leaf emitted the
// `L(cdef_bits)` literal (mirrors `decode_cdef`'s inner-branch
// grid-fill: stride `cdefSize4 = 16` over the footprint).
if let Some(v) = s.cdef {
let cdef_size4 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as u32;
let mask: u32 = !(cdef_size4 - 1);
let r = s.mi_row & mask;
let c = s.mi_col & mask;
let mut i = r;
while i < r + bh4 {
if i >= self.mi_rows {
break;
}
let mut j = c;
while j < c + bw4 {
if j >= self.mi_cols {
break;
}
self.cdef_idx[(i * self.mi_cols + j) as usize] = v;
j += cdef_size4;
}
i += cdef_size4;
}
}
// §5.11.5 `DeltaLFs[ r + y ][ c + x ][ i ] = DeltaLF[ i ]`
// grid-fill — stamp the current §5.11.13 accumulator over the
// block footprint, keeping the encoder-mirror per-mi snapshot in
// decode-walker parity for the §7.14.4 deblock. When
// `delta_lf_present == 0` the accumulator stays at its all-zero
// §5.9.18 default, so this is a no-op write of zeros.
self.stamp_delta_lfs(s.mi_row, s.mi_col, s.sub_size);
}
/// r412 — rect snapshot of every mode-info grid
/// [`Self::stamp_encoder_block_syntax`] writes, over the (clipped)
/// `n4 × n4` mi footprint anchored at `(mi_row, mi_col)`.
///
/// The encoder's rate-distortion search trial-stamps candidate
/// leaves into a driver-side mirror walker so the §7.10.2
/// `find_mv_stack` scan (and the §8.3.2 neighbour-ctx walks that
/// consult `RefFrames[]` / `InterpFilters[]` / `Mvs[]`) can be
/// evaluated at search time with exactly the state the write-pass
/// mirror will hold. Because every trial stamps only cells INSIDE
/// the node's own footprint, restoring that rect returns the
/// mirror to its pre-trial state — neighbour cells outside the
/// rect are never touched by a trial, so they need no capture
/// (the same region-locality argument the pixel-plane
/// `save_region` / `restore_region` pair rests on).
///
/// NOT captured: `cdef_idx` (the §5.11.56 anchor stamp can land
/// OUTSIDE the rect at a 64×64-aligned origin, so a search driver
/// must stamp with `cdef: None`) and the txb level/DC context
/// arrays (not written by the stamp). Callers that trial-stamp
/// with `cdef: Some(..)` would corrupt out-of-rect state and are
/// a caller bug by this contract.
pub(crate) fn snapshot_encoder_stamp_rect(
&self,
mi_row: u32,
mi_col: u32,
n4: u32,
) -> EncoderStampSnapshot {
self.snapshot_encoder_stamp_rect_wh(mi_row, mi_col, n4, n4)
}
/// r460 — [`Self::snapshot_encoder_stamp_rect`] over a `w4 × h4`
/// (columns × rows, mi units) rectangle.
pub(crate) fn snapshot_encoder_stamp_rect_wh(
&self,
mi_row: u32,
mi_col: u32,
w4: u32,
h4: u32,
) -> EncoderStampSnapshot {
let r1 = (mi_row + h4).min(self.mi_rows);
let c1 = (mi_col + w4).min(self.mi_cols);
let rows = r1.saturating_sub(mi_row);
let cols = c1.saturating_sub(mi_col);
let cells = (rows as usize) * (cols as usize);
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
let mut snap = EncoderStampSnapshot {
mi_row,
mi_col,
rows,
cols,
mi_sizes: Vec::with_capacity(cells),
skips: Vec::with_capacity(cells),
skip_modes: Vec::with_capacity(cells),
segment_ids: Vec::with_capacity(cells),
y_modes: Vec::with_capacity(cells),
is_inters: Vec::with_capacity(cells),
tx_sizes: Vec::with_capacity(cells),
inter_tx_sizes: Vec::with_capacity(cells),
motion_modes: Vec::with_capacity(cells),
ref_frames: Vec::with_capacity(cells * 2),
interp_filters: Vec::with_capacity(cells * 2),
mvs: Vec::with_capacity(cells * 4),
delta_lfs: Vec::with_capacity(cells * FRAME_LF_COUNT),
palette_sizes: Vec::with_capacity(cells * 3),
palette_colors: Vec::with_capacity(cells * 3 * PALETTE_COLORS),
comp_group_idxs: Vec::with_capacity(cells),
compound_idxs: Vec::with_capacity(cells),
compound_types: Vec::with_capacity(cells),
compound_wedge_indices: Vec::with_capacity(cells),
compound_wedge_signs: Vec::with_capacity(cells),
compound_mask_types: Vec::with_capacity(cells),
interintra_modes: Vec::with_capacity(cells),
wedge_interintras: Vec::with_capacity(cells),
interintra_wedge_indices: Vec::with_capacity(cells),
};
for rr in mi_row..r1 {
for cc in mi_col..c1 {
let cell = (rr * self.mi_cols + cc) as usize;
snap.mi_sizes.push(self.mi_sizes[cell]);
snap.skips.push(self.skips[cell]);
snap.skip_modes.push(self.skip_modes[cell]);
snap.segment_ids.push(self.segment_ids[cell]);
snap.y_modes.push(self.y_modes[cell]);
snap.is_inters.push(self.is_inters[cell]);
snap.tx_sizes.push(self.tx_sizes[cell]);
snap.inter_tx_sizes.push(self.inter_tx_sizes[cell]);
snap.motion_modes.push(self.motion_modes[cell]);
snap.ref_frames
.extend_from_slice(&self.ref_frames[cell * 2..cell * 2 + 2]);
snap.interp_filters
.extend_from_slice(&self.interp_filters[cell * 2..cell * 2 + 2]);
snap.mvs
.extend_from_slice(&self.mvs[cell * 4..cell * 4 + 4]);
snap.delta_lfs.extend_from_slice(
&self.delta_lfs[cell * FRAME_LF_COUNT..(cell + 1) * FRAME_LF_COUNT],
);
for plane in 0..3usize {
let pcell = plane * area + cell;
snap.palette_sizes.push(self.palette_sizes[pcell]);
if self.palette_colors.is_empty() {
// Lazily-materialized grid never touched:
// snapshot the all-zero pre-fill.
let n = snap.palette_colors.len();
snap.palette_colors.resize(n + PALETTE_COLORS, 0);
} else {
snap.palette_colors.extend_from_slice(
&self.palette_colors
[pcell * PALETTE_COLORS..(pcell + 1) * PALETTE_COLORS],
);
}
}
snap.comp_group_idxs.push(self.comp_group_idxs[cell]);
snap.compound_idxs.push(self.compound_idxs[cell]);
snap.compound_types.push(self.compound_types[cell]);
snap.compound_wedge_indices
.push(self.compound_wedge_indices[cell]);
snap.compound_wedge_signs
.push(self.compound_wedge_signs[cell]);
snap.compound_mask_types
.push(self.compound_mask_types[cell]);
snap.interintra_modes.push(self.interintra_modes[cell]);
snap.wedge_interintras.push(self.wedge_interintras[cell]);
snap.interintra_wedge_indices
.push(self.interintra_wedge_indices[cell]);
}
}
snap
}
/// r412 — restore a rect captured by
/// [`Self::snapshot_encoder_stamp_rect`]. Exact inverse: every
/// captured grid value is written back to its cell; nothing
/// outside the rect is touched.
pub(crate) fn restore_encoder_stamp_rect(&mut self, snap: &EncoderStampSnapshot) {
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
let mut i = 0usize;
for dr in 0..snap.rows {
for dc in 0..snap.cols {
let cell = ((snap.mi_row + dr) * self.mi_cols + (snap.mi_col + dc)) as usize;
self.mi_sizes[cell] = snap.mi_sizes[i];
self.skips[cell] = snap.skips[i];
self.skip_modes[cell] = snap.skip_modes[i];
self.segment_ids[cell] = snap.segment_ids[i];
self.y_modes[cell] = snap.y_modes[i];
self.is_inters[cell] = snap.is_inters[i];
self.tx_sizes[cell] = snap.tx_sizes[i];
self.inter_tx_sizes[cell] = snap.inter_tx_sizes[i];
self.motion_modes[cell] = snap.motion_modes[i];
self.ref_frames[cell * 2..cell * 2 + 2]
.copy_from_slice(&snap.ref_frames[i * 2..i * 2 + 2]);
self.interp_filters[cell * 2..cell * 2 + 2]
.copy_from_slice(&snap.interp_filters[i * 2..i * 2 + 2]);
self.mvs[cell * 4..cell * 4 + 4].copy_from_slice(&snap.mvs[i * 4..i * 4 + 4]);
self.delta_lfs[cell * FRAME_LF_COUNT..(cell + 1) * FRAME_LF_COUNT]
.copy_from_slice(&snap.delta_lfs[i * FRAME_LF_COUNT..(i + 1) * FRAME_LF_COUNT]);
for plane in 0..3usize {
let pcell = plane * area + cell;
self.palette_sizes[pcell] = snap.palette_sizes[i * 3 + plane];
let src = &snap.palette_colors
[(i * 3 + plane) * PALETTE_COLORS..(i * 3 + plane + 1) * PALETTE_COLORS];
if !self.palette_colors.is_empty() || src.iter().any(|&v| v != 0) {
// Writing all-zero into a never-materialized
// grid is the identity — skip the allocation.
self.ensure_palette_colors();
self.palette_colors[pcell * PALETTE_COLORS..(pcell + 1) * PALETTE_COLORS]
.copy_from_slice(src);
}
}
self.comp_group_idxs[cell] = snap.comp_group_idxs[i];
self.compound_idxs[cell] = snap.compound_idxs[i];
self.compound_types[cell] = snap.compound_types[i];
self.compound_wedge_indices[cell] = snap.compound_wedge_indices[i];
self.compound_wedge_signs[cell] = snap.compound_wedge_signs[i];
self.compound_mask_types[cell] = snap.compound_mask_types[i];
self.interintra_modes[cell] = snap.interintra_modes[i];
self.wedge_interintras[cell] = snap.wedge_interintras[i];
self.interintra_wedge_indices[cell] = snap.interintra_wedge_indices[i];
i += 1;
}
}
}
/// r460 — capture every walker cell the ENCODER write path
/// (`write_block_syntax` / `write_single_transform_block` and the
/// partition-symbol stamps) can touch while coding one block at
/// `(mi_row, mi_col)` of `w4 × h4` mi units, so a search driver
/// can price a candidate on the LIVE state and roll it back
/// without cloning the whole-frame mirror (the pre-r460 rate twin
/// deep-cloned the walker per priced transform unit — O(frame)
/// per candidate, quadratic over a picture).
///
/// Covered: the [`Self::snapshot_encoder_stamp_rect_wh`] grids,
/// `TxTypes[]` over the rect, the §5.11.56 `cdef_idx` anchor cells
/// (the stamp lands on the 64×64-aligned origins covering the
/// rect, which can lie OUTSIDE it), the §5.11.39 above / left
/// level + DC context spans of every plane (widened by one mi on
/// each side so the sub-8×8 chroma stitch's neighbouring column /
/// row is inside the scope), the §5.11.9 segment-prediction
/// context spans, and the §5.11.2 `CurrentQIndex` /
/// `DeltaLF[]` / `ReadDeltas` scalars.
pub(crate) fn snapshot_price_scope(
&self,
mi_row: u32,
mi_col: u32,
w4: u32,
h4: u32,
subsampling_x: u8,
subsampling_y: u8,
) -> PriceScopeSnapshot {
let stamp = self.snapshot_encoder_stamp_rect_wh(mi_row, mi_col, w4, h4);
let r1 = (mi_row + h4).min(self.mi_rows);
let c1 = (mi_col + w4).min(self.mi_cols);
let mut tx_types =
Vec::with_capacity(((r1 - mi_row.min(r1)) * (c1 - mi_col.min(c1))) as usize);
for rr in mi_row..r1 {
for cc in mi_col..c1 {
tx_types.push(self.tx_types[(rr * self.mi_cols + cc) as usize]);
}
}
let cdef_size4 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as u32;
let mask: u32 = !(cdef_size4 - 1);
let mut cdef = Vec::new();
let mut i = mi_row & mask;
while i < mi_row + h4 && i < self.mi_rows {
let mut j = mi_col & mask;
while j < mi_col + w4 && j < self.mi_cols {
let idx = (i * self.mi_cols + j) as usize;
cdef.push((idx, self.cdef_idx[idx]));
j += cdef_size4;
}
i += cdef_size4;
}
let mut above_ctx = Vec::new();
let mut left_ctx = Vec::new();
let mut spans = [(0usize, 0usize, 0usize, 0usize); 3];
for (plane, span) in spans.iter_mut().enumerate() {
let (ssx, ssy) = if plane == 0 {
(0u32, 0u32)
} else {
(u32::from(subsampling_x), u32::from(subsampling_y))
};
let above_base = plane * self.mi_cols as usize;
let left_base = plane * self.mi_rows as usize;
let c0 = (mi_col.saturating_sub(1) >> ssx) as usize;
let c1p = (((mi_col + w4 + 1) >> ssx) as usize + 1).min(self.mi_cols as usize);
let r0 = (mi_row.saturating_sub(1) >> ssy) as usize;
let r1p = (((mi_row + h4 + 1) >> ssy) as usize + 1).min(self.mi_rows as usize);
let (c0, r0) = (c0.min(c1p), r0.min(r1p));
*span = (c0, c1p, r0, r1p);
for i in c0..c1p {
above_ctx.push((
self.above_level_context[above_base + i],
self.above_dc_context[above_base + i],
));
}
for i in r0..r1p {
left_ctx.push((
self.left_level_context[left_base + i],
self.left_dc_context[left_base + i],
));
}
}
let seg_c0 = mi_col.min(self.mi_cols) as usize;
let seg_c1 = c1 as usize;
let seg_r0 = mi_row.min(self.mi_rows) as usize;
let seg_r1 = r1 as usize;
PriceScopeSnapshot {
stamp,
tx_types,
cdef,
spans,
above_ctx,
left_ctx,
seg_cols: (seg_c0, seg_c1),
seg_rows: (seg_r0, seg_r1),
above_seg: self.above_seg_pred_context[seg_c0..seg_c1.max(seg_c0)].to_vec(),
left_seg: self.left_seg_pred_context[seg_r0..seg_r1.max(seg_r0)].to_vec(),
current_q_index: self.current_q_index,
current_delta_lf: self.current_delta_lf,
read_deltas_pending: self.read_deltas_pending,
}
}
/// r460 — roll the walker back to (or forward onto) a
/// [`Self::snapshot_price_scope`] capture. Value-restoring, so a
/// capture taken AFTER a trial commit re-applies that commit.
pub(crate) fn restore_price_scope(&mut self, snap: &PriceScopeSnapshot) {
self.restore_encoder_stamp_rect(&snap.stamp);
let (mi_row, mi_col) = (snap.stamp.mi_row, snap.stamp.mi_col);
let mut i = 0usize;
for dr in 0..snap.stamp.rows {
for dc in 0..snap.stamp.cols {
let cell = ((mi_row + dr) * self.mi_cols + mi_col + dc) as usize;
self.tx_types[cell] = snap.tx_types[i];
i += 1;
}
}
for &(idx, v) in &snap.cdef {
self.cdef_idx[idx] = v;
}
let mut ai = 0usize;
let mut li = 0usize;
for (plane, &(c0, c1, r0, r1)) in snap.spans.iter().enumerate() {
let above_base = plane * self.mi_cols as usize;
let left_base = plane * self.mi_rows as usize;
for i in c0..c1 {
let (lvl, dc) = snap.above_ctx[ai];
ai += 1;
self.above_level_context[above_base + i] = lvl;
self.above_dc_context[above_base + i] = dc;
}
for i in r0..r1 {
let (lvl, dc) = snap.left_ctx[li];
li += 1;
self.left_level_context[left_base + i] = lvl;
self.left_dc_context[left_base + i] = dc;
}
}
let (c0, c1) = snap.seg_cols;
if c1 > c0 {
self.above_seg_pred_context[c0..c1].copy_from_slice(&snap.above_seg);
}
let (r0, r1) = snap.seg_rows;
if r1 > r0 {
self.left_seg_pred_context[r0..r1].copy_from_slice(&snap.left_seg);
}
self.current_q_index = snap.current_q_index;
self.current_delta_lf = snap.current_delta_lf;
self.read_deltas_pending = snap.read_deltas_pending;
}
/// Helper to read `PaletteSizes[ plane ][ r ][ c ]` for the
/// §5.11.49 `get_palette_cache` neighbour-walk. Returns `0` for
/// out-of-grid coordinates (the §5.11.49 `aboveN = 0` / `leftN = 0`
/// "no palette available" fallback).
#[inline]
fn palette_size_at(&self, plane: usize, r: i32, c: i32) -> u8 {
debug_assert!(plane < 3, "PaletteSizes plane must be 0, 1 or 2");
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
self.palette_sizes[plane * area + (r as usize) * (self.mi_cols as usize) + (c as usize)]
}
/// Helper to read `PaletteColors[ plane ][ r ][ c ][ idx ]` for the
/// §5.11.49 `get_palette_cache` neighbour-walk. Returns `0` for
/// out-of-grid coordinates / out-of-range `idx` (the spec gates
/// these reads behind `aboveIdx < aboveN` / `leftIdx < leftN`, so
/// the fallback value is unobservable on the conformant path).
#[inline]
fn palette_color_at(&self, plane: usize, r: i32, c: i32, idx: usize) -> u16 {
debug_assert!(plane < 3, "PaletteColors plane must be 0, 1 or 2");
debug_assert!(
idx < PALETTE_COLORS,
"palette idx must be in 0..PALETTE_COLORS"
);
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
if self.palette_colors.is_empty() {
// Lazily-materialized grid never touched: all-zero pre-fill.
return 0;
}
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
let cell = plane * area + (r as usize) * (self.mi_cols as usize) + (c as usize);
self.palette_colors[cell * PALETTE_COLORS + idx]
}
/// `get_palette_cache( plane )` per §5.11.49 (av1-spec p.99) — the
/// merged-and-deduplicated cache of the above + left neighbour
/// palettes for the block anchored at `(mi_row, mi_col)`. Returns
/// the number `n` of cache entries written (`0..=2 * PALETTE_COLORS
/// = 16`); the entries themselves are written into the first `n`
/// slots of the caller-supplied `cache` buffer in ascending order
/// (the spec's `PaletteCache[ n ]` writes).
///
/// ```text
/// get_palette_cache( plane ) {
/// aboveN = 0
/// if ( ( MiRow * MI_SIZE ) % 64 ) {
/// aboveN = PaletteSizes[ plane ][ MiRow - 1 ][ MiCol ]
/// }
/// leftN = 0
/// if ( AvailL ) {
/// leftN = PaletteSizes[ plane ][ MiRow ][ MiCol - 1 ]
/// }
/// … walk above + left palettes in ascending order …
/// return n
/// }
/// ```
///
/// The `(MiRow * MI_SIZE) % 64` gate suppresses the above-neighbour
/// read at superblock-top boundaries (the §5.11.49 spec body's
/// "above is unavailable at the top of a 64×64-aligned superblock"
/// derivation). `AvailL` is the standard left-availability gate
/// (`mi_col > 0` for an mi-aligned tile-start fixture; this method
/// gates on `mi_col > 0` only — callers operating inside a tile
/// with `mi_col_start > 0` should consult the tile geometry for the
/// full availability check, but the cache read still returns
/// correct entries because `palette_size_at` returns 0 for any
/// unwritten cell).
///
/// The neighbour palettes are already in ascending order (the
/// §5.11.46 `sort()` invariant); the merge walks them with a
/// classic two-pointer sort and de-dupe against the most-recently-
/// written cache entry (`n == 0 || val != PaletteCache[ n - 1 ]`).
///
/// The `cache` buffer must hold at least `2 * PALETTE_COLORS = 16`
/// entries — callers should pass a fixed-size buffer.
#[must_use]
pub fn get_palette_cache(
&self,
plane: usize,
mi_row: u32,
mi_col: u32,
cache: &mut [u16; 2 * PALETTE_COLORS],
) -> usize {
debug_assert!(plane < 3, "plane must be 0, 1 or 2");
// §5.11.49 `aboveN = 0; if ((MiRow * MI_SIZE) % 64) aboveN =
// PaletteSizes[plane][MiRow - 1][MiCol]`. MI_SIZE = 4 so
// `MiRow * 4 % 64` simplifies to `MiRow % 16`.
let above_n: usize = if mi_row > 0 && ((mi_row as usize) * MI_SIZE) % 64 != 0 {
self.palette_size_at(plane, mi_row as i32 - 1, mi_col as i32) as usize
} else {
0
};
// §5.11.49 `leftN = 0; if (AvailL) leftN = PaletteSizes[plane][MiRow][MiCol - 1]`.
// `AvailL` is the §5.11.51 TILE-scope predicate — a block at a
// mid-frame tile's first column has `mi_col > 0` but its left
// neighbour belongs to the previous tile, where `AvailL` is
// false (r384 multi-tile fix: the `mi_col > 0` shortcut let a
// later tile merge the previous tile's palette colours into
// its cache and desynchronise the arithmetic decoder).
let left_n: usize = if self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1) {
self.palette_size_at(plane, mi_row as i32, mi_col as i32 - 1) as usize
} else {
0
};
let mut above_idx: usize = 0;
let mut left_idx: usize = 0;
let mut n: usize = 0;
// §5.11.49 two-pointer merge with de-dupe against the previous
// cache entry.
while above_idx < above_n && left_idx < left_n {
let above_c = self.palette_color_at(plane, mi_row as i32 - 1, mi_col as i32, above_idx);
let left_c = self.palette_color_at(plane, mi_row as i32, mi_col as i32 - 1, left_idx);
if left_c < above_c {
if n == 0 || left_c != cache[n - 1] {
cache[n] = left_c;
n += 1;
}
left_idx += 1;
} else {
if n == 0 || above_c != cache[n - 1] {
cache[n] = above_c;
n += 1;
}
above_idx += 1;
if left_c == above_c {
left_idx += 1;
}
}
}
// §5.11.49 above-tail: drain any remaining above entries.
while above_idx < above_n {
let val = self.palette_color_at(plane, mi_row as i32 - 1, mi_col as i32, above_idx);
above_idx += 1;
if n == 0 || val != cache[n - 1] {
cache[n] = val;
n += 1;
}
}
// §5.11.49 left-tail: drain any remaining left entries.
while left_idx < left_n {
let val = self.palette_color_at(plane, mi_row as i32, mi_col as i32 - 1, left_idx);
left_idx += 1;
if n == 0 || val != cache[n - 1] {
cache[n] = val;
n += 1;
}
}
n
}
/// View of the §6.10.4 `SegmentIds[]` grid after the walk. Indexed
/// row-major: `segment_ids()[ r * MiCols + c ]`. Cells that no
/// §5.11.9 [`Self::decode_segment_id`] call has touched yet carry
/// the `-1` sentinel (the spec's `prevUL = -1` / `prevU = -1` /
/// `prevL = -1` "neighbour unavailable" marker). Cells inside a
/// decoded block's `bh4 * bw4` footprint carry the block's
/// `segment_id` (a value in `0..MAX_SEGMENTS = 0..8`).
#[must_use]
pub fn segment_ids(&self) -> &[i32] {
&self.segment_ids
}
/// Helper to read `SegmentIds[ r ][ c ]` for the §5.11.9
/// neighbour-lookup. Returns the `-1` sentinel for out-of-grid
/// coordinates (matching the spec's `AvailU ? ... : -1` /
/// `AvailL ? ... : -1` / `AvailU && AvailL ? ... : -1`
/// gating). Returns the stored value for in-grid coordinates;
/// if no §5.11.9 call has touched the cell yet, the stored
/// value is also `-1` (the constructor's pre-fill sentinel),
/// which is the same "no information" value §5.11.9's `pred`
/// derivation already understands.
#[inline]
fn segment_id_at(&self, r: i32, c: i32) -> i32 {
if r < 0 || c < 0 {
return -1;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return -1;
}
self.segment_ids[(r * self.mi_cols + c) as usize]
}
/// View of the §5.11.19 / §8.3.2 `AboveSegPredContext[ MiCol ]`
/// per-column flag array. One slot per `mi_col` column; entries
/// are `0` until a §5.11.19 `inter_segment_id` call has stamped a
/// `seg_id_predicted` value into the column. The §8.3.2 ctx walk
/// adds `AboveSegPredContext[ MiCol ]` to
/// `LeftSegPredContext[ MiRow ]` (each in `0..=1`) for a sum in
/// `0..SEGMENT_ID_PREDICTED_CONTEXTS = 0..3`.
#[must_use]
pub fn above_seg_pred_context(&self) -> &[u8] {
&self.above_seg_pred_context
}
/// View of the §5.11.19 / §8.3.2 `LeftSegPredContext[ MiRow ]`
/// per-row flag array. One slot per `mi_row` row; semantics
/// mirror [`Self::above_seg_pred_context`].
#[must_use]
pub fn left_seg_pred_context(&self) -> &[u8] {
&self.left_seg_pred_context
}
/// View of the §6.10.2 / §8.3.2 `AboveLevelContext[ plane ][ x4 ]`
/// array — flat plane-major (`plane * mi_cols + x4`). Encoder
/// write↔decode lockstep tests compare this against the mirror's.
#[must_use]
pub fn above_level_context(&self) -> &[u8] {
&self.above_level_context
}
/// View of `AboveDcContext[ plane ][ x4 ]` —
/// see [`Self::above_level_context`].
#[must_use]
pub fn above_dc_context(&self) -> &[u8] {
&self.above_dc_context
}
/// View of `LeftLevelContext[ plane ][ y4 ]` — flat plane-major
/// (`plane * mi_rows + y4`).
#[must_use]
pub fn left_level_context(&self) -> &[u8] {
&self.left_level_context
}
/// View of `LeftDcContext[ plane ][ y4 ]` —
/// see [`Self::left_level_context`].
#[must_use]
pub fn left_dc_context(&self) -> &[u8] {
&self.left_dc_context
}
/// §5.11.2 `clear_left_context()` (the §6.10.2 semantics) — zeroes
/// `LeftLevelContext` / `LeftDcContext` for every plane and row.
/// The §5.11.2 `decode_tile()` loop invokes this at the start of
/// every superblock row; the walker's constructor covers the first
/// row, so a multi-superblock-row driver calls this between rows.
/// (`LeftSegPredContext` shares the §6.10.2 reset; it is zeroed
/// here too.)
pub fn clear_txb_left_context(&mut self) {
self.left_level_context.fill(0);
self.left_dc_context.fill(0);
self.left_seg_pred_context.fill(0);
}
/// §5.11.2 `decode_tile()` per-tile entry state — point the walker
/// at a new tile's [`TileGeometry`] and perform the tile-scope
/// resets the spec runs before the superblock loop: the
/// `clear_above_context()` column-indexed above entropy contexts
/// (`Above{Level,Dc}Context` / `AboveSegPredContext` — a tile in a
/// SECOND tile row would otherwise observe the tile above it;
/// r408 fix, the pre-r408 reset covered only the left arrays and
/// desynced every multi-tile-ROW frame), the row-indexed left
/// entropy contexts (`Left{Level,Dc}Context` /
/// `LeftSegPredContext` — shared across horizontally-adjacent
/// tiles, so a later tile must not observe an earlier tile's
/// state), the §5.11.13 `DeltaLF` accumulator, and the §5.11.58
/// running loop-restoration references (`RefLrWiener` /
/// `RefSgrXqd`).
///
/// The frame-scope decode grids (`MiSizes[]`, `CurrFrame[]`, the
/// mode/skip/tx grids, decoded LR units) are deliberately retained
/// — they accumulate across every tile of the frame.
pub fn begin_tile(&mut self, geometry: TileGeometry) {
self.geometry = geometry;
// §5.11.2 `clear_above_context()` — zero `AboveLevelContext`,
// `AboveDcContext` and `AboveSegPredContext` over the full
// `MiCols` extent at every tile entry.
self.above_level_context.fill(0);
self.above_dc_context.fill(0);
self.above_seg_pred_context.fill(0);
self.clear_txb_left_context();
self.current_delta_lf = [0; FRAME_LF_COUNT];
self.reset_lr_refs();
// §5.11.18 per-cell `AvailU` / `AvailL` follow the tile
// geometry (`is_inside` is tile-relative) — rebuild for the
// in-walk §5.11.33 OBMC dispatch.
self.walk_avail_u.clear();
self.walk_avail_l.clear();
for r in 0..self.mi_rows {
for c in 0..self.mi_cols {
self.walk_avail_u
.push(u8::from(self.geometry.is_inside(r as i32 - 1, c as i32)));
self.walk_avail_l
.push(u8::from(self.geometry.is_inside(r as i32, c as i32 - 1)));
}
}
}
/// §8.3.2 `all_zero` ctx (av1-spec p.370-371) — the
/// `TileTxbSkipCdf[ txSzCtx ][ ctx ]` second axis, derived from
/// the `AboveLevelContext` / `LeftLevelContext` (luma) or those
/// plus `AboveDcContext` / `LeftDcContext` (chroma) neighbour
/// scans over the TU's 4-sample columns / rows.
///
/// * `plane` — `0..=2`; `tx_sz` — the TU's `TX_SIZES_ALL` ordinal.
/// * `mi_size` — the block's `MiSize` (the §8.3.2 body derives
/// `bsize = get_plane_residual_size( MiSize, plane )` from the
/// *block* size, not the §5.11.34 chunk size).
/// * `x4` / `y4` — the TU top-left in the plane's own 4-sample
/// units (`startX >> 2` / `startY >> 2` of §5.11.39).
/// * `sub_x` / `sub_y` — the plane's subsampling (0 for luma; the
/// §5.5.2 `subsampling_x` / `subsampling_y` for chroma), used
/// for the `maxX4 = MiCols >> subsampling_x` clip.
///
/// Returns `None` for out-of-range `plane` / `tx_sz` / `mi_size`
/// or a `BLOCK_INVALID` plane-residual size (caller bugs).
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn txb_skip_ctx(
&self,
plane: u8,
tx_sz: usize,
mi_size: usize,
x4: u32,
y4: u32,
sub_x: u8,
sub_y: u8,
) -> Option<usize> {
if plane > 2 || tx_sz >= TX_SIZES_ALL || mi_size >= BLOCK_SIZES {
return None;
}
// §8.3.2: `maxX4 = MiCols` / `maxY4 = MiRows`, halved per
// axis for chroma.
let (max_x4, max_y4) = if plane > 0 {
(
(self.mi_cols >> sub_x) as usize,
(self.mi_rows >> sub_y) as usize,
)
} else {
(self.mi_cols as usize, self.mi_rows as usize)
};
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
let w4 = w >> 2;
let h4 = h >> 2;
let bsize = get_plane_residual_size(mi_size, plane, sub_x, sub_y)?;
let bw = block_width(bsize);
let bh = block_height(bsize);
let p = plane as usize;
let above_base = p * self.mi_cols as usize;
let left_base = p * self.mi_rows as usize;
let x4 = x4 as usize;
let y4 = y4 as usize;
let ctx = if plane == 0 {
// §8.3.2 luma arm: Max-scan of the level arrays only.
let mut top: u32 = 0;
let mut left: u32 = 0;
for k in 0..w4 {
if x4 + k < max_x4 {
top = core::cmp::max(top, self.above_level_context[above_base + x4 + k] as u32);
}
}
for k in 0..h4 {
if y4 + k < max_y4 {
left = core::cmp::max(left, self.left_level_context[left_base + y4 + k] as u32);
}
}
// §8.3.2 `top = Min( top, 255 )` / `left = Min( left, 255
// )` — vacuous for the u8-backed arrays, kept for spec
// parity.
let top = core::cmp::min(top, 255);
let left = core::cmp::min(left, 255);
if bw == w && bh == h {
0
} else if top == 0 && left == 0 {
1
} else if top == 0 || left == 0 {
2 + usize::from(core::cmp::max(top, left) > 3)
} else if core::cmp::max(top, left) <= 3 {
4
} else if core::cmp::min(top, left) <= 3 {
5
} else {
6
}
} else {
// §8.3.2 chroma arm: OR-fold of level + dc arrays.
let mut above: u32 = 0;
let mut left: u32 = 0;
for i in 0..w4 {
if x4 + i < max_x4 {
above |= self.above_level_context[above_base + x4 + i] as u32;
above |= self.above_dc_context[above_base + x4 + i] as u32;
}
}
for i in 0..h4 {
if y4 + i < max_y4 {
left |= self.left_level_context[left_base + y4 + i] as u32;
left |= self.left_dc_context[left_base + y4 + i] as u32;
}
}
let mut ctx = usize::from(above != 0) + usize::from(left != 0) + 7;
if bw * bh > w * h {
ctx += 3;
}
ctx
};
debug_assert!(ctx < TXB_SKIP_CONTEXTS);
Some(ctx)
}
/// §8.3.2 `dc_sign` ctx (av1-spec p.377) — the
/// `TileDcSignCdf[ ptype ][ ctx ]` second axis: the signed census
/// of `AboveDcContext` / `LeftDcContext` over the TU's footprint
/// (`1` decrements, `2` increments), reduced to `1` (negative
/// majority) / `2` (positive majority) / `0` (tie).
///
/// Parameter semantics mirror [`Self::txb_skip_ctx`] (no
/// `mi_size` — the `dc_sign` body never consults the block size).
/// Out-of-range `plane` / `tx_sz` return `0` rather than `None`
/// (the downstream CDF lookup re-guards), keeping the happy-path
/// call sites unwrap-free.
#[must_use]
pub fn dc_sign_ctx(
&self,
plane: u8,
tx_sz: usize,
x4: u32,
y4: u32,
sub_x: u8,
sub_y: u8,
) -> usize {
if plane > 2 || tx_sz >= TX_SIZES_ALL {
return 0;
}
let (max_x4, max_y4) = if plane > 0 {
(
(self.mi_cols >> sub_x) as usize,
(self.mi_rows >> sub_y) as usize,
)
} else {
(self.mi_cols as usize, self.mi_rows as usize)
};
let w4 = TX_WIDTH[tx_sz] >> 2;
let h4 = TX_HEIGHT[tx_sz] >> 2;
let p = plane as usize;
let above_base = p * self.mi_cols as usize;
let left_base = p * self.mi_rows as usize;
let x4 = x4 as usize;
let y4 = y4 as usize;
let mut dc_sign: i32 = 0;
for k in 0..w4 {
if x4 + k < max_x4 {
match self.above_dc_context[above_base + x4 + k] {
1 => dc_sign -= 1,
2 => dc_sign += 1,
_ => {}
}
}
}
for k in 0..h4 {
if y4 + k < max_y4 {
match self.left_dc_context[left_base + y4 + k] {
1 => dc_sign -= 1,
2 => dc_sign += 1,
_ => {}
}
}
}
match dc_sign.cmp(&0) {
core::cmp::Ordering::Less => 1,
core::cmp::Ordering::Greater => 2,
core::cmp::Ordering::Equal => 0,
}
}
/// §5.11.39 tail stamps (av1-spec p.91) — after every `coeffs()`
/// call (gate-open or gate-closed) the per-TU `culLevel` /
/// `dcCategory` overwrite the `Above*Context[ plane ][ x4 + i ]`
/// columns (`i in 0..w4`) and `Left*Context[ plane ][ y4 + i ]`
/// rows (`i in 0..h4`). Stamps past the per-plane array extent
/// are clipped (the §8.3.2 readers never consult cells at or
/// beyond `maxX4` / `maxY4`).
pub fn stamp_txb_level_context(
&mut self,
plane: u8,
tx_sz: usize,
x4: u32,
y4: u32,
cul_level: u8,
dc_category: u8,
) {
if plane > 2 || tx_sz >= TX_SIZES_ALL {
return;
}
let w4 = TX_WIDTH[tx_sz] >> 2;
let h4 = TX_HEIGHT[tx_sz] >> 2;
let p = plane as usize;
let above_base = p * self.mi_cols as usize;
let left_base = p * self.mi_rows as usize;
let x4 = x4 as usize;
let y4 = y4 as usize;
for i in 0..w4 {
if x4 + i < self.mi_cols as usize {
self.above_level_context[above_base + x4 + i] = cul_level;
self.above_dc_context[above_base + x4 + i] = dc_category;
}
}
for i in 0..h4 {
if y4 + i < self.mi_rows as usize {
self.left_level_context[left_base + y4 + i] = cul_level;
self.left_dc_context[left_base + y4 + i] = dc_category;
}
}
}
/// §5.11.42 `reset_block_context( bw4, bh4 )` (av1-spec p.92) —
/// zeroes the `Above{Level,Dc}Context` columns and
/// `Left{Level,Dc}Context` rows covering a `skip == 1` block's
/// footprint, per plane (`1 + 2 * HasChroma` planes; chroma
/// indices halved per the plane's subsampling). The §5.11.5
/// `decode_block()` body invokes this right after
/// `read_block_tx_size()` when `skip` is set.
#[allow(clippy::too_many_arguments)]
pub fn reset_txb_block_context(
&mut self,
mi_row: u32,
mi_col: u32,
bw4: u32,
bh4: u32,
has_chroma: bool,
subsampling_x: u8,
subsampling_y: u8,
) {
let num_planes: u8 = if has_chroma { 3 } else { 1 };
for plane in 0..num_planes {
let sub_x = if plane > 0 { subsampling_x } else { 0 };
let sub_y = if plane > 0 { subsampling_y } else { 0 };
let above_base = plane as usize * self.mi_cols as usize;
let left_base = plane as usize * self.mi_rows as usize;
// §5.11.42: `for i = MiCol >> subX .. (MiCol + bw4) >> subX`.
for i in ((mi_col >> sub_x) as usize)..(((mi_col + bw4) >> sub_x) as usize) {
if i < self.mi_cols as usize {
self.above_level_context[above_base + i] = 0;
self.above_dc_context[above_base + i] = 0;
}
}
for i in ((mi_row >> sub_y) as usize)..(((mi_row + bh4) >> sub_y) as usize) {
if i < self.mi_rows as usize {
self.left_level_context[left_base + i] = 0;
self.left_dc_context[left_base + i] = 0;
}
}
}
}
/// Helper to read `MiSizes[ r ][ c ]`. Returns [`BLOCK_INVALID`]
/// for out-of-grid coordinates (the §5.11.4 walker treats those
/// as not-yet-decoded).
#[inline]
fn mi_size_at(&self, r: i32, c: i32) -> usize {
if r < 0 || c < 0 {
return BLOCK_INVALID;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return BLOCK_INVALID;
}
self.mi_sizes[(r * self.mi_cols + c) as usize]
}
/// View of the [`DecodedBlockRecord`] leaves the walker has
/// emitted, in §5.11.4 syntax order.
#[must_use]
pub fn blocks(&self) -> &[DecodedBlockRecord] {
&self.blocks
}
/// Take ownership of the emitted leaves, draining the internal
/// buffer.
pub fn take_blocks(&mut self) -> Vec<DecodedBlockRecord> {
core::mem::take(&mut self.blocks)
}
/// `decode_block( r, c, subSize )` per §5.11.5 — for the §5.11.4
/// walker, this is the leaf emission point. The walker:
///
/// 1. Pushes a [`DecodedBlockRecord`] onto [`Self::blocks`].
/// 2. Fills the `MiSizes[ r..r+bh4 ][ c..c+bw4 ]` footprint with
/// `sub_size` per the §6.10.4 grid-fill rule.
///
/// Clipped at the frame's `MiRows` / `MiCols` extent so leaves
/// straddling the bottom or right edge fill only the in-grid
/// portion of their footprint.
fn decode_block(&mut self, r: u32, c: u32, sub_size: usize) {
debug_assert!(sub_size < BLOCK_SIZES, "leaf sub_size must be valid");
self.blocks.push(DecodedBlockRecord {
mi_row: r,
mi_col: c,
sub_size,
});
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = r + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = c + dc;
if cc >= self.mi_cols {
break;
}
self.mi_sizes[(rr * self.mi_cols + cc) as usize] = sub_size;
}
}
}
/// §8.3.2 `partition` context derivation: the `above` / `left`
/// booleans for [`partition_ctx`] are
/// `AvailU && (Mi_Width_Log2[ MiSizes[r-1][c] ] < bsl)` /
/// `AvailL && (Mi_Height_Log2[ MiSizes[r][c-1] ] < bsl)` per
/// av1-spec p.362. An out-of-grid or unfilled neighbour contributes
/// `false` to either term.
fn partition_ctx_for(&self, r: u32, c: u32, bsl: u32) -> usize {
let avail_u = self.geometry.is_inside(r as i32 - 1, c as i32);
let avail_l = self.geometry.is_inside(r as i32, c as i32 - 1);
let above = if avail_u {
let nb = self.mi_size_at(r as i32 - 1, c as i32);
nb < BLOCK_SIZES && (MI_WIDTH_LOG2[nb] as u32) < bsl
} else {
false
};
let left = if avail_l {
let nb = self.mi_size_at(r as i32, c as i32 - 1);
nb < BLOCK_SIZES && (MI_HEIGHT_LOG2[nb] as u32) < bsl
} else {
false
};
partition_ctx(above, left)
}
/// `decode_partition( r, c, bSize )` — §5.11.4 (av1-spec p.61-62)
/// recursive partition-tree walker.
///
/// Reads the §8.3.2 `partition` / `split_or_horz` / `split_or_vert`
/// symbols from `decoder` against the per-block-size [`TileCdfContext`]
/// partition CDFs, derives `subSize` and `splitSize` via
/// [`partition_subsize`], and either emits a [`DecodedBlockRecord`]
/// leaf (for `PARTITION_NONE`, the orthogonal-block partitions
/// `HORZ` / `VERT`, the asymmetric `HORZ_A` / `HORZ_B` / `VERT_A`
/// / `VERT_B`, and the `HORZ_4` / `VERT_4` quarter-splits) or
/// recurses into four `subSize` quadrants (for `PARTITION_SPLIT`).
///
/// `bSize < BLOCK_8X8` short-circuits to `PARTITION_NONE` with no
/// symbol read per the §5.11.4 first conditional. The
/// edge-of-frame fall-through paths (`!hasRows && !hasCols`
/// implies `PARTITION_SPLIT`, `hasCols` alone reads
/// `split_or_horz`, `hasRows` alone reads `split_or_vert`) match
/// the spec literally. The §5.11.4 `r >= MiRows || c >= MiCols`
/// early return is honoured.
///
/// Returns [`Error::PartitionWalkOutOfRange`] if a recursive call
/// is invoked with `bSize >= BLOCK_SIZES` (a caller bug — the
/// public entry is gated on a valid `b_size`).
pub fn decode_partition(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
r: u32,
c: u32,
b_size: usize,
) -> Result<(), crate::Error> {
// §5.11.4 line 1: `if (r >= MiRows || c >= MiCols) return 0`.
if r >= self.mi_rows || c >= self.mi_cols {
return Ok(());
}
if b_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let num4x4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let half_block4x4 = num4x4 >> 1;
let quarter_block4x4 = half_block4x4 >> 1;
let has_rows = (r + half_block4x4) < self.mi_rows;
let has_cols = (c + half_block4x4) < self.mi_cols;
// §5.11.4 partition decode.
let partition = if b_size < BLOCK_8X8 {
PARTITION_NONE
} else {
let bsl = MI_WIDTH_LOG2[b_size] as u32;
let pctx = self.partition_ctx_for(r, c, bsl);
if has_rows && has_cols {
let cdf = cdfs
.partition_cdf(bsl, pctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? as usize
} else if has_cols {
// §8.3.2 split_or_horz: derive the binary CDF from the
// already-selected partition row.
let cdf_row = cdfs
.partition_cdf(bsl, pctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let mut bin = split_or_horz_cdf(cdf_row, b_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let s = decoder.read_symbol(&mut bin)?;
if s == 0 {
PARTITION_HORZ
} else {
PARTITION_SPLIT
}
} else if has_rows {
let cdf_row = cdfs
.partition_cdf(bsl, pctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let mut bin = split_or_vert_cdf(cdf_row, b_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let s = decoder.read_symbol(&mut bin)?;
if s == 0 {
PARTITION_VERT
} else {
PARTITION_SPLIT
}
} else {
PARTITION_SPLIT
}
};
// §5.11.4 subSize / splitSize lookup via the r150 accessor.
let sub_size =
partition_subsize(partition, b_size).ok_or(crate::Error::PartitionWalkOutOfRange)?;
let split_size = partition_subsize(PARTITION_SPLIT, b_size);
// §5.11.4 per-partition dispatch.
match partition {
PARTITION_NONE => {
self.decode_block(r, c, sub_size);
}
PARTITION_HORZ => {
self.decode_block(r, c, sub_size);
if has_rows {
self.decode_block(r + half_block4x4, c, sub_size);
}
}
PARTITION_VERT => {
self.decode_block(r, c, sub_size);
if has_cols {
self.decode_block(r, c + half_block4x4, sub_size);
}
}
PARTITION_SPLIT => {
self.decode_partition(decoder, cdfs, r, c, sub_size)?;
self.decode_partition(decoder, cdfs, r, c + half_block4x4, sub_size)?;
self.decode_partition(decoder, cdfs, r + half_block4x4, c, sub_size)?;
self.decode_partition(
decoder,
cdfs,
r + half_block4x4,
c + half_block4x4,
sub_size,
)?;
}
PARTITION_HORZ_A => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
self.decode_block(r, c, split);
self.decode_block(r, c + half_block4x4, split);
self.decode_block(r + half_block4x4, c, sub_size);
}
PARTITION_HORZ_B => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
self.decode_block(r, c, sub_size);
self.decode_block(r + half_block4x4, c, split);
self.decode_block(r + half_block4x4, c + half_block4x4, split);
}
PARTITION_VERT_A => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
self.decode_block(r, c, split);
self.decode_block(r + half_block4x4, c, split);
self.decode_block(r, c + half_block4x4, sub_size);
}
PARTITION_VERT_B => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
self.decode_block(r, c, sub_size);
self.decode_block(r, c + half_block4x4, split);
self.decode_block(r + half_block4x4, c + half_block4x4, split);
}
// PARTITION_HORZ_4: the spec writes `r +
// quarterBlock4x4 * { 0, 1, 2, 3 }`; clippy folds the
// `* 0` and `* 1` cases, so the body is shown with the
// folded forms.
PARTITION_HORZ_4 => {
self.decode_block(r, c, sub_size);
self.decode_block(r + quarter_block4x4, c, sub_size);
self.decode_block(r + quarter_block4x4 * 2, c, sub_size);
if r + quarter_block4x4 * 3 < self.mi_rows {
self.decode_block(r + quarter_block4x4 * 3, c, sub_size);
}
}
// The spec's `else { ... }` arm (PARTITION_VERT_4 — the
// tenth and final partition value). Same clippy-folded
// pattern as PARTITION_HORZ_4.
PARTITION_VERT_4 => {
self.decode_block(r, c, sub_size);
self.decode_block(r, c + quarter_block4x4, sub_size);
self.decode_block(r, c + quarter_block4x4 * 2, sub_size);
if c + quarter_block4x4 * 3 < self.mi_cols {
self.decode_block(r, c + quarter_block4x4 * 3, sub_size);
}
}
_ => return Err(crate::Error::PartitionWalkOutOfRange),
}
Ok(())
}
/// `read_skip()` per §5.11.11 (av1-spec p.65) — reads the per-block
/// `skip` syntax element at the leaf `(mi_row, mi_col)` of a block
/// whose §5.11.5 `MiSize` is `sub_size`.
///
/// The spec body (av1-spec p.65) reads:
///
/// ```text
/// read_skip() {
/// if ( SegIdPreSkip && seg_feature_active( SEG_LVL_SKIP ) ) {
/// skip = 1
/// } else {
/// skip S()
/// }
/// }
/// ```
///
/// The caller passes the combined precondition `seg_skip_active =
/// SegIdPreSkip && seg_feature_active(SEG_LVL_SKIP)` (computed
/// from the §5.9.14 segmentation state owned by the frame parser —
/// the walker itself doesn't track segmentation, since the
/// segment id is per-block and read by a sibling §5.11.9
/// `intra_segment_id()` call). When `seg_skip_active` is `true`,
/// the spec short-circuits to `skip = 1` with no symbol read.
///
/// Otherwise the §8.3.2 `skip` context is computed as
/// `ctx = AvailU * Skips[MiRow-1][MiCol] + AvailL *
/// Skips[MiRow][MiCol-1]` (av1-spec p.378) — driven through the
/// walker's [`Self::skips`] grid and the [`TileGeometry`]'s
/// `AvailU` / `AvailL` predicates — and a `S()` symbol is decoded
/// against `TileSkipCdf[ctx]` (the `skip_cdf(ctx)` accessor).
///
/// The decoded `skip` value is then stamped over the block's
/// `bw4 * bh4` footprint of [`Self::skips`] per the §5.11.5
/// per-block invariant
///
/// ```text
/// for ( y = 0; y < bh4; y++ )
/// for ( x = 0; x < bw4; x++ )
/// Skips[ r + y ][ c + x ] = skip
/// ```
///
/// (av1-spec p.65, the §5.11.5 footer covering `Skips[]` /
/// `MiSizes[]` / etc.) so the next block's §8.3.2 skip-ctx walk
/// observes the value.
///
/// Returns the decoded `skip` value (0 or 1) on success, or
/// [`Error::PartitionWalkOutOfRange`] for caller bugs (out-of-range
/// `sub_size`, `mi_row` / `mi_col` outside the frame's mi extent).
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
pub fn decode_skip(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
seg_skip_active: bool,
) -> Result<u8, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.11 first conditional: SegIdPreSkip && seg_feature_active(
// SEG_LVL_SKIP ) ⇒ skip = 1, no symbol read.
let skip = if seg_skip_active {
1u8
} else {
// §8.3.2 skip ctx derivation (av1-spec p.378):
// ctx = 0
// if ( AvailU ) ctx += Skips[ MiRow - 1 ][ MiCol ]
// if ( AvailL ) ctx += Skips[ MiRow ][ MiCol - 1 ]
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let above_skip = if avail_u {
self.skip_at(mi_row as i32 - 1, mi_col as i32)
} else {
0
};
let left_skip = if avail_l {
self.skip_at(mi_row as i32, mi_col as i32 - 1)
} else {
0
};
let ctx = skip_ctx(above_skip, left_skip);
// skip_ctx is bounded by 0..=2 < SKIP_CONTEXTS (3); the
// `skip_cdf` accessor is total over that range.
let cdf = cdfs.skip_cdf(ctx);
// §8.2.6 S(): a 2-symbol read against `Default_Skip_Cdf`
// (length 3 including the counter slot) returns 0 or 1.
let sym = decoder.read_symbol(cdf)? as u8;
debug_assert!(sym <= 1, "S() over Default_Skip_Cdf yields 0 or 1");
sym
};
// §5.11.5 grid-fill: stamp `skip` over the block's bh4 * bw4
// footprint, clipped at the frame's MiRows / MiCols extent so
// a leaf straddling the bottom or right edge fills only the
// in-grid portion.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.skips[(rr * self.mi_cols + cc) as usize] = skip;
}
}
Ok(skip)
}
/// `read_skip_mode()` per §5.11.10 (av1-spec p.67) — reads the
/// per-block `skip_mode` syntax element at the leaf
/// `(mi_row, mi_col)` of a block whose §5.11.5 `MiSize` is
/// `sub_size`. `skip_mode` is the inter-frame "compound-reference
/// shortcut" flag read in [`inter_frame_mode_info`] (§5.11.18)
/// before the rest of the inter mode decode; it is never read for
/// intra-only frames.
///
/// The spec body (av1-spec p.67) reads:
///
/// ```text
/// read_skip_mode() {
/// if ( seg_feature_active( SEG_LVL_SKIP ) ||
/// seg_feature_active( SEG_LVL_REF_FRAME ) ||
/// seg_feature_active( SEG_LVL_GLOBALMV ) ||
/// !skip_mode_present ||
/// Block_Width[ MiSize ] < 8 ||
/// Block_Height[ MiSize ] < 8 ) {
/// skip_mode = 0
/// } else {
/// skip_mode S()
/// }
/// }
/// ```
///
/// The caller passes the combined precondition
/// `seg_skip_mode_off = seg_feature_active(SEG_LVL_SKIP) ||
/// seg_feature_active(SEG_LVL_REF_FRAME) ||
/// seg_feature_active(SEG_LVL_GLOBALMV)` (computed from the
/// §5.9.14 segmentation state owned by the frame parser — the
/// walker stays segmentation-state free) and the frame-header
/// scalar `skip_mode_present` (§5.9.21, av1-spec p.41). The
/// `Block_Width[MiSize] < 8 || Block_Height[MiSize] < 8`
/// short-circuit is derived locally from the `sub_size` argument
/// via the §9.3 [`block_width`] / [`block_height`] tables.
///
/// Otherwise the §8.3.2 `skip_mode` context is computed as
/// `ctx = AvailU * SkipModes[MiRow-1][MiCol] + AvailL *
/// SkipModes[MiRow][MiCol-1]` (av1-spec p.378) — driven through
/// the walker's [`Self::skip_modes`] grid and the
/// [`TileGeometry`]'s `AvailU` / `AvailL` predicates via
/// [`skip_mode_ctx`] — and a `S()` symbol is decoded against
/// `TileSkipModeCdf[ctx]` (the `skip_mode_cdf(ctx)` accessor).
///
/// The decoded `skip_mode` value is then stamped over the block's
/// `bw4 * bh4` footprint of [`Self::skip_modes`] per the §5.11.5
/// per-block invariant
///
/// ```text
/// for ( y = 0; y < bh4; y++ )
/// for ( x = 0; x < bw4; x++ )
/// SkipModes[ r + y ][ c + x ] = skip_mode
/// ```
///
/// (av1-spec p.65, the §5.11.5 footer covering `SkipModes[]` /
/// `Skips[]` / `MiSizes[]` / etc.) so the next block's §8.3.2
/// skip-mode-ctx walk observes the value.
///
/// Returns the decoded `skip_mode` value (0 or 1) on success, or
/// [`Error::PartitionWalkOutOfRange`] for caller bugs
/// (out-of-range `sub_size`, `mi_row` / `mi_col` outside the
/// frame's mi extent).
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
#[allow(clippy::too_many_arguments)]
pub fn decode_skip_mode(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
seg_skip_mode_off: bool,
skip_mode_present: bool,
) -> Result<u8, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.10 short-circuit set (any-true ⇒ skip_mode = 0, no
// symbol read):
// * seg_feature_active( SEG_LVL_SKIP / REF_FRAME / GLOBALMV )
// — collapsed into the caller-provided `seg_skip_mode_off`.
// * !skip_mode_present — the §5.9.21 frame-header scalar
// gating skip-mode for the whole frame.
// * Block_Width[ MiSize ] < 8 || Block_Height[ MiSize ] < 8 —
// derived locally from `sub_size` via the §9.3 tables.
let bw = block_width(sub_size);
let bh = block_height(sub_size);
let small_block = bw < 8 || bh < 8;
let skip_mode = if seg_skip_mode_off || !skip_mode_present || small_block {
0u8
} else {
// §8.3.2 skip_mode ctx derivation (av1-spec p.378):
// ctx = 0
// if ( AvailU ) ctx += SkipModes[ MiRow - 1 ][ MiCol ]
// if ( AvailL ) ctx += SkipModes[ MiRow ][ MiCol - 1 ]
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let above_sm = if avail_u {
self.skip_mode_at(mi_row as i32 - 1, mi_col as i32)
} else {
0
};
let left_sm = if avail_l {
self.skip_mode_at(mi_row as i32, mi_col as i32 - 1)
} else {
0
};
let ctx = skip_mode_ctx(above_sm, left_sm);
// skip_mode_ctx is bounded by 0..=2 < SKIP_MODE_CONTEXTS
// (3); the `skip_mode_cdf` accessor is total over that
// range.
let cdf = cdfs.skip_mode_cdf(ctx);
// §8.2.6 S(): a 2-symbol read against
// `Default_Skip_Mode_Cdf` (length 3 including the counter
// slot) returns 0 or 1.
let sym = decoder.read_symbol(cdf)? as u8;
debug_assert!(sym <= 1, "S() over Default_Skip_Mode_Cdf yields 0 or 1");
sym
};
// §5.11.5 grid-fill: stamp `skip_mode` over the block's bh4 *
// bw4 footprint, clipped at the frame's MiRows / MiCols
// extent so a leaf straddling the bottom or right edge fills
// only the in-grid portion.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.skip_modes[(rr * self.mi_cols + cc) as usize] = skip_mode;
}
}
Ok(skip_mode)
}
/// `read_is_inter()` per §5.11.20 (av1-spec p.71-72) — reads the
/// per-block `is_inter` syntax element at the leaf
/// `(mi_row, mi_col)` of a block whose §5.11.5 `MiSize` is
/// `sub_size`. `is_inter` selects between
/// [§5.11.22 `intra_block_mode_info`] and
/// [§5.11.23 `inter_block_mode_info`] inside the §5.11.18
/// `inter_frame_mode_info` outer dispatch; it is never read for
/// intra-only frames (those go through §5.11.7 directly).
///
/// The spec body (av1-spec p.71-72) reads:
///
/// ```text
/// read_is_inter() {
/// if ( skip_mode ) {
/// is_inter = 1
/// } else if ( seg_feature_active( SEG_LVL_REF_FRAME ) ) {
/// is_inter = FeatureData[ segment_id ][ SEG_LVL_REF_FRAME ] != INTRA_FRAME
/// } else if ( seg_feature_active( SEG_LVL_GLOBALMV ) ) {
/// is_inter = 1
/// } else {
/// is_inter S()
/// }
/// }
/// ```
///
/// The four arms are ordered (only the first matching arm fires):
///
/// 1. `skip_mode` (the §5.11.10 result the caller has already
/// decoded for this block) forces `is_inter = 1` — a
/// compound-reference skip block is by definition inter.
/// 2. `seg_ref_frame_active` (the §5.9.14 segmentation override
/// `seg_feature_active( SEG_LVL_REF_FRAME )` evaluated against
/// the block's `segment_id`) routes through the per-segment
/// `FeatureData[ segment_id ][ SEG_LVL_REF_FRAME ]` value:
/// `INTRA_FRAME` (value `0` per §3) ⇒ intra, anything else ⇒
/// inter. The caller pre-computes this comparison into the
/// `seg_ref_frame_is_inter` boolean so the walker stays
/// segmentation-state free.
/// 3. `seg_globalmv_active` (the §5.9.14 segmentation override
/// `seg_feature_active( SEG_LVL_GLOBALMV )` — a boolean
/// feature with no data, just an enabled bit) forces
/// `is_inter = 1` (global-MV is intrinsically an inter mode).
/// 4. Fall-through `S()` symbol read against
/// `TileIsInterCdf[ ctx ]` per §8.3.2 (av1-spec p.365) with
/// `ctx` derived from neighbour intra-ness via the existing
/// [`is_inter_ctx`] helper.
///
/// The §8.3.2 ctx derivation is:
///
/// ```text
/// if ( AvailU && AvailL )
/// ctx = (LeftIntra && AboveIntra) ? 3 : LeftIntra || AboveIntra
/// else if ( AvailU || AvailL )
/// ctx = 2 * (AvailU ? AboveIntra : LeftIntra)
/// else
/// ctx = 0
/// ```
///
/// Per §5.11.18 the neighbour intra-ness is sampled from
/// `LeftRefFrame[0] / AboveRefFrame[0]`:
/// `LeftIntra = LeftRefFrame[0] <= INTRA_FRAME`. The walker's
/// `IsInters[]` grid stores `is_inter` (0 / 1), so `LeftIntra`
/// is the complement: `!is_inter_at(...)`. Per §5.11.18
/// `LeftRefFrame[0] = AvailL ? RefFrames[..][0] : INTRA_FRAME`,
/// so an unavailable neighbour is treated as intra — exactly
/// the `None` arm of [`is_inter_ctx`].
///
/// The decoded `is_inter` value is then stamped over the block's
/// `bw4 * bh4` footprint of [`Self::is_inters`] per the §5.11.5
/// per-block invariant
///
/// ```text
/// for ( y = 0; y < bh4; y++ )
/// for ( x = 0; x < bw4; x++ )
/// IsInters[ r + y ][ c + x ] = is_inter
/// ```
///
/// (av1-spec p.65, the §5.11.5 footer covering `IsInters[]` /
/// `SkipModes[]` / `Skips[]` / `MiSizes[]` / etc.) so the next
/// block's §8.3.2 is-inter-ctx walk observes the value.
///
/// Returns the decoded `is_inter` value (0 = intra block, 1 =
/// inter block) on success, or [`Error::PartitionWalkOutOfRange`]
/// for caller bugs (out-of-range `sub_size`, `mi_row` / `mi_col`
/// outside the frame's mi extent).
///
/// [§5.11.22 `intra_block_mode_info`]: https://aomediacodec.github.io/av1-spec/av1-spec.pdf
/// [§5.11.23 `inter_block_mode_info`]: https://aomediacodec.github.io/av1-spec/av1-spec.pdf
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
#[allow(clippy::too_many_arguments)]
pub fn decode_is_inter(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip_mode: u8,
seg_ref_frame_active: bool,
seg_ref_frame_is_inter: bool,
seg_globalmv_active: bool,
) -> Result<u8, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.20 four-arm dispatch — first match fires. The three
// short-circuit arms read zero bits; only the fall-through
// `else` reads an `S()` symbol against `TileIsInterCdf[ctx]`.
let is_inter: u8 = if skip_mode != 0 {
// Arm 1: `if ( skip_mode ) is_inter = 1`.
1
} else if seg_ref_frame_active {
// Arm 2: `is_inter = FeatureData[segment_id][SEG_LVL_REF_FRAME] != INTRA_FRAME`.
// Caller pre-computed the !=INTRA_FRAME comparison.
seg_ref_frame_is_inter as u8
} else if seg_globalmv_active {
// Arm 3: `if ( seg_feature_active( SEG_LVL_GLOBALMV ) ) is_inter = 1`.
1
} else {
// Arm 4: `is_inter S()` — fall-through symbol read.
//
// §8.3.2 is_inter ctx derivation: the neighbour
// `LeftIntra` / `AboveIntra` predicates per §5.11.18 are
// the complement of `IsInters[]` (intra ≡ !is_inter), and
// an unavailable neighbour is treated as intra per
// §5.11.18 (`LeftRefFrame[0] = AvailL ? ... :
// INTRA_FRAME`) — i.e. `None` to [`is_inter_ctx`].
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let above_intra: Option<bool> = if avail_u {
Some(self.is_inter_at(mi_row as i32 - 1, mi_col as i32) == 0)
} else {
None
};
let left_intra: Option<bool> = if avail_l {
Some(self.is_inter_at(mi_row as i32, mi_col as i32 - 1) == 0)
} else {
None
};
let ctx = is_inter_ctx(above_intra, left_intra);
// is_inter_ctx returns 0..=3 < IS_INTER_CONTEXTS (4); the
// `is_inter_cdf` accessor is total over that range.
let cdf = cdfs.is_inter_cdf(ctx);
// §8.2.6 S(): a 2-symbol read against
// `Default_Is_Inter_Cdf` (length 3 including the counter
// slot) returns 0 or 1.
let sym = decoder.read_symbol(cdf)? as u8;
debug_assert!(sym <= 1, "S() over Default_Is_Inter_Cdf yields 0 or 1");
sym
};
// §5.11.5 grid-fill: stamp `is_inter` over the block's bh4 *
// bw4 footprint, clipped at the frame's MiRows / MiCols
// extent so a leaf straddling the bottom or right edge fills
// only the in-grid portion.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.is_inters[(rr * self.mi_cols + cc) as usize] = is_inter;
}
}
Ok(is_inter)
}
/// `read_segment_id()` per §5.11.9 (av1-spec p.66) — reads the
/// per-block `segment_id` syntax element at the leaf
/// `(mi_row, mi_col)` of a block whose §5.11.5 `MiSize` is
/// `sub_size`. `segment_id` is the §6.10.4 per-block index into the
/// frame's eight segment slots; it controls the §5.11.18
/// `Lossless = LosslessArray[ segment_id ]` lookup and feeds the
/// `FeatureData[ segment_id ][ * ]` indexing the §5.11.20
/// [`Self::decode_is_inter`] dispatch already consumes via its
/// caller-pre-computed `seg_ref_frame_is_inter` boolean.
///
/// The spec body (av1-spec p.66) reads:
///
/// ```text
/// read_segment_id( ) {
/// if ( AvailU && AvailL )
/// prevUL = SegmentIds[ MiRow - 1 ][ MiCol - 1 ]
/// else
/// prevUL = -1
/// if ( AvailU )
/// prevU = SegmentIds[ MiRow - 1 ][ MiCol ]
/// else
/// prevU = -1
/// if ( AvailL )
/// prevL = SegmentIds[ MiRow ][ MiCol - 1 ]
/// else
/// prevL = -1
/// if ( prevU == -1 )
/// pred = (prevL == -1) ? 0 : prevL
/// else if ( prevL == -1 )
/// pred = prevU
/// else
/// pred = (prevUL == prevU) ? prevU : prevL
/// if ( skip ) {
/// segment_id = pred
/// } else {
/// segment_id S()
/// segment_id = neg_deinterleave( segment_id, pred,
/// LastActiveSegId + 1 )
/// }
/// }
/// ```
///
/// `last_active_seg_id` is the §5.9.14 trailing derivation
/// (`FeatureEnabled[i][.]` reduction the
/// [`uncompressed_header_tail`] parse already lifts into
/// [`SegmentationParams::last_active_seg_id`]) — the caller passes
/// it as a `u8 ∈ 0..MAX_SEGMENTS = 0..8`. `skip` is the
/// `Skips[r][c]` value the §5.11.11 [`Self::decode_skip`] just
/// stamped (the caller has it in scope from that call's return);
/// the §5.11.9 `skip ⇒ segment_id = pred` short-circuit reads zero
/// bits, matching the spec's "spatially predicted on a skipped
/// block" semantics. The §8.3.2 ctx for the non-skip `S()` is
/// `segment_id_ctx(prev_ul, prev_u, prev_l)`; the helper already
/// honours the `-1` sentinels.
///
/// The §5.11.5 grid-fill stamps the decoded `segment_id` over the
/// block's `bw4 * bh4` footprint, clipped at the frame's `MiRows`
/// / `MiCols` extent so a leaf straddling the bottom or right
/// edge fills only the in-grid portion of its footprint. The
/// neighbour-lookup reads happen *before* the grid-fill, so a
/// non-square block with `(mi_row, mi_col)` past the block's own
/// top-left corner still sees only previously-decoded neighbours.
///
/// Returns the decoded `segment_id ∈ 0..=last_active_seg_id` on
/// success, or [`Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (out-of-range `sub_size`, `mi_row` / `mi_col`
/// outside the frame's mi extent, or `last_active_seg_id >=
/// MAX_SEGMENTS`). [`Error::SegmentIdOutOfRange`] rejects a
/// malformed stream whose `S()` symbol exceeds
/// `last_active_seg_id` (the §6.10.8 conformance requirement on
/// the postprocessed id, checked on the raw symbol via the
/// `neg_deinterleave` bijection). [`Error::UnexpectedEnd`] /
/// [`Error::SymbolExitUnderflow`] surface bitstream errors.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::SegmentIdOutOfRange`]: crate::Error::SegmentIdOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
/// [`Error::SymbolExitUnderflow`]: crate::Error::SymbolExitUnderflow
/// [`SegmentationParams::last_active_seg_id`]: crate::uncompressed_header_tail::SegmentationParams::last_active_seg_id
#[allow(clippy::too_many_arguments)]
pub fn decode_segment_id(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip: u8,
last_active_seg_id: u8,
) -> Result<u8, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.9 neighbour lookup. `prev_ul` requires *both* AvailU
// and AvailL (the diagonal neighbour is only meaningful when
// both edges are inside the tile per the spec's `AvailU &&
// AvailL` gate); `prev_u` and `prev_l` are each gated by their
// own edge.
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let prev_ul: i32 = if avail_u && avail_l {
self.segment_id_at(mi_row as i32 - 1, mi_col as i32 - 1)
} else {
-1
};
let prev_u: i32 = if avail_u {
self.segment_id_at(mi_row as i32 - 1, mi_col as i32)
} else {
-1
};
let prev_l: i32 = if avail_l {
self.segment_id_at(mi_row as i32, mi_col as i32 - 1)
} else {
-1
};
// §5.11.9 `pred` derivation. Transcribes the four-arm cascade
// literally. Two arms happen to return `prev_u` (the
// `prev_l == -1` arm and the `prev_ul == prev_u` arm) — the
// spec spells them out as separate branches because the
// predicates are semantically distinct (the former is
// "left neighbour unavailable", the latter is "above-left
// agrees with above"). Collapsing them would obscure the
// spec correspondence, so the clippy `if_same_then_else`
// lint is allowed on this block.
#[allow(clippy::if_same_then_else)]
let pred: i32 = if prev_u == -1 {
if prev_l == -1 {
0
} else {
prev_l
}
} else if prev_l == -1 {
prev_u
} else if prev_ul == prev_u {
prev_u
} else {
prev_l
};
// `pred` is in `0..=last_active_seg_id`: either it comes from
// a neighbour cell (whose stored value was in
// `0..=last_active_seg_id` from a prior call) or it falls
// through to `0` (the empty-neighbour seed). The
// [`neg_deinterleave`] `ref < max` precondition therefore
// holds for `max = last_active_seg_id + 1`.
debug_assert!(
pred >= 0 && (pred as u32) <= last_active_seg_id as u32,
"§5.11.9 pred is in 0..=last_active_seg_id"
);
// §5.11.9 dispatch — skip-block short-circuit vs. S() with
// neg_deinterleave decoding.
let segment_id: u8 = if skip != 0 {
// §5.11.9 `if ( skip ) segment_id = pred`. The neighbour
// cascade has already produced `pred`; no symbol read.
pred as u8
} else {
// §8.3.2 ctx derivation: feeds the three neighbour values
// (each `Some(v)` for an in-grid cell with v >= 0, `None`
// for the spec's `-1` sentinel) into [`segment_id_ctx`].
// The helper returns 0..=2 < SEGMENT_ID_CONTEXTS (3); the
// `segment_id_cdf` accessor is total over that range.
let to_opt = |v: i32| if v < 0 { None } else { Some(v) };
let ctx = segment_id_ctx(to_opt(prev_ul), to_opt(prev_u), to_opt(prev_l));
let cdf = cdfs.segment_id_cdf(ctx);
// §8.2.6 S(): the read against `Default_Segment_Id_Cdf`
// (length MAX_SEGMENTS + 1 = 9 including the counter slot)
// returns 0..=MAX_SEGMENTS-1 = 0..=7 — the full 8-symbol
// alphabet, regardless of the frame's
// `last_active_seg_id`.
let diff = decoder.read_symbol(cdf)?;
// §6.10.8 bitstream conformance: the postprocessed
// segment id (the `neg_deinterleave` return) must lie in
// `0..=LastActiveSegId`. Because `neg_deinterleave( diff,
// pred, max )` is a bijection on `0..max` for any fixed
// `pred < max` (with `max = last_active_seg_id + 1`),
// that requirement holds exactly when the raw symbol
// does; checking `diff` up front also keeps
// `neg_deinterleave` inside its documented `diff < max`
// domain (its `ref >= max - 1` arm computes
// `max - diff - 1`). A corrupt / adversarial stream can
// code `diff > last_active_seg_id` whenever
// `last_active_seg_id < 7` — reject it as a malformed
// stream rather than panicking.
if diff > last_active_seg_id as u32 {
return Err(crate::Error::SegmentIdOutOfRange);
}
let max = last_active_seg_id as u32 + 1;
let sid = neg_deinterleave(diff, pred as u32, max);
debug_assert!(sid < max, "§5.11.9 neg_deinterleave bijects onto 0..max");
sid as u8
};
// §5.11.5 grid-fill: stamp `segment_id` over the block's bh4 *
// bw4 footprint, clipped at the frame's MiRows / MiCols extent.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.segment_ids[(rr * self.mi_cols + cc) as usize] = segment_id as i32;
}
}
Ok(segment_id)
}
/// `intra_segment_id()` per §5.11.8 (av1-spec p.66) — the
/// intra-frame variant of the per-block segment-id read, called
/// from §5.11.7 `intra_frame_mode_info` (both the `SegIdPreSkip`
/// pre-skip arm and the `!SegIdPreSkip` post-skip arm route here).
/// Wraps the r159 [`Self::decode_segment_id`] under a
/// `segmentation_enabled` gate and resolves the `Lossless`
/// per-block boolean via the §6.8.2 `LosslessArray[ segment_id ]`
/// table.
///
/// The spec body (av1-spec p.66) reads:
///
/// ```text
/// intra_segment_id( ) {
/// if ( segmentation_enabled )
/// read_segment_id( )
/// else
/// segment_id = 0
/// Lossless = LosslessArray[ segment_id ]
/// }
/// ```
///
/// `segmentation_enabled` is the §5.9.14 frame-header bit (the
/// [`SegmentationParams::enabled`] field on the parsed segmentation
/// state). When clear, every block forces `segment_id = 0` and no
/// `S()` is read; when set, the call descends into r159
/// `decode_segment_id` with the caller-supplied `skip` (the
/// §5.11.11 `decode_skip` return) and `last_active_seg_id` (the
/// §5.9.14 trailing derivation) — that path stamps the grid and
/// reads at most one symbol per the §5.11.9 dispatch.
///
/// `lossless_array` is the §6.8.2 per-segment `LosslessArray[]`
/// table the uncompressed-header walk derives once per frame
/// (`LosslessArray[ segmentId ] = qindex == 0 && DeltaQYDc == 0 &&
/// DeltaQUAc == 0 && DeltaQUDc == 0 && DeltaQVAc == 0 && DeltaQVDc
/// == 0` per av1-spec p.42; `qindex = get_qindex(1, segmentId)`).
/// The full conjunction (`AllLossless = CodedLossless && (FrameWidth
/// == UpscaledWidth)`) is owned by the frame-header parse; this
/// method only consumes the eight-entry per-segment slice. The
/// caller-side derivation matches the pattern of r159's
/// `last_active_seg_id` argument (the walker stays
/// segmentation-state-free).
///
/// When `segmentation_enabled == false`, the §5.11.5 grid-fill
/// over the leaf's `bh4 * bw4` footprint stamps `segment_id = 0`
/// (matching the spec's `segment_id = 0` assignment) so subsequent
/// `decode_segment_id` neighbour lookups see a real zero rather
/// than the §5.11.9 `-1` sentinel. The `lossless_array[0]` slot is
/// the universal `Lossless` for the whole frame in that case (a
/// segmentation-disabled frame has only segment 0 in use).
///
/// Returns `(segment_id, lossless)` on success, where `segment_id ∈
/// 0..=last_active_seg_id` (or `0` when segmentation is disabled)
/// and `lossless == lossless_array[ segment_id as usize ]`. Returns
/// [`Error::PartitionWalkOutOfRange`] for caller-bug arguments
/// (out-of-range `sub_size`, `mi_row` / `mi_col` outside the
/// frame's mi extent, or `last_active_seg_id >= MAX_SEGMENTS`).
/// [`Error::UnexpectedEnd`] / [`Error::SymbolExitUnderflow`]
/// surface bitstream errors from the inner `decode_segment_id`
/// call.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
/// [`Error::SymbolExitUnderflow`]: crate::Error::SymbolExitUnderflow
/// [`SegmentationParams::enabled`]: crate::uncompressed_header_tail::SegmentationParams::enabled
#[allow(clippy::too_many_arguments)]
pub fn decode_intra_segment_id(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip: u8,
segmentation_enabled: bool,
last_active_seg_id: u8,
lossless_array: &[bool; MAX_SEGMENTS],
) -> Result<(u8, bool), crate::Error> {
// §5.11.8 range guards happen here even on the
// `!segmentation_enabled` branch (the spec still stamps the
// grid in that branch, so a bogus `mi_row` / `mi_col` /
// `sub_size` should fail symmetrically with the
// `segmentation_enabled == true` path). The `decode_segment_id`
// path re-checks them but performs the bitstream read first;
// checking up front keeps the no-symbol path total over the
// same input space.
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let segment_id: u8 = if segmentation_enabled {
// §5.11.8 `if ( segmentation_enabled ) read_segment_id( )`
// — descend into the r159 implementation, which performs
// the §5.11.9 neighbour cascade, the skip / non-skip
// dispatch (with the `S()` read against
// `TileSegmentIdCdf[ctx]` and the `neg_deinterleave`
// mapping), and the §5.11.5 grid-fill of the result.
self.decode_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
last_active_seg_id,
)?
} else {
// §5.11.8 else-branch: `segment_id = 0` (no bits read,
// every block on the frame shares the single segment
// slot). The §5.11.5 grid-fill still stamps the
// footprint so downstream §5.11.9 neighbour lookups see
// a real `0` rather than the `-1` sentinel — matching
// the spec's reading of `SegmentIds[ r + y ][ c + x ] =
// segment_id` regardless of how `segment_id` was
// derived. Without this, a later frame with
// `segmentation_enabled == true` (rare across
// intra-only streams but legal) would see stale `-1`
// sentinels in the same `PartitionWalker`'s neighbour
// history; in practice each frame instantiates a fresh
// walker, but the spec's grid-fill semantics are
// upheld either way.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.segment_ids[(rr * self.mi_cols + cc) as usize] = 0;
}
}
0
};
// §5.11.8 `Lossless = LosslessArray[ segment_id ]`. With
// `segment_id ∈ 0..MAX_SEGMENTS = 0..8` by construction
// (the §5.11.9 / spec range), the eight-entry table indexes
// total.
let lossless = lossless_array[segment_id as usize];
Ok((segment_id, lossless))
}
/// `inter_segment_id( preSkip )` per §5.11.19 (av1-spec p.71) — the
/// inter-frame per-block segment-id read, called twice from
/// §5.11.18 `inter_frame_mode_info`: once with `preSkip = 1` before
/// the §5.11.11 `read_skip()` call (the §5.9.14 `SegIdPreSkip == 1`
/// arm activates the early write) and once with `preSkip = 0`
/// after (the post-skip arm covers the `SegIdPreSkip == 0` case
/// and the temporal-update / skip-after-pre-skip update of the
/// segmentation-prediction context arrays).
///
/// The spec body (av1-spec p.71) reads:
///
/// ```text
/// inter_segment_id( preSkip ) {
/// if ( segmentation_enabled ) {
/// predictedSegmentId = get_segment_id( )
/// if ( segmentation_update_map ) {
/// if ( preSkip && !SegIdPreSkip ) {
/// segment_id = 0
/// return
/// }
/// if ( !preSkip ) {
/// if ( skip ) {
/// seg_id_predicted = 0
/// for ( i = 0; i < Num_4x4_Blocks_Wide[ MiSize ]; i++ )
/// AboveSegPredContext[ MiCol + i ] = seg_id_predicted
/// for ( i = 0; i < Num_4x4_Blocks_High[ MiSize ]; i++ )
/// LeftSegPredContext[ MiRow + i ] = seg_id_predicted
/// read_segment_id( )
/// return
/// }
/// }
/// if ( segmentation_temporal_update == 1 ) {
/// seg_id_predicted S()
/// if ( seg_id_predicted )
/// segment_id = predictedSegmentId
/// else
/// read_segment_id( )
/// for ( i = 0; i < Num_4x4_Blocks_Wide[ MiSize ]; i++ )
/// AboveSegPredContext[ MiCol + i ] = seg_id_predicted
/// for ( i = 0; i < Num_4x4_Blocks_High[ MiSize ]; i++ )
/// LeftSegPredContext[ MiRow + i ] = seg_id_predicted
/// } else {
/// read_segment_id( )
/// }
/// } else {
/// segment_id = predictedSegmentId
/// }
/// } else {
/// segment_id = 0
/// }
/// }
/// ```
///
/// `pre_skip` is the §5.11.18 caller-axis (`1` on the pre-skip
/// call, `0` on the post-skip call). `seg_id_pre_skip`,
/// `segmentation_enabled`, `segmentation_update_map`, and
/// `segmentation_temporal_update` are the §5.9.14 frame-header
/// fields surfaced through [`SegmentationParams`]. `skip` is the
/// §5.11.11 `decode_skip` return (ignored on the `pre_skip = 1`
/// arm — that arm fires before `read_skip` per §5.11.18, so the
/// caller passes `0`). `last_active_seg_id` is the §5.9.14
/// trailing derivation; `lossless_array` is the §6.8.2 per-segment
/// table.
///
/// `predictedSegmentId = get_segment_id()` per §5.11.21 reads the
/// previous-frame segmentation map. Because the walker stays
/// inter-frame-state-free (the current-frame `segment_ids[]` grid
/// is the only segmentation surface it owns), callers pass the
/// pre-computed `predicted_segment_id` they derived from the §6.10
/// reference-frame walk's `PrevSegmentIds[]` lookup over the
/// `Min(MiCols - MiCol, bw4)` × `Min(MiRows - MiRow, bh4)` window.
/// For the §5.11.19 `temporal_update == 1` `seg_id_predicted = 1`
/// arm this becomes the literal `segment_id` (no `S()` read for
/// the id itself); for the `seg_id_predicted = 0` arm the
/// `read_segment_id()` follow-on reads the literal id off the
/// bitstream via [`Self::decode_segment_id`].
///
/// Returns `(segment_id, lossless)` where `segment_id ∈
/// 0..=last_active_seg_id` (or `0` on the disabled / pre-skip
/// early-exit branches) and `lossless == lossless_array[ segment_id
/// as usize ]`. The [`Self::above_seg_pred_context`] /
/// [`Self::left_seg_pred_context`] arrays are re-stamped over the
/// block's `bw4` columns / `bh4` rows on the two arms that update
/// them (the `!preSkip && skip` arm and the
/// `temporal_update == 1` arm); the other arms leave the context
/// arrays untouched per the spec body.
///
/// Returns [`Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (out-of-range `sub_size`, `mi_row` / `mi_col` past
/// the frame's mi extent, `last_active_seg_id >= MAX_SEGMENTS`,
/// or `predicted_segment_id > last_active_seg_id`).
/// [`Error::UnexpectedEnd`] / [`Error::SymbolExitUnderflow`]
/// surface bitstream errors from the inner reads.
///
/// [`SegmentationParams`]: crate::uncompressed_header_tail::SegmentationParams
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
/// [`Error::SymbolExitUnderflow`]: crate::Error::SymbolExitUnderflow
#[allow(clippy::too_many_arguments)]
pub fn decode_inter_segment_id(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
pre_skip: bool,
skip: u8,
seg_id_pre_skip: bool,
segmentation_enabled: bool,
segmentation_update_map: bool,
segmentation_temporal_update: bool,
predicted_segment_id: u8,
last_active_seg_id: u8,
lossless_array: &[bool; MAX_SEGMENTS],
) -> Result<(u8, bool), crate::Error> {
// Range guards fire up-front on both arms so the no-symbol path
// is total over the same input space as the bitstream-reading
// path (matching the r160 `decode_intra_segment_id` pattern).
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if predicted_segment_id as u32 > last_active_seg_id as u32 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.19 outer `if ( segmentation_enabled )` gate. The
// disabled arm collapses to `segment_id = 0` with no bits
// read; stamp the grid for downstream §5.11.9 neighbour
// lookups (matching the §5.11.8 disabled-branch grid-fill).
if !segmentation_enabled {
self.stamp_segment_id_grid(mi_row, mi_col, sub_size, 0);
let lossless = lossless_array[0];
return Ok((0, lossless));
}
// §5.11.19 inner `if ( segmentation_update_map )` gate. The
// else-branch collapses to `segment_id = predictedSegmentId`
// (no bits read, no context updates). Stamp the grid the same
// way so neighbour lookups see the actual id, not the -1
// sentinel.
if !segmentation_update_map {
let sid = predicted_segment_id;
self.stamp_segment_id_grid(mi_row, mi_col, sub_size, sid as i32);
let lossless = lossless_array[sid as usize];
return Ok((sid, lossless));
}
// §5.11.19 `if ( preSkip && !SegIdPreSkip ) { segment_id = 0;
// return }`. The pre-skip caller fires this early-exit when
// the §5.9.14 `SegIdPreSkip` derivation says the segment-id
// should be deferred to the post-skip call. No bits read, no
// context updates, but the grid is still stamped so
// intervening §5.11.9 lookups see a real `0`.
if pre_skip && !seg_id_pre_skip {
self.stamp_segment_id_grid(mi_row, mi_col, sub_size, 0);
let lossless = lossless_array[0];
return Ok((0, lossless));
}
// §5.11.19 `if ( !preSkip ) { if ( skip ) { ... return } }` —
// the post-skip arm's skip-block branch. `seg_id_predicted` is
// forced to `0` (not read), the context arrays are stamped to
// `0`, then `read_segment_id()` reads the id off the
// bitstream (the §5.11.9 `skip` short-circuit fires inside
// because the caller-passed `skip == 1`, so `segment_id =
// pred` with no `S()` read).
if !pre_skip && skip != 0 {
self.stamp_seg_pred_context(mi_row, mi_col, sub_size, 0);
let sid = self.decode_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
last_active_seg_id,
)?;
let lossless = lossless_array[sid as usize];
return Ok((sid, lossless));
}
// §5.11.19 `if ( segmentation_temporal_update == 1 )` branch:
// read the binary `seg_id_predicted` symbol, then either
// adopt the predicted id (no further read) or fall through
// to `read_segment_id()`; in both cases, stamp the context
// arrays with the just-read flag.
if segmentation_temporal_update {
let ctx = self.seg_pred_ctx(mi_row, mi_col);
let cdf = cdfs.segment_id_predicted_cdf(ctx);
let seg_id_predicted = decoder.read_symbol(cdf)? as u8;
// The cdf is a binary symbol (3 slots: two cumulative + one
// counter); `read_symbol` returns 0 or 1.
debug_assert!(seg_id_predicted <= 1, "§5.11.19 seg_id_predicted is binary");
let sid: u8 = if seg_id_predicted != 0 {
// §5.11.19 `if ( seg_id_predicted ) segment_id =
// predictedSegmentId` — adopt the previous-frame id;
// still stamp the current-frame grid so neighbour
// lookups see the actual id.
self.stamp_segment_id_grid(mi_row, mi_col, sub_size, predicted_segment_id as i32);
predicted_segment_id
} else {
// §5.11.19 `else read_segment_id()` — descend into the
// r159 implementation, which stamps the grid itself.
// `skip` is the caller-passed value (0 on the pre-skip
// call; non-zero on the post-skip call short-circuits
// earlier, so here it's effectively 0).
self.decode_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
last_active_seg_id,
)?
};
self.stamp_seg_pred_context(mi_row, mi_col, sub_size, seg_id_predicted);
let lossless = lossless_array[sid as usize];
return Ok((sid, lossless));
}
// §5.11.19 `else read_segment_id()` — the
// `segmentation_temporal_update == 0` fall-through. The
// context arrays stay untouched (the spec writes them only
// on the two arms above), and the inner call stamps the
// segment-id grid.
let sid = self.decode_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
last_active_seg_id,
)?;
let lossless = lossless_array[sid as usize];
Ok((sid, lossless))
}
/// Helper for [`Self::decode_inter_segment_id`]: re-stamp the
/// block's `bw4` columns of `AboveSegPredContext[]` and `bh4` rows
/// of `LeftSegPredContext[]` with the given binary `flag`.
/// Frame-edge cells are skipped (mirroring the §5.11.5 grid-fill
/// edge-clip pattern). `pub(crate)` since r423: the encoder's
/// §5.11.19 write arm performs the same two spec-mandated stamps
/// on its mirror walker (the `!preSkip && skip` arm's forced `0`
/// and the `temporal_update == 1` arm's `seg_id_predicted`).
#[inline]
pub(crate) fn stamp_seg_pred_context(
&mut self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
flag: u8,
) {
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for i in 0..bw4 {
let cc = mi_col + i;
if cc >= self.mi_cols {
break;
}
self.above_seg_pred_context[cc as usize] = flag;
}
for i in 0..bh4 {
let rr = mi_row + i;
if rr >= self.mi_rows {
break;
}
self.left_seg_pred_context[rr as usize] = flag;
}
}
/// Helper for [`Self::decode_inter_segment_id`]: stamp the
/// `bh4 * bw4` footprint of `segment_ids[]` with the given
/// `segment_id` value (used on the no-read arms — `segmentation_enabled
/// == false`, `!segmentation_update_map`, the pre-skip
/// `!SegIdPreSkip` early return, and the `seg_id_predicted == 1`
/// adoption of `predictedSegmentId`). The read-via-`decode_segment_id`
/// arms stamp the grid themselves and skip this helper.
#[inline]
fn stamp_segment_id_grid(&mut self, mi_row: u32, mi_col: u32, sub_size: usize, sid: i32) {
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.segment_ids[(rr * self.mi_cols + cc) as usize] = sid;
}
}
}
/// Helper for [`Self::decode_inter_segment_id`]: derive the §8.3.2
/// `seg_id_predicted` ctx (`LeftSegPredContext[ MiRow ] +
/// AboveSegPredContext[ MiCol ]`). Each contribution is in
/// `0..=1`, so the sum is in `0..SEGMENT_ID_PREDICTED_CONTEXTS =
/// 0..3`. `pub(crate)` since r423: the encoder's §5.11.19 write
/// arm derives the identical ctx from its mirror walker before
/// emitting the `seg_id_predicted` S().
#[inline]
pub(crate) fn seg_pred_ctx(&self, mi_row: u32, mi_col: u32) -> usize {
let left = self.left_seg_pred_context[mi_row as usize] as usize;
let above = self.above_seg_pred_context[mi_col as usize] as usize;
left + above
}
/// `read_delta_qindex()` per §5.11.12 (av1-spec p.67) — reads the
/// per-superblock quantiser-index delta and applies it to the
/// running `CurrentQIndex` scalar. Updates
/// [`Self::current_q_index`] in place and returns the new value.
///
/// The spec body (av1-spec p.67) reads:
///
/// ```text
/// read_delta_qindex( ) {
/// sbSize = use_128x128_superblock ? BLOCK_128X128 : BLOCK_64X64
/// if ( MiSize == sbSize && skip )
/// return
/// if ( ReadDeltas ) {
/// delta_q_abs S()
/// if ( delta_q_abs == DELTA_Q_SMALL ) {
/// delta_q_rem_bits L(3)
/// delta_q_rem_bits++
/// delta_q_abs_bits L(delta_q_rem_bits)
/// delta_q_abs = delta_q_abs_bits + (1 << delta_q_rem_bits) + 1
/// }
/// if ( delta_q_abs ) {
/// delta_q_sign_bit L(1)
/// reducedDeltaQIndex = delta_q_sign_bit ? -delta_q_abs
/// : delta_q_abs
/// CurrentQIndex = Clip3(1, 255,
/// CurrentQIndex + (reducedDeltaQIndex << delta_q_res))
/// }
/// }
/// }
/// ```
///
/// Per §6.10.4 / §5.11.12 the gating condition `ReadDeltas` is
/// `delta_q_present && first-block-of-superblock`; the §6.10.4
/// derivation is owned by the (future) tile walk and we accept it
/// as the caller-passed `read_deltas` boolean. `skip` is the
/// `Skips[r][c]` value the §5.11.11 [`Self::decode_skip`] just
/// stamped (the caller has it in scope from that call's return).
/// `use_128x128_superblock` is the §5.5.1 sequence-header bit.
/// `delta_q_res` is the §5.9.17 frame-header field clamped to
/// `0..=3` by `f(2)`.
///
/// The `S()` read uses the single `TileDeltaQCdf` row (length
/// `DELTA_Q_SMALL + 2 = 5`, four literals `0..=DELTA_Q_SMALL` plus
/// the §8.2.6 counter slot). When the decoded `delta_q_abs`
/// equals `DELTA_Q_SMALL` it is the escape sentinel: the spec
/// then reads `delta_q_rem_bits` as `L(3)` (a fixed 3-bit
/// uniform integer), increments it by one to get the working
/// `n = rem_bits + 1`, reads `delta_q_abs_bits` as `L(n)` (the
/// `rem_bits` increment is performed *before* the second
/// literal read, so the second read uses the incremented count),
/// and reconstructs `delta_q_abs = delta_q_abs_bits + (1 << n) +
/// 1`. With `rem_bits ∈ 0..=7` after the `L(3)` read and a
/// post-increment `n ∈ 1..=8`, `delta_q_abs ∈ 2 + 2 ⋯ 256 +
/// 256 = 3 .. 511`; combined with the literal-branch range
/// `0..=DELTA_Q_SMALL` the full §5.11.12 `delta_q_abs` range is
/// `0..=2 ∪ 3..=511`, i.e. up to `511`.
///
/// Returns the post-call `CurrentQIndex` on success, or
/// [`Error::PartitionWalkOutOfRange`] for caller-bug arguments
/// (out-of-range `sub_size`, `mi_row` / `mi_col` outside the
/// frame's mi extent). [`Error::UnexpectedEnd`] /
/// [`Error::SymbolExitUnderflow`] surface bitstream errors.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
/// [`Error::SymbolExitUnderflow`]: crate::Error::SymbolExitUnderflow
#[allow(clippy::too_many_arguments)]
pub fn decode_delta_qindex(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip: u8,
read_deltas: bool,
use_128x128_superblock: bool,
delta_q_res: u8,
) -> Result<i32, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.12 superblock-skip short-circuit:
// sbSize = use_128x128_superblock ? BLOCK_128X128 : BLOCK_64X64
// if ( MiSize == sbSize && skip ) return
// — a skipped whole-superblock does not signal a delta.
let sb_size = if use_128x128_superblock {
BLOCK_128X128
} else {
BLOCK_64X64
};
if sub_size == sb_size && skip != 0 {
return Ok(self.current_q_index);
}
// §5.11.12 outer gate: `ReadDeltas` (§6.10.4) — when off the
// function body is a no-op and CurrentQIndex stays unchanged.
if !read_deltas {
return Ok(self.current_q_index);
}
// delta_q_abs S()
// §8.3.2: the cdf is given by TileDeltaQCdf (no ctx index).
let cdf = cdfs.delta_q_cdf();
let mut delta_q_abs: u32 = decoder.read_symbol(cdf)?;
debug_assert!(
(delta_q_abs as usize) < DELTA_Q_SMALL + 1,
"S() over Default_Delta_Q_Cdf yields 0..=DELTA_Q_SMALL"
);
if delta_q_abs as usize == DELTA_Q_SMALL {
// Escape ladder: delta_q_rem_bits = L(3) + 1, then
// delta_q_abs_bits = L(delta_q_rem_bits), and finally
// delta_q_abs = delta_q_abs_bits + (1 << delta_q_rem_bits) + 1.
// The spec's `delta_q_rem_bits++` happens *before* the
// second L() read, so `n` below is the incremented count.
let rem_bits = decoder.read_literal(3)?;
let n = rem_bits + 1;
let abs_bits = decoder.read_literal(n)?;
// n ∈ 1..=8 so (1 << n) ∈ 2..=256; abs_bits < (1 << n);
// the reconstructed value fits in u32 comfortably.
delta_q_abs = abs_bits + (1 << n) + 1;
}
if delta_q_abs != 0 {
let sign = decoder.read_literal(1)?;
// reducedDeltaQIndex = delta_q_sign_bit ? -delta_q_abs : delta_q_abs
let reduced: i32 = if sign == 1 {
-(delta_q_abs as i32)
} else {
delta_q_abs as i32
};
// CurrentQIndex = Clip3(1, 255,
// CurrentQIndex + (reducedDeltaQIndex << delta_q_res))
// delta_q_res is f(2) so ∈ 0..=3; the left shift fits in
// i32 (max |reduced| is 511 ⇒ 511 << 3 = 4088 << 16-bit).
let shift = delta_q_res as u32;
let scaled = reduced.wrapping_shl(shift);
let pre_clip = self.current_q_index.wrapping_add(scaled);
self.current_q_index = pre_clip.clamp(1, 255);
}
Ok(self.current_q_index)
}
/// `read_delta_lf()` per §5.11.13 (av1-spec p.68) — reads the
/// per-superblock loop-filter delta values and applies them to
/// the running `DeltaLF[ i ]` row. Updates
/// [`Self::current_delta_lf`] in place and returns the post-call
/// row as a fixed-length array.
///
/// The spec body (av1-spec p.68) reads:
///
/// ```text
/// read_delta_lf( ) {
/// sbSize = use_128x128_superblock ? BLOCK_128X128 : BLOCK_64X64
/// if ( MiSize == sbSize && skip )
/// return
/// if ( ReadDeltas && delta_lf_present ) {
/// frameLfCount = 1
/// if ( delta_lf_multi )
/// frameLfCount = ( NumPlanes > 1 ) ? FRAME_LF_COUNT
/// : ( FRAME_LF_COUNT - 2 )
/// for ( i = 0; i < frameLfCount; i++ ) {
/// delta_lf_abs S()
/// if ( delta_lf_abs == DELTA_LF_SMALL ) {
/// delta_lf_rem_bits L(3)
/// n = delta_lf_rem_bits + 1
/// delta_lf_abs_bits L(n)
/// deltaLfAbs = delta_lf_abs_bits + (1 << n) + 1
/// } else {
/// deltaLfAbs = delta_lf_abs
/// }
/// if ( deltaLfAbs ) {
/// delta_lf_sign_bit L(1)
/// reducedDeltaLfLevel = delta_lf_sign_bit ?
/// -deltaLfAbs : deltaLfAbs
/// DeltaLF[ i ] = Clip3( -MAX_LOOP_FILTER, MAX_LOOP_FILTER,
/// DeltaLF[ i ] + (reducedDeltaLfLevel << delta_lf_res) )
/// }
/// }
/// }
/// }
/// ```
///
/// Note that §5.11.13 differs structurally from §5.11.12
/// `read_delta_qindex` in three places: (1) the outer guard is
/// `ReadDeltas && delta_lf_present` (two AND-ed flags, not just
/// `ReadDeltas`), (2) the body iterates `frameLfCount` times
/// instead of running once, and (3) the per-iteration `S()`
/// uses a *different* CDF depending on the `delta_lf_multi`
/// flag (`TileDeltaLFCdf` for the single-LF branch,
/// `TileDeltaLFMultiCdf[ i ]` for the multi-LF branch). The
/// `MiSize == sbSize && skip` short-circuit at the top is
/// identical to §5.11.12, as are the `DELTA_LF_SMALL` escape
/// ladder and the `delta_lf_sign_bit` + `Clip3` update step.
///
/// The `frameLfCount` derivation honours `NumPlanes`: when
/// `delta_lf_multi == 1` and `NumPlanes > 1` the loop covers
/// all four edge categories (horizontal-Y, vertical-Y, U, V);
/// when `NumPlanes == 1` (monochrome) the U/V categories are
/// skipped and the loop covers just the two Y categories
/// (`FRAME_LF_COUNT - 2 = 2`). When `delta_lf_multi == 0` the
/// loop runs once and writes only `DeltaLF[ 0 ]` regardless of
/// `NumPlanes`.
///
/// `delta_lf_res` is the §5.9.18 frame-header field clamped to
/// `0..=3` by `f(2)`. `mono_chrome` is `NumPlanes == 1` per
/// §5.5.2. `delta_lf_present` is the §5.9.18 frame-header bit;
/// when set, `delta_lf_multi` is read by the frame header and
/// passed through as the same-named argument. `read_deltas` is
/// the §6.10.4 `ReadDeltas` derivation (`delta_q_present` AND
/// first-block-of-superblock) — accepted from the caller per
/// the §5.11.12 convention.
///
/// Returns the post-call `DeltaLF[ .. ]` row on success, or
/// [`Error::PartitionWalkOutOfRange`] for caller-bug arguments
/// (`sub_size >= BLOCK_SIZES`, `mi_row >= MiRows`, `mi_col >=
/// MiCols`). [`Error::UnexpectedEnd`] /
/// [`Error::SymbolExitUnderflow`] surface bitstream errors.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
/// [`Error::SymbolExitUnderflow`]: crate::Error::SymbolExitUnderflow
#[allow(clippy::too_many_arguments)]
pub fn decode_delta_lf(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip: u8,
read_deltas: bool,
delta_lf_present: bool,
delta_lf_multi: bool,
mono_chrome: bool,
use_128x128_superblock: bool,
delta_lf_res: u8,
) -> Result<[i32; FRAME_LF_COUNT], crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.13 superblock-skip short-circuit (identical to
// §5.11.12): a skipped whole-superblock does not signal
// a delta.
let sb_size = if use_128x128_superblock {
BLOCK_128X128
} else {
BLOCK_64X64
};
if sub_size == sb_size && skip != 0 {
return Ok(self.current_delta_lf);
}
// §5.11.13 outer gate: `ReadDeltas && delta_lf_present` —
// both must be true. Either off ⇒ no read, accumulator
// unchanged.
if !(read_deltas && delta_lf_present) {
return Ok(self.current_delta_lf);
}
// §5.11.13 `frameLfCount` derivation.
let frame_lf_count: usize = if delta_lf_multi {
if mono_chrome {
FRAME_LF_COUNT - 2
} else {
FRAME_LF_COUNT
}
} else {
1
};
debug_assert!(
frame_lf_count <= FRAME_LF_COUNT,
"frameLfCount cannot exceed FRAME_LF_COUNT = 4"
);
for i in 0..frame_lf_count {
// delta_lf_abs S()
// §8.3.2: the cdf is given by TileDeltaLFCdf (single-LF)
// or TileDeltaLFMultiCdf[ i ] (multi-LF).
let cdf: &mut [u16] = if delta_lf_multi {
cdfs.delta_lf_multi_cdf(i)
} else {
cdfs.delta_lf_cdf()
};
let mut delta_lf_abs: u32 = decoder.read_symbol(cdf)?;
debug_assert!(
(delta_lf_abs as usize) < DELTA_LF_SMALL + 1,
"S() over Default_Delta_Lf_Cdf yields 0..=DELTA_LF_SMALL"
);
if delta_lf_abs as usize == DELTA_LF_SMALL {
// Escape ladder: identical shape to §5.11.12. n is
// the post-increment `delta_lf_rem_bits + 1`; the
// second L() read uses the incremented count.
let rem_bits = decoder.read_literal(3)?;
let n = rem_bits + 1;
let abs_bits = decoder.read_literal(n)?;
delta_lf_abs = abs_bits + (1 << n) + 1;
}
if delta_lf_abs != 0 {
let sign = decoder.read_literal(1)?;
let reduced: i32 = if sign == 1 {
-(delta_lf_abs as i32)
} else {
delta_lf_abs as i32
};
// DeltaLF[ i ] = Clip3(-MAX_LOOP_FILTER,
// MAX_LOOP_FILTER,
// DeltaLF[ i ] + (reducedDeltaLfLevel << delta_lf_res))
let shift = delta_lf_res as u32;
let scaled = reduced.wrapping_shl(shift);
let pre_clip = self.current_delta_lf[i].wrapping_add(scaled);
self.current_delta_lf[i] = pre_clip.clamp(-MAX_LOOP_FILTER, MAX_LOOP_FILTER);
}
}
Ok(self.current_delta_lf)
}
/// `intra_frame_mode_info()` per §5.11.7 (av1-spec p.64–65), prefix
/// only — composes the per-block syntax elements
///
/// ```text
/// skip = 0
/// if ( SegIdPreSkip )
/// intra_segment_id( )
/// skip_mode = 0
/// read_skip( )
/// if ( !SegIdPreSkip )
/// intra_segment_id( )
/// read_cdef( )
/// read_delta_qindex( )
/// read_delta_lf( )
/// ReadDeltas = 0
/// RefFrame[ 0 ] = INTRA_FRAME
/// RefFrame[ 1 ] = NONE
/// ```
///
/// (av1-spec p.64, the first 11 lines of `intra_frame_mode_info`)
/// into a single walker entry-point. The dispatcher composes
/// existing leaf methods in spec order: r160
/// [`Self::decode_intra_segment_id`] for the §5.11.8 segment-id
/// read (called on either the pre-skip or post-skip arm per the
/// §5.9.14 `SegIdPreSkip` derivation); r152 [`Self::decode_skip`]
/// for the §5.11.11 read; r156 [`Self::decode_cdef`] for the
/// §5.11.56 CDEF-index read; r154 [`Self::decode_delta_qindex`]
/// for the §5.11.12 quantiser delta; r155 [`Self::decode_delta_lf`]
/// for the §5.11.13 loop-filter deltas.
///
/// The §5.11.7 `read_skip()` call routes through r152's
/// `decode_skip` with the caller-supplied `seg_skip_active` flag
/// (the §5.9.14 `SegIdPreSkip && seg_feature_active(SEG_LVL_SKIP)`
/// short-circuit that forces `skip = 1` without consuming a bit).
/// `skip_mode = 0` is implicit — `read_skip_mode` is never called
/// from the intra-frame walk (skip-mode is an inter-only
/// compound-reference shortcut per §5.11.10 — the
/// `seg_feature_active(SEG_LVL_REF_FRAME) || ... || !skip_mode_present`
/// guard in `read_skip_mode` always trips for an intra-only frame
/// because intra-only frames have `skip_mode_present == 0` per
/// §5.9.21). The returned [`IntraFrameModeInfoPrefix::skip_mode`]
/// field is therefore always `0`.
///
/// The dispatcher returns at the `RefFrame[0] = INTRA_FRAME` /
/// `RefFrame[1] = NONE` assignment, which is the point where the
/// §5.11.7 body diverges into the `use_intrabc` arm vs. the
/// regular intra-mode arm (`intra_frame_y_mode S()`,
/// `intra_angle_info_y()`, `uv_mode S()`, …). Those follow-on
/// elements need symbols not yet wired through the walker
/// (`use_intrabc`, `intra_frame_y_mode`, `uv_mode`, etc.) and are
/// the topic of a later sub-round; the §5.11.22
/// `intra_block_mode_info` composite (3–4 sub-rounds per the
/// round-161 plan) covers the regular-intra arm directly.
///
/// The §6.10.4 `ReadDeltas = 0` assignment is **not** performed
/// inside the dispatcher: `ReadDeltas` is the §6.10.4 tile-walk
/// scalar gating the delta reads, owned by the tile walk, not the
/// per-block walker. The dispatcher consumes the caller-passed
/// `read_deltas` boolean exactly once (both `decode_delta_qindex`
/// and `decode_delta_lf` route through it), so the spec's "reset
/// to 0 after the first block of the superblock" semantics are
/// implemented by the *caller* clearing its own `ReadDeltas`
/// variable to `false` on every call after the first per
/// superblock. This keeps the walker stateless about
/// per-superblock first-block detection and matches the §6.10.4
/// pattern already used by the existing
/// [`Self::decode_delta_qindex`] / [`Self::decode_delta_lf`]
/// call sites.
///
/// Caller-passed arguments split into three groups:
///
/// 1. **Per-block location**: `mi_row`, `mi_col`, `sub_size` (the
/// §5.11.5 `MiSize` ordinal). Range-checked once on entry
/// (`sub_size < BLOCK_SIZES`, `mi_row < MiRows`, `mi_col <
/// MiCols`); each inner method re-checks for paranoia but the
/// dispatcher short-circuits before any of them on a caller
/// bug.
/// 2. **Segmentation state** (§5.9.14, walker stays state-free):
/// `seg_id_pre_skip` (the `SegIdPreSkip` trailing derivation,
/// boolean), `segmentation_enabled` (§5.9.14 toggle),
/// `seg_skip_active` (the `seg_feature_active(SEG_LVL_SKIP)`
/// short-circuit the §5.11.11 read consumes),
/// `last_active_seg_id` (§5.9.14 trailing derivation),
/// `lossless_array` (§6.8.2 per-segment `LosslessArray[]`).
/// 3. **Frame-header scalars** (§5.5.1 / §5.9.x / §6.10.4):
/// `coded_lossless` (§6.8.2 frame-wide AND), `enable_cdef`
/// (§5.5.2 sequence-header bit), `allow_intrabc` (§5.9.5
/// frame-header bit), `cdef_bits` (§5.9.19 `f(2)`),
/// `read_deltas` (§6.10.4 derivation),
/// `use_128x128_superblock` (§5.5.1 sequence-header bit),
/// `delta_q_res` (§5.9.17 `f(2)`), `delta_lf_present`
/// (§5.9.18 frame-header bit), `delta_lf_multi` (§5.9.18
/// frame-header bit), `mono_chrome` (§5.5.2 sequence-header
/// `NumPlanes == 1`), `delta_lf_res` (§5.9.18 `f(2)`).
///
/// The return [`IntraFrameModeInfoPrefix`] mirrors the §5.11.7
/// state the dispatcher produced. `skip` and `segment_id` carry
/// the §5.11.11 / §5.11.8 outputs; `lossless` carries the
/// §5.11.8 `Lossless = LosslessArray[segment_id]` lookup;
/// `cdef_idx` carries the §5.11.56 `cdef_idx` value (`-1` when
/// the §5.11.56 short-circuit fired and no anchor was written);
/// `current_q_index` and `current_delta_lf` carry the post-call
/// running accumulators (the walker's own state, returned for
/// caller observability without forcing a separate accessor
/// call). `ref_frame[0] = INTRA_FRAME = 0` and `ref_frame[1] =
/// NONE = -1` are the §5.11.7 fixed assignments.
///
/// Returns [`Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (out-of-range `sub_size`, `mi_row` / `mi_col` past
/// the frame's mi extent, `last_active_seg_id >= MAX_SEGMENTS`,
/// `cdef_bits > 3`). [`Error::UnexpectedEnd`] /
/// [`Error::SymbolExitUnderflow`] surface bitstream errors from
/// any of the inner reads.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
/// [`Error::SymbolExitUnderflow`]: crate::Error::SymbolExitUnderflow
#[allow(clippy::too_many_arguments)]
pub fn decode_intra_frame_mode_info_prefix(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
// Segmentation state (§5.9.14):
seg_id_pre_skip: bool,
segmentation_enabled: bool,
seg_skip: &[bool; MAX_SEGMENTS],
last_active_seg_id: u8,
lossless_array: &[bool; MAX_SEGMENTS],
// Frame-header / sequence-header scalars:
coded_lossless: bool,
enable_cdef: bool,
allow_intrabc: bool,
cdef_bits: u32,
read_deltas: bool,
use_128x128_superblock: bool,
delta_q_res: u8,
delta_lf_present: bool,
delta_lf_multi: bool,
mono_chrome: bool,
delta_lf_res: u8,
) -> Result<IntraFrameModeInfoPrefix, crate::Error> {
// §5.11.7 range guards on the dispatcher level — every inner
// method re-checks these, but a fail-fast here keeps the
// dispatcher's behaviour consistent on caller bugs (no
// partial reads).
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if cdef_bits > 3 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.7 line 1: `skip = 0`. The §5.11.11 `decode_skip` call
// below overwrites this; the local initial value is the spec's
// fall-through if `decode_skip`'s seg_skip_active arm fires
// with a `0` (it can't — that arm always returns `1` — but the
// assignment matches the spec text).
//
// §5.11.7 lines 2-3: `if ( SegIdPreSkip ) intra_segment_id( )`.
// The pre-skip arm calls intra_segment_id BEFORE read_skip;
// the post-skip arm calls it AFTER. Pre-skip is the §5.9.14
// SegIdPreSkip derivation: any active feature j >=
// SEG_LVL_REF_FRAME forces it to 1.
//
// On the pre-skip arm the §5.11.9 read_segment_id is called
// with `skip = 0` (the §5.11.7 init); the skip short-circuit
// there does not fire. On the post-skip arm `skip` carries
// the just-decoded value.
let (mut segment_id, mut lossless) = (0u8, lossless_array[0]);
if seg_id_pre_skip {
let (sid, ll) = self.decode_intra_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
/* skip = */ 0,
segmentation_enabled,
last_active_seg_id,
lossless_array,
)?;
segment_id = sid;
lossless = ll;
}
// §5.11.7 line 4: `skip_mode = 0`. The intra-frame walk never
// reads `skip_mode` — §5.11.10 short-circuits to `0` for
// intra-only frames via the `!skip_mode_present` guard
// (intra-only ⇒ `skip_mode_present == 0` per §5.9.21). The
// walker's `SkipModes[]` grid is unchanged (the §5.11.10
// `decode_skip_mode` is the only writer and we don't call
// it from this dispatcher).
let skip_mode = 0u8;
// §5.11.7 line 5: `read_skip( )`. Routes through r152
// `decode_skip` with `seg_skip_active = SegIdPreSkip &&
// seg_feature_active( SEG_LVL_SKIP )` — derived per BLOCK
// from the pre-skip-committed segment id (r426: the arm can
// only fire on the `SegIdPreSkip == 1` shape, where the
// §5.11.8 read above already resolved `segment_id`; a
// frame-level any-segment collapse mis-forced `skip = 1` on
// mixed tables).
let skip = self.decode_skip(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
seg_id_pre_skip && segmentation_enabled && seg_skip[segment_id as usize],
)?;
// §5.11.7 lines 6-7: `if ( !SegIdPreSkip ) intra_segment_id( )`.
// Post-skip arm — the §5.11.9 path now sees the just-decoded
// `skip` value, which gates whether `S()` is consumed
// (skip ⇒ segment_id = pred = neighbour-derived, no bit
// read; !skip ⇒ `diff S()` against `TileSegmentIdCdf[ctx]`).
if !seg_id_pre_skip {
let (sid, ll) = self.decode_intra_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
segmentation_enabled,
last_active_seg_id,
lossless_array,
)?;
segment_id = sid;
lossless = ll;
}
// §5.11.7 line 8: `read_cdef( )`. Routes through r156
// `decode_cdef`; the `coded_lossless || !enable_cdef ||
// allow_intrabc || skip` short-circuit fires inside.
let cdef_idx = self.decode_cdef(
decoder,
mi_row,
mi_col,
sub_size,
skip,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
)?;
// §5.11.7 line 9: `read_delta_qindex( )`. Routes through r154
// `decode_delta_qindex` with the caller-passed `ReadDeltas`
// flag, additionally gated by the §5.11.2 per-superblock
// `ReadDeltas` lifecycle bit (only the superblock's FIRST
// block reads — §5.11.7 clears `ReadDeltas` right after).
// Updates `Self::current_q_index` in place.
let block_read_deltas = read_deltas && self.read_deltas_pending;
let current_q_index = self.decode_delta_qindex(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
block_read_deltas,
use_128x128_superblock,
delta_q_res,
)?;
// §5.11.7 line 10: `read_delta_lf( )`. Routes through r155
// `decode_delta_lf` with the caller-passed `read_deltas` AND
// `delta_lf_present` gating (the spec body of
// `read_delta_lf` re-checks both, mirroring the §5.11.13
// outer condition). Updates `Self::current_delta_lf` in
// place.
let current_delta_lf = self.decode_delta_lf(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
block_read_deltas,
delta_lf_present,
delta_lf_multi,
mono_chrome,
use_128x128_superblock,
delta_lf_res,
)?;
// §5.11.7 line 11: `ReadDeltas = 0` — subsequent blocks of
// this superblock skip the §5.11.12/§5.11.13 reads.
self.read_deltas_pending = false;
// §5.11.5 `DeltaLFs[ r + y ][ c + x ][ i ] = DeltaLF[ i ]`
// grid-fill — snapshot the (now-final) §5.11.13 accumulator over
// the block footprint so the §7.14.4 deblock reads the per-mi
// value. No-op on `delta_lf_present == 0` (the accumulator is the
// all-zero §5.9.18 default).
self.stamp_delta_lfs(mi_row, mi_col, sub_size);
// §5.11.7 lines 11-13: `ReadDeltas = 0`, `RefFrame[ 0 ] =
// INTRA_FRAME`, `RefFrame[ 1 ] = NONE`. `ReadDeltas` is
// caller-owned (see method doc) and not reset inside the
// walker. The two `RefFrame[*]` assignments are returned in
// the struct's `ref_frame` field — `INTRA_FRAME = 0` /
// `NONE = -1` per §6.10.1 / §3 of the spec.
Ok(IntraFrameModeInfoPrefix {
skip,
skip_mode,
segment_id,
lossless,
cdef_idx,
current_q_index,
current_delta_lf,
ref_frame: [0, -1],
})
}
/// `use_intrabc` syntax element per §5.11.7 (av1-spec p.65) — the
/// per-block intra-block-copy enable bit read on the §5.11.7
/// `intra_frame_mode_info()` body immediately after the `RefFrame[
/// 0..2 ]` assignments the
/// [`Self::decode_intra_frame_mode_info_prefix`] dispatcher produced.
///
/// The spec body (av1-spec p.65) reads:
///
/// ```text
/// if ( allow_intrabc ) {
/// use_intrabc S()
/// } else {
/// use_intrabc = 0
/// }
/// ```
///
/// `allow_intrabc` is the §5.9.20 frame-header bit (also routed
/// through the §5.9.5 / §5.5.2 `screen_content_tools` gates — see
/// [`crate::uncompressed_header_tail::parse_segmentation_params`]
/// adjacent fields). When `allow_intrabc == 0`, the spec
/// fall-through assigns `use_intrabc = 0` with no bit consumed;
/// when `allow_intrabc == 1`, a single `S()` symbol is read against
/// `TileIntrabcCdf` (the §8.3.2 selection is contextless — see
/// [`TileCdfContext::intrabc_cdf`]).
///
/// `use_intrabc` itself is **not** stamped onto any §5.11.5 grid
/// (the walker tracks `Skips[]`, `SkipModes[]`, `IsInters[]`,
/// `MiSizes[]`, `SegmentIds[]`, but not a per-block
/// `UseIntrabc[][]` map — the value is consumed locally by the
/// §5.11.7 follow-on `if ( use_intrabc ) { is_inter = 1; YMode =
/// DC_PRED; ... find_mv_stack(0); assign_mv(0) }` arm and never
/// referenced by a future block's CDF-context derivation). The
/// caller threads the returned `u8` into the §5.11.7 arm
/// dispatcher (the `intra_block_mode_info` composite vs. the
/// `is_inter = 1` / `YMode = DC_PRED` short-circuit followed by
/// `find_mv_stack(0)` / `assign_mv(0)` — both of which need
/// symbols not yet wired through the walker).
///
/// Returns the decoded `use_intrabc` value (0 or 1) on success.
/// Returns [`Error::PartitionWalkOutOfRange`] for caller bugs
/// (out-of-range `sub_size`, `mi_row` / `mi_col` outside the
/// frame's mi extent). [`Error::UnexpectedEnd`] /
/// [`Error::SymbolExitUnderflow`] surface bitstream errors from
/// the §8.2.6 `S()` read.
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
/// [`Error::SymbolExitUnderflow`]: crate::Error::SymbolExitUnderflow
pub fn decode_use_intrabc(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
allow_intrabc: bool,
) -> Result<u8, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.7: `if ( allow_intrabc ) { use_intrabc S() } else {
// use_intrabc = 0 }`. The fall-through reads zero bits; the
// S() arm reads one symbol against `TileIntrabcCdf` (the §8.3.2
// selection is contextless — see [`TileCdfContext::intrabc_cdf`]).
let use_intrabc: u8 = if allow_intrabc {
let cdf = cdfs.intrabc_cdf();
// §8.2.6 S(): a 2-symbol read against `Default_Intrabc_Cdf`
// (length 3 including the counter slot) returns 0 or 1.
let sym = decoder.read_symbol(cdf)? as u8;
debug_assert!(sym <= 1, "S() over Default_Intrabc_Cdf yields 0 or 1");
sym
} else {
0
};
Ok(use_intrabc)
}
/// `intra_frame_y_mode` per §5.11.7 (av1-spec p.65 — the
/// `intra_frame_mode_info()` `else` arm when `use_intrabc == 0`)
/// — reads the per-block `intra_frame_y_mode` syntax element at
/// the leaf `(mi_row, mi_col)` of a block whose §5.11.5 `MiSize`
/// is `sub_size`. The spec text reads:
///
/// ```text
/// intra_frame_y_mode S()
/// YMode = intra_frame_y_mode
/// ```
///
/// This is the single syntax element that selects the luma intra
/// prediction mode for an intra block in a key frame or intra-only
/// frame (the non-`use_intrabc` arm of §5.11.7). The decoded value
/// is in `0..INTRA_MODES = 0..13` per the §9.3 enumeration
/// (`DC_PRED = 0` through `PAETH_PRED = 12`) and becomes the
/// per-block `YMode`.
///
/// The §8.3.2 selection (av1-spec p.361) is
///
/// ```text
/// intra_frame_y_mode: The cdf for intra_frame_y_mode is given by
/// TileIntraFrameYModeCdf[ abovemode ][ leftmode ]
/// where:
/// abovemode = Intra_Mode_Context[ AvailU ? YModes[ MiRow - 1 ][ MiCol ] : DC_PRED ]
/// leftmode = Intra_Mode_Context[ AvailL ? YModes[ MiRow ][ MiCol - 1 ] : DC_PRED ]
/// ```
///
/// The walker reads `YModes[]` via [`Self::y_mode_at`] (which
/// returns `DC_PRED == 0` for out-of-grid coordinates), gates on
/// `AvailU` / `AvailL` via [`TileGeometry::is_inside`], maps each
/// neighbour's `YMode` through [`intra_mode_ctx`] (=
/// `Intra_Mode_Context[mode]`, in `0..INTRA_MODE_CONTEXTS`), and
/// selects the CDF row via
/// [`TileCdfContext::intra_frame_y_mode_cdf`]. A `S()` symbol is
/// then decoded against that row; the result is in
/// `0..INTRA_MODES` per the §9.4
/// `Default_Intra_Frame_Y_Mode_Cdf` width (each row is `INTRA_MODES
/// + 1 = 14` entries, the last being the §8.2.6 counter slot).
///
/// The decoded `YMode` value is then stamped over the block's
/// `bw4 * bh4` footprint of [`Self::y_modes`] per the §5.11.5
/// per-block invariant
///
/// ```text
/// for ( y = 0; y < bh4; y++ )
/// for ( x = 0; x < bw4; x++ )
/// YModes[ r + y ][ c + x ] = YMode
/// ```
///
/// (av1-spec p.65) so the next block's §8.3.2
/// `intra_frame_y_mode` ctx walk observes the value. Cells
/// straddling the bottom or right edge are clipped at the frame's
/// `MiRows` / `MiCols` extent.
///
/// Returns the decoded `YMode` (0..INTRA_MODES) on success, or
/// [`Error::PartitionWalkOutOfRange`] for caller bugs (out-of-range
/// `sub_size`, `mi_row` / `mi_col` outside the frame's mi extent).
/// [`Error::UnexpectedEnd`] surfaces if the bitstream runs out
/// mid-symbol.
///
/// Note: this method is the §5.11.22 `intra_block_mode_info`
/// composite's first sub-element. It is also the second
/// `S()`-typed leaf of the §5.11.7 `else` arm (after r161's
/// `decode_intra_frame_mode_info_prefix` covers the §5.11.7 body
/// down through `read_delta_lf( )`, and r164's
/// `decode_use_intrabc` covers the `use_intrabc` element). It is
/// distinct from `y_mode` (§5.11.22 in `intra_block_mode_info` for
/// inter-frame intra blocks), which uses the `Size_Group`-based
/// CDF [`TileCdfContext::y_mode_cdf`] instead of the
/// neighbour-mode-based one selected here. The §5.11.7 follow-on
/// elements (`intra_angle_info_y`, `uv_mode`, `read_cfl_alphas`,
/// `intra_angle_info_uv`, `palette_mode_info`,
/// `filter_intra_mode_info`) are composed — together with this
/// leaf — by [`Self::decode_intra_frame_mode_info_else_arm`].
///
/// [`Error::PartitionWalkOutOfRange`]: crate::Error::PartitionWalkOutOfRange
/// [`Error::UnexpectedEnd`]: crate::Error::UnexpectedEnd
pub fn decode_intra_frame_y_mode(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
) -> Result<u8, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §8.3.2 ctx derivation (av1-spec p.361):
// abovemode = Intra_Mode_Context[ AvailU ? YModes[ MiRow - 1 ][ MiCol ] : DC_PRED ]
// leftmode = Intra_Mode_Context[ AvailL ? YModes[ MiRow ][ MiCol - 1 ] : DC_PRED ]
//
// `AvailU` / `AvailL` are the §5.11.51 tile-bound predicates;
// an unavailable neighbour contributes the spec's
// `DC_PRED == 0` fallback, which `y_mode_at` returns naturally
// for out-of-grid coordinates. The `is_inside` gate
// additionally suppresses neighbours that are in-grid but
// outside the current tile (a real tile-mid block's left
// neighbour can be in-grid yet in the previous tile, where
// `AvailL` is false per §5.11.51).
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let above_mode = if avail_u {
self.y_mode_at(mi_row as i32 - 1, mi_col as i32) as usize
} else {
0 // DC_PRED per spec
};
let left_mode = if avail_l {
self.y_mode_at(mi_row as i32, mi_col as i32 - 1) as usize
} else {
0 // DC_PRED per spec
};
// `intra_mode_ctx` is total over `0..INTRA_MODES`; both
// neighbour values are bounded by the §3 enumeration so the
// mapping is safe.
debug_assert!(
above_mode < INTRA_MODES,
"neighbour above YMode out of §3 enumeration range"
);
debug_assert!(
left_mode < INTRA_MODES,
"neighbour left YMode out of §3 enumeration range"
);
let abovemode_ctx = intra_mode_ctx(above_mode);
let leftmode_ctx = intra_mode_ctx(left_mode);
// §8.3.2 selection + §8.2.6 S() over a 14-entry CDF row (13
// symbol slots + the counter). The §9.4
// `Default_Intra_Frame_Y_Mode_Cdf` shape is
// `[[u16; INTRA_MODES + 1]; INTRA_MODE_CONTEXTS];
// INTRA_MODE_CONTEXTS`.
let cdf = cdfs.intra_frame_y_mode_cdf(abovemode_ctx, leftmode_ctx);
let y_mode = decoder.read_symbol(cdf)? as u8;
debug_assert!(
(y_mode as usize) < INTRA_MODES,
"S() over Default_Intra_Frame_Y_Mode_Cdf yields 0..INTRA_MODES"
);
// §5.11.5 grid-fill: stamp `YMode` over the block's bh4 * bw4
// footprint, clipped at the frame's MiRows / MiCols extent so
// a leaf straddling the bottom or right edge fills only the
// in-grid portion.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.y_modes[(rr * self.mi_cols + cc) as usize] = y_mode;
}
}
Ok(y_mode)
}
/// `inter_frame_mode_info()` per §5.11.18 (av1-spec p.71) — the
/// per-block syntax composite for an inter-frame leaf. Composes
/// every leaf already wired through the walker:
///
/// * §5.11.19 `inter_segment_id( 1 )` — pre-skip arm via
/// [`Self::decode_inter_segment_id`].
/// * §5.11.10 `read_skip_mode()` — via [`Self::decode_skip_mode`].
/// * §5.11.11 `read_skip()` (if not `skip_mode == 1`) — via
/// [`Self::decode_skip`].
/// * §5.11.19 `inter_segment_id( 0 )` (if `!SegIdPreSkip`) —
/// post-skip arm via [`Self::decode_inter_segment_id`].
/// * §5.11.56 `read_cdef()` — via [`Self::decode_cdef`].
/// * §5.11.12 `read_delta_qindex()` — via
/// [`Self::decode_delta_qindex`].
/// * §5.11.13 `read_delta_lf()` — via [`Self::decode_delta_lf`].
/// * §5.11.20 `read_is_inter()` — via [`Self::decode_is_inter`].
///
/// The spec body (av1-spec p.71) reads:
///
/// ```text
/// inter_frame_mode_info( ) {
/// use_intrabc = 0
/// LeftRefFrame[ 0 ] = AvailL ? RefFrames[ MiRow ][ MiCol-1 ][ 0 ] : INTRA_FRAME
/// AboveRefFrame[ 0 ] = AvailU ? RefFrames[ MiRow-1 ][ MiCol ][ 0 ] : INTRA_FRAME
/// LeftRefFrame[ 1 ] = AvailL ? RefFrames[ MiRow ][ MiCol-1 ][ 1 ] : NONE
/// AboveRefFrame[ 1 ] = AvailU ? RefFrames[ MiRow-1 ][ MiCol ][ 1 ] : NONE
/// LeftIntra = LeftRefFrame[ 0 ] <= INTRA_FRAME
/// AboveIntra = AboveRefFrame[ 0 ] <= INTRA_FRAME
/// LeftSingle = LeftRefFrame[ 1 ] <= INTRA_FRAME
/// AboveSingle = AboveRefFrame[ 1 ] <= INTRA_FRAME
/// skip = 0
/// inter_segment_id( 1 )
/// read_skip_mode( )
/// if ( skip_mode )
/// skip = 1
/// else
/// read_skip( )
/// if ( !SegIdPreSkip )
/// inter_segment_id( 0 )
/// Lossless = LosslessArray[ segment_id ]
/// read_cdef( )
/// read_delta_qindex( )
/// read_delta_lf( )
/// ReadDeltas = 0
/// read_is_inter( )
/// if ( is_inter )
/// inter_block_mode_info( )
/// else
/// intra_block_mode_info( )
/// }
/// ```
///
/// The `LeftRefFrame[..]` / `AboveRefFrame[..]` /
/// `LeftIntra` / `AboveIntra` / `LeftSingle` / `AboveSingle`
/// derivations are local-only — only the §5.11.23
/// `inter_block_mode_info` body consumes them (via the §8.3.2
/// ref-frame ctx walks). Since §5.11.23 is the next round's
/// target, the walker does NOT yet track `RefFrames[][][..]`; the
/// derivations are surfaced through the returned
/// [`DecodedInterFrameModeInfo`] for tests / callers that want to
/// verify the §5.11.18 derivations, but the walker treats every
/// neighbour `RefFrames[..][..][0]` as `INTRA_FRAME` and
/// `RefFrames[..][..][1]` as `NONE` (i.e. as if no inter neighbour
/// existed yet) — matching the conformant initial state for the
/// first inter block of a frame.
///
/// On the §5.11.18 terminal `if ( is_inter )` dispatch this method
/// short-circuits at either
/// [`crate::Error::InterBlockModeInfoUnsupported`] (the
/// `is_inter == 1` arm — §5.11.23 next-round target) or
/// [`crate::Error::IntraBlockModeInfoUnsupported`] (the
/// `is_inter == 0` arm — §5.11.22 next-round target). All
/// pre-dispatch reads have committed to the bitstream / grids by
/// the time the stub fires.
///
/// Caller-bug arguments surface
/// [`crate::Error::PartitionWalkOutOfRange`]; bitstream errors
/// surface [`crate::Error::UnexpectedEnd`] /
/// [`crate::Error::SymbolExitUnderflow`].
#[allow(clippy::too_many_arguments)]
pub fn decode_inter_frame_mode_info(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
// §5.9.14 / §6.10 segmentation state (per-frame scalars):
seg_id_pre_skip: bool,
segmentation_enabled: bool,
segmentation_update_map: bool,
segmentation_temporal_update: bool,
predicted_segment_id: u8,
last_active_seg_id: u8,
lossless_array: &[bool; MAX_SEGMENTS],
// §5.9.14 `FeatureEnabled[][]` / `FeatureData[][]` tables —
// r394: every `seg_feature_active( feature )` gate (§5.11.14
// — `read_skip_mode`, `read_skip`, `read_is_inter`, the
// §5.11.25 SEG_LVL_REF_FRAME override, the §5.11.24 / §5.11.26
// SEG_LVL_SKIP / SEG_LVL_GLOBALMV mode forcing) is derived
// HERE at the block's own just-decoded `segment_id`, replacing
// the caller-collapsed frame-scope booleans (wrong whenever
// two segments carried different feature sets).
seg_feature_active: &[[bool; crate::uncompressed_header_tail::SEG_LVL_MAX]; MAX_SEGMENTS],
seg_feature_data: &[[i16; crate::uncompressed_header_tail::SEG_LVL_MAX]; MAX_SEGMENTS],
// §5.9.21 / §6.8.21 / §5.9.14 frame-header / sequence-header
// scalars (mirroring [`Self::decode_intra_frame_mode_info_prefix`]):
skip_mode_present: bool,
coded_lossless: bool,
enable_cdef: bool,
allow_intrabc: bool,
cdef_bits: u32,
read_deltas: bool,
use_128x128_superblock: bool,
delta_q_res: u8,
delta_lf_present: bool,
delta_lf_multi: bool,
mono_chrome: bool,
delta_lf_res: u8,
// r170: §5.11.25 caller-supplied scalars threaded through to
// the §5.11.23 inter arm. The intra arm ignores these; the
// inter arm consumes them in [`Self::decode_inter_block_mode_info`]
// / [`Self::read_ref_frames`]. Pass `[0, -1]` /
// `false / 0 / false / false` for callers that never run the
// inter arm (or for legacy callers that pre-date the §5.11.25
// wiring — the dispatcher only consults them when
// `is_inter == 1`).
skip_mode_frame: [i32; 2],
reference_select: bool,
// r173 §7.10 caller-supplied frame-header scalars threaded
// through to [`Self::find_mv_stack`] via
// [`Self::decode_inter_block_mode_info`]. The intra arm
// ignores these; the inter arm consumes them when the
// §5.11.23 dispatcher invokes `find_mv_stack`. Pass the
// §5.9.24 identity-default `gm_params` (`gm_params[ref][2] =
// gm_params[ref][5] = 1 << WARPEDMODEL_PREC_BITS`, other
// slots = 0) + `[GM_TYPE_IDENTITY; 8]` + `[0; 8]` /
// `false / false / false` for callers that never run the
// inter arm.
gm_type: [i32; 8],
gm_params: [[i32; 6]; 8],
ref_frame_sign_bias: [i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
use_ref_frame_mvs: bool,
// r175: §5.11.27 caller-supplied frame-header scalars (see
// [`Self::decode_inter_block_mode_info`]).
is_motion_mode_switchable: bool,
allow_warped_motion: bool,
is_scaled_per_ref: [bool; 7],
// r176: §5.11.28 caller-supplied sequence-header scalar (see
// [`Self::decode_inter_block_mode_info`]).
enable_interintra_compound: bool,
// r177: §5.11.29 caller-supplied sequence-header / frame-header
// scalars (see [`Self::decode_inter_block_mode_info`]).
// r394: `dist_equal` is no longer caller-threaded — the §8.3.2
// `compound_idx` ctx seed is per-BLOCK (it reads the block's
// own `RefFrame[0]` / `RefFrame[1]`), so the walker derives it
// at the §5.11.29 read from `order_hints`.
enable_masked_compound: bool,
enable_jnt_comp: bool,
order_hints: FrameInterOrderHints,
// r178: §5.11.x caller-supplied frame-header / sequence-header
// scalars (see [`Self::decode_inter_block_mode_info`]).
interpolation_filter: u8,
enable_dual_filter: bool,
// r205: §7.10.2.5 / §7.10.2.6 motion-field grid. Only
// consulted when `use_ref_frame_mvs == true`; pass
// `&MotionFieldMvs::new_invalid(mi_rows, mi_cols)` for an
// all-sentinel grid (which makes the temporal scan a no-op).
motion_field_mvs: &MotionFieldMvs,
) -> Result<DecodedInterFrameModeInfo, crate::Error> {
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if (last_active_seg_id as usize) >= MAX_SEGMENTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.18 line 1: `use_intrabc = 0`. The §5.11.18 walker
// never reads `use_intrabc` (the §5.11.7 intra-only path is
// the only `use_intrabc` caller).
let use_intrabc: u8 = 0;
// §5.11.18 lines 2-9: `LeftRefFrame[..]` / `AboveRefFrame[..]`
// / `LeftIntra` / `AboveIntra` / `LeftSingle` / `AboveSingle`
// local derivations. As of r170 the walker tracks
// `RefFrames[][][..]` (added alongside the §5.11.23 reader);
// the neighbour reads now consult [`Self::ref_frame_at`] which
// returns the spec's `INTRA_FRAME = 0` / `NONE = -1` fallback
// for out-of-grid coordinates AND for in-grid cells that no
// prior block stamped (the pre-fill identity). Callers that
// run §5.11.23 on the first inter block of a fresh walker
// therefore observe the same `[0, -1]` derivations as before
// r170; only subsequent inter blocks see the propagated values.
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let left_ref_frame: [i32; 2] = if avail_l {
[
self.ref_frame_at(mi_row as i32, mi_col as i32 - 1, 0) as i32,
self.ref_frame_at(mi_row as i32, mi_col as i32 - 1, 1) as i32,
]
} else {
// Spec line: `LeftRefFrame[0] = AvailL ? ... : INTRA_FRAME`
// / `LeftRefFrame[1] = AvailL ? ... : NONE`.
[0, -1]
};
let above_ref_frame: [i32; 2] = if avail_u {
[
self.ref_frame_at(mi_row as i32 - 1, mi_col as i32, 0) as i32,
self.ref_frame_at(mi_row as i32 - 1, mi_col as i32, 1) as i32,
]
} else {
[0, -1]
};
// §5.11.18 lines 6-9: `LeftIntra = LeftRefFrame[0] <= INTRA_FRAME`
// (and mirror for AboveIntra / LeftSingle / AboveSingle). The
// spec uses `<=` with `INTRA_FRAME = 0` so any negative value
// (`NONE = -1`) also counts as intra/single.
let left_intra = left_ref_frame[0] <= 0;
let above_intra = above_ref_frame[0] <= 0;
let left_single = left_ref_frame[1] <= 0;
let above_single = above_ref_frame[1] <= 0;
// §5.11.18 line 10: `skip = 0`. The §5.11.10 `read_skip_mode`
// arm below either overrides this to `1` when
// `skip_mode == 1`, or §5.11.11 `read_skip` reads the bit.
// The local `skip` is initialised after the §5.11.10 read so
// the compiler doesn't see the spec's pre-read `skip = 0`
// assignment as a dead store — the spec text spells out the
// initialisation for clarity but the value is unconditionally
// overwritten in the next branch.
// §5.11.18 line 11: `inter_segment_id( 1 )` — pre-skip. The
// §5.11.19 spec body short-circuits when
// `segmentation_enabled == 0` (sets `segment_id = 0`) or when
// `segmentation_update_map == 0` (adopts
// `predictedSegmentId`); on the temporal-update arm it reads
// `seg_id_predicted` and either adopts the predicted id or
// falls through to `read_segment_id`. The post-skip arm of
// §5.11.18 (line 16) re-invokes the function with
// `preSkip = 0`.
let (mut segment_id, _) = self.decode_inter_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
/* pre_skip = */ true,
/* skip = */ 0,
seg_id_pre_skip,
segmentation_enabled,
segmentation_update_map,
segmentation_temporal_update,
predicted_segment_id,
last_active_seg_id,
lossless_array,
)?;
// §5.11.14 `seg_feature_active_idx( idx, feature ) =
// segmentation_enabled && FeatureEnabled[ idx ][ feature ]`,
// evaluated against the CURRENT segment_id (which the
// post-skip §5.11.19 read below may still change — the spec
// derives each gate at its own read point).
let feat_active = |sid: u8, feature: usize| -> bool {
segmentation_enabled
&& seg_feature_active[(sid as usize).min(MAX_SEGMENTS - 1)][feature]
};
use crate::uncompressed_header_tail::{SEG_LVL_GLOBALMV, SEG_LVL_REF_FRAME, SEG_LVL_SKIP};
// §5.11.18 line 12: `read_skip_mode( )`. The §5.11.10 spec
// body short-circuits on the three segmentation overrides +
// !skip_mode_present + small-block (Block_Width/Height < 8)
// gates; otherwise reads a binary symbol against
// `TileSkipModeCdf[ctx]`.
let seg_skip_mode_off = feat_active(segment_id, SEG_LVL_SKIP)
|| feat_active(segment_id, SEG_LVL_REF_FRAME)
|| feat_active(segment_id, SEG_LVL_GLOBALMV);
let skip_mode = self.decode_skip_mode(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
seg_skip_mode_off,
skip_mode_present,
)?;
// §5.11.18 lines 13-14: `if ( skip_mode ) skip = 1 else
// read_skip( )`. The compound-reference-skip shortcut forces
// `skip = 1` without reading the §5.11.11 skip bit.
let skip: u8 = if skip_mode != 0 {
// §5.11.5 grid-fill: the §5.11.10 writer stamped
// `skip_modes[]` but the spec's `skip = 1` also stamps
// `Skips[]` over the footprint (the §5.11.11 writer is
// bypassed). Mirror the §5.11.11 grid-fill so neighbour
// §8.3.2 ctx walks observe the value.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.skips[(rr * self.mi_cols + cc) as usize] = 1;
}
}
1
} else {
// §5.11.18 line 14: `read_skip( )`. The §5.11.11 spec body
// forces `skip = 1` on `SegIdPreSkip &&
// seg_feature_active( SEG_LVL_SKIP )` — SEG_LVL_SKIP
// ALONE, and only when the segment id was already read
// pre-skip (r394 fix: the previous code collapsed all
// three §5.11.10 features into the gate and ignored
// `SegIdPreSkip`, forcing skip on REF_FRAME/GLOBALMV-only
// segments and on `SegIdPreSkip == 0` frames).
self.decode_skip(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
/* seg_skip_active = */
seg_id_pre_skip && feat_active(segment_id, SEG_LVL_SKIP),
)?
};
// §5.11.18 lines 15-16: `if ( !SegIdPreSkip ) inter_segment_id( 0 )`.
// The post-skip arm fires only when the §5.9.14 SegIdPreSkip
// derivation is false; otherwise the pre-skip call already
// committed the segment_id.
if !seg_id_pre_skip {
let (sid, _) = self.decode_inter_segment_id(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
/* pre_skip = */ false,
skip,
seg_id_pre_skip,
segmentation_enabled,
segmentation_update_map,
segmentation_temporal_update,
predicted_segment_id,
last_active_seg_id,
lossless_array,
)?;
segment_id = sid;
}
// §5.11.18 line 17: `Lossless = LosslessArray[ segment_id ]`.
// segment_id ∈ 0..MAX_SEGMENTS by construction (the §5.11.9
// reader caps it via [`neg_deinterleave`] against
// `last_active_seg_id + 1`).
debug_assert!(
(segment_id as usize) < MAX_SEGMENTS,
"segment_id must be in 0..MAX_SEGMENTS"
);
let lossless = lossless_array[segment_id as usize];
// §5.11.18 line 18: `read_cdef( )`. The §5.11.56 spec body
// short-circuits on `skip || coded_lossless || !enable_cdef
// || allow_intrabc`; otherwise reads `L(cdef_bits)`.
let cdef_idx = self.decode_cdef(
decoder,
mi_row,
mi_col,
sub_size,
skip,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
)?;
// §5.11.18 line 19: `read_delta_qindex( )` — gated by the
// §5.11.2 per-superblock `ReadDeltas` lifecycle bit (see the
// §5.11.7 twin site).
let block_read_deltas = read_deltas && self.read_deltas_pending;
let current_q_index = self.decode_delta_qindex(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
block_read_deltas,
use_128x128_superblock,
delta_q_res,
)?;
// §5.11.18 line 20: `read_delta_lf( )`.
let current_delta_lf = self.decode_delta_lf(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip,
block_read_deltas,
delta_lf_present,
delta_lf_multi,
mono_chrome,
use_128x128_superblock,
delta_lf_res,
)?;
// §5.11.18 line 21: `ReadDeltas = 0`.
self.read_deltas_pending = false;
// §5.11.5 `DeltaLFs[][][]` per-mi snapshot grid-fill (see the
// intra-arm twin above). No-op on `delta_lf_present == 0`.
self.stamp_delta_lfs(mi_row, mi_col, sub_size);
// §5.11.18 line 21: `ReadDeltas = 0`. Caller-owned per the
// §6.10.4 derivation (`ReadDeltas = delta_q_present && (MiCol
// & (sbSize4 - 1)) == 0 && (MiRow & (sbSize4 - 1)) == 0`);
// the spec writes the local `ReadDeltas = 0` after the
// per-superblock read site, mirroring the
// [`Self::decode_intra_frame_mode_info_prefix`] convention.
// §5.11.18 line 22: `read_is_inter( )`. The §5.11.20 spec body
// has the four-arm dispatch (skip_mode forces is_inter = 1,
// segment overrides, else S()). The three feature gates are
// re-derived at the FINAL segment_id (the post-skip §5.11.19
// read above may have changed it).
let seg = (segment_id as usize).min(MAX_SEGMENTS - 1);
let seg_ref_frame_active = feat_active(segment_id, SEG_LVL_REF_FRAME);
let seg_globalmv_active = feat_active(segment_id, SEG_LVL_GLOBALMV);
let seg_skip_active = feat_active(segment_id, SEG_LVL_SKIP);
let seg_ref_frame_data = i32::from(seg_feature_data[seg][SEG_LVL_REF_FRAME]);
// `FeatureData[ segment_id ][ SEG_LVL_REF_FRAME ] !=
// INTRA_FRAME` per §5.11.20 (INTRA_FRAME = 0).
let seg_ref_frame_is_inter = seg_ref_frame_data != 0;
let is_inter = self.decode_is_inter(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip_mode,
seg_ref_frame_active,
seg_ref_frame_is_inter,
seg_globalmv_active,
)?;
// §5.11.18 lines 23-26: `if ( is_inter ) inter_block_mode_info()
// else intra_block_mode_info()`. Surface the pre-dispatch
// aggregate first so callers observe the §5.11.18 prologue
// state regardless of which terminal path fires.
let mut info = DecodedInterFrameModeInfo {
mi_row,
mi_col,
mi_size: sub_size,
use_intrabc,
avail_u,
avail_l,
left_ref_frame,
above_ref_frame,
left_intra,
above_intra,
left_single,
above_single,
skip,
skip_mode,
segment_id,
lossless,
cdef_idx,
current_q_index,
current_delta_lf,
is_inter,
inter_block: None,
};
if is_inter != 0 {
// r176: §5.11.23 prologue + §5.11.25 `read_ref_frames` +
// §7.10 `find_mv_stack` (spatial-only path) + the §5.11.23
// YMode dispatch + per-mode RefMvIdx / drl_mode loops + the
// §5.11.31 `assign_mv` body + the §5.11.27 `read_motion_mode`
// body (with §7.10.3 `has_overlappable_candidates` and
// §7.10.4 `find_warp_samples`) + the §5.11.28
// `read_interintra_mode` body (with `RefFrame[1] =
// INTRA_FRAME` grid-stamp override on the inner arm) all
// run to completion. The post-`read_interintra_mode`
// cascade (`read_compound_type` per §5.11.29 /
// `read_interpolation_filter` per §5.11.x) is the next-arc
// target — for now the §5.11.18 dispatcher surfaces an
// `InterBlockModeInfoUnsupported` Err on the `Ok(_)` arm
// so the inner per-block aggregate observable via tests is
// not dropped silently.
match self.decode_inter_block_mode_info(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip_mode,
avail_u,
avail_l,
above_ref_frame,
left_ref_frame,
above_intra,
left_intra,
above_single,
left_single,
skip_mode_frame,
seg_ref_frame_active,
seg_ref_frame_data,
seg_skip_active,
seg_globalmv_active,
reference_select,
gm_type,
gm_params,
ref_frame_sign_bias,
allow_high_precision_mv,
force_integer_mv,
use_ref_frame_mvs,
is_motion_mode_switchable,
allow_warped_motion,
is_scaled_per_ref,
enable_interintra_compound,
enable_masked_compound,
enable_jnt_comp,
order_hints,
interpolation_filter,
enable_dual_filter,
motion_field_mvs,
) {
Ok(inter_info) => {
// r190: lift the historical
// `InterBlockModeInfoUnsupported` stub. The
// §5.11.23 inter cascade
// (`read_ref_frames` / `find_mv_stack` / YMode
// dispatch / `assign_mv` / `read_motion_mode` /
// `read_interintra_mode` / `read_compound_type` /
// `read_interpolation_filter`) is fully wired as
// of r178; the per-block aggregate is now plumbed
// through [`DecodedInterFrameModeInfo::inter_block`]
// so the §5.11.5
// [`Self::decode_block_syntax`] walker can thread
// MV / ref-frame / interp-filter into
// `compute_prediction` + `residual`.
info.inter_block = Some(inter_info);
Ok(info)
}
Err(e) => Err(e),
}
} else {
// §5.11.18 `else intra_block_mode_info()` — r387: surface
// the prologue aggregate with `is_inter == 0` and
// `inter_block == None`; the §5.11.5 inter arm then runs
// the §5.11.22 composite
// ([`Self::decode_intra_block_mode_info`]) itself, since it
// owns the §5.11.22 argument set (`has_chroma` /
// `allow_screen_content_tools` / `enable_filter_intra` /
// `bit_depth` / the §8.3.2 palette neighbour ctx) that
// this dispatcher does not carry. No §5.11.22 bit is read
// here, so the caller's follow-on read continues at the
// exact §5.11.22 bit position. (Pre-r387 this arm was the
// `IntraBlockModeInfoUnsupported` stub.)
Ok(info)
}
}
/// `intra_block_mode_info()` per §5.11.22 (av1-spec p.73) — the
/// per-block intra-mode syntax composite. Reached as the `else`
/// arm of the §5.11.18 `if ( is_inter )` dispatch (for an
/// inter-frame block whose §5.11.20 `read_is_inter` returned 0);
/// the §5.11.7 keyframe / intra-only path reads `intra_frame_y_mode`
/// instead and so does NOT route through this composite (see
/// [`Self::decode_intra_frame_y_mode`]).
///
/// The spec body (av1-spec p.73) reads:
///
/// ```text
/// intra_block_mode_info( ) {
/// RefFrame[ 0 ] = INTRA_FRAME
/// RefFrame[ 1 ] = NONE
/// y_mode S()
/// YMode = y_mode
/// intra_angle_info_y( )
/// if ( HasChroma ) {
/// uv_mode S()
/// UVMode = uv_mode
/// if ( UVMode == UV_CFL_PRED ) {
/// read_cfl_alphas( )
/// }
/// intra_angle_info_uv( )
/// }
/// PaletteSizeY = 0
/// PaletteSizeUV = 0
/// if ( MiSize >= BLOCK_8X8 &&
/// Block_Width[ MiSize ] <= 64 &&
/// Block_Height[ MiSize ] <= 64 &&
/// allow_screen_content_tools )
/// palette_mode_info( )
/// filter_intra_mode_info( )
/// }
/// ```
///
/// Each leaf composes a §8.3.2 selection + §8.2.6 `S()` read:
///
/// * `y_mode` — `TileYModeCdf[ ctx = Size_Group[ MiSize ] ]` per
/// §8.3.2 (av1-spec p.376). Range `0..INTRA_MODES = 13`.
/// Distinct from the §5.11.7 keyframe `intra_frame_y_mode` CDF
/// (`TileIntraFrameYModeCdf[ abovemode ][ leftmode ]`), which
/// the [`Self::decode_intra_frame_y_mode`] reader handles.
///
/// * `intra_angle_info_y` — §5.11.42 short-circuits to
/// `AngleDeltaY = 0` when `MiSize < BLOCK_8X8` or `YMode` is not
/// directional (§5.11.44 `is_directional_mode`: `V_PRED <= YMode
/// <= D67_PRED`, i.e. `1..=8`). Otherwise reads one `S()`
/// against `TileAngleDeltaCdf[ YMode - V_PRED ]`; the decoded
/// value is biased by `MAX_ANGLE_DELTA = 3` so the returned
/// `AngleDeltaY` is in `-3..=3`.
///
/// * `uv_mode` — gated on `HasChroma`. The §8.3.2 selection picks
/// between `TileUVModeCflAllowedCdf[ YMode ]` (the §8.3.2
/// "CFL allowed" derivation: `Lossless == 1` &&
/// `get_plane_residual_size(MiSize, 1) == BLOCK_4X4`, OR
/// `!Lossless` && `Max(Block_Width, Block_Height) <= 32`) and
/// `TileUVModeCflNotAllowedCdf[ YMode ]`. The CFL-allowed row is
/// 14-wide (`UV_CFL_PRED = 13` is reachable); the CFL-not-allowed
/// row is 13-wide (`UV_CFL_PRED` is excluded).
///
/// * `read_cfl_alphas` — §5.11.45 (only on `UVMode == UV_CFL_PRED ==
/// 13`). Reads `cfl_alpha_signs` (`S()` over `TileCflSignCdf`,
/// range `0..CFL_JOINT_SIGNS = 8`), derives the joint sign
/// components `signU = (signs + 1) / 3`, `signV = (signs + 1) %
/// 3` (each in `0..3`: `CFL_SIGN_ZERO = 0`, `CFL_SIGN_NEG = 1`,
/// `CFL_SIGN_POS = 2`). Per axis: if the sign is non-zero, read
/// one `S()` against `TileCflAlphaCdf[ ctx ]` (with `ctx` =
/// [`cfl_alpha_u_ctx`] / [`cfl_alpha_v_ctx`]) and compute
/// `CflAlpha{U,V} = (signed) (1 + cfl_alpha_*)`. When zero,
/// `CflAlpha{U,V} = 0` (no `S()` consumed).
///
/// * `intra_angle_info_uv` — §5.11.43 mirror of the luma reader
/// gated on `UVMode` being directional and `MiSize >= BLOCK_8X8`.
///
/// * `palette_mode_info` — §5.11.46. Reaches only when the outer
/// gate (`MiSize >= BLOCK_8X8` && `Block_Width <= 64` &&
/// `Block_Height <= 64` && `allow_screen_content_tools`) is true
/// AND, per-plane, `YMode == DC_PRED` (luma) / `HasChroma &&
/// UVMode == DC_PRED` (chroma). On either inner arm, reads:
///
/// - `has_palette_{y,uv}` — `S()` against
/// `TilePaletteYModeCdf[ bsizeCtx ][ ctx ]` (luma) or
/// `TilePaletteUVModeCdf[ ctx ]` (chroma). `bsizeCtx` =
/// `Mi_Width_Log2[ MiSize ] + Mi_Height_Log2[ MiSize ] - 2`
/// (in `0..PALETTE_BLOCK_SIZE_CONTEXTS = 7`); the luma `ctx`
/// is [`palette_y_mode_ctx`] (caller-passed neighbour-palette
/// booleans), the chroma `ctx` is [`palette_uv_mode_ctx`]
/// (= `(PaletteSizeY > 0) ? 1 : 0`).
///
/// - On `has_palette_* == 1`: read `palette_size_*_minus_2` —
/// `S()` against `TilePaletteYSizeCdf[ bsizeCtx ]` /
/// `TilePaletteUVSizeCdf[ bsizeCtx ]`. Decoded value is in
/// `0..PALETTE_SIZES = 7`, so `PaletteSize* = decoded + 2` is
/// in `2..=PALETTE_COLORS = 8`. After the size read, §5.11.46
/// reads the actual palette entries (`palette_colors_*[]` via
/// `L(1)` / `L(BitDepth)` / `L(2)` literals + `L(paletteBits)`
/// deltas) — this composite **does not** decode the palette
/// entries themselves; the parser-scope `BitDepth` /
/// `PaletteCache[]` plumbing the §5.11.46 entries need is not
/// yet wired through the walker (the §5.11.46 entry reads will
/// land as a subsequent arc once the walker tracks the palette
/// grids `PaletteSizes[]` / `PaletteColors[]`). Callers that
/// reach a non-zero `palette_size_*` therefore receive
/// `Err(Error::PaletteEntriesUnsupported)` immediately after
/// the size read; the size value itself is committed to the
/// §8.3 adaptation state via the `S()` read.
///
/// * `filter_intra_mode_info` — §5.11.24. Outer gate is
/// `enable_filter_intra && YMode == DC_PRED && PaletteSizeY == 0
/// && Max(Block_Width, Block_Height) <= 32`. Inner reads
/// `use_filter_intra` (`S()` against
/// `TileFilterIntraCdf[ MiSize ]`); on `use_filter_intra == 1`
/// reads `filter_intra_mode` (`S()` against
/// `TileFilterIntraModeCdf`, a context-free row coding 5 modes).
///
/// ## Grid stamping
///
/// The reader stamps `YModes[]` over the `bh4 * bw4` footprint
/// (mirroring [`Self::decode_intra_frame_y_mode`]) so neighbour
/// §8.3.2 `intra_frame_y_mode` ctx walks observe the value (the
/// §5.11.22 spec body's `YModes[r+y][c+x] = YMode` write site
/// shared with §5.11.5). The walker does NOT yet track
/// `UVModes[]`, `PaletteSizes[][]`, `PaletteColors[][][]` (those
/// grids land alongside the §5.11.49 palette-tokens wiring); the
/// uv / palette values are surfaced through
/// [`DecodedIntraBlockModeInfo`] for callers that want them.
///
/// ## Caller-passed arguments
///
/// The §5.11.22 body consumes seven frame-header / sequence-header
/// scalars that the walker does not yet track:
///
/// * `lossless` — `LosslessArray[ segment_id ]` (already computed
/// in the §5.11.18 prologue / §5.11.7 prefix); drives the §8.3.2
/// `uv_mode` CFL-allowance derivation alongside §5.11.38
/// `get_plane_residual_size`.
/// * `has_chroma` — §5.11.5 `HasChroma` (depends on `NumPlanes`,
/// `subsampling_x`, `subsampling_y`, `MiRow`, `MiCol`, `bw4`,
/// `bh4`); the §5.11.22 body uses it to gate the `uv_mode` /
/// `cfl_alphas` / `intra_angle_info_uv` reads + the chroma arm
/// of `palette_mode_info`.
/// * `allow_screen_content_tools` — §5.9.5 sequence-header bit;
/// the §5.11.22 outer gate for `palette_mode_info`.
/// * `enable_filter_intra` — §5.5.2 sequence-header bit; the
/// §5.11.24 outer gate.
/// * `subsampling_x`, `subsampling_y` — §5.7.2 sequence-header
/// bits; needed only for the §8.3.2 `uv_mode` CFL-allowance
/// derivation when `lossless == 1` (the
/// `get_plane_residual_size(MiSize, 1) == BLOCK_4X4` test). On
/// the `!lossless` path the walker uses the simpler
/// `Max(Block_Width, Block_Height) <= 32` test, which doesn't
/// read either subsampling flag.
/// * `above_palette_y` / `left_palette_y` — booleans for
/// [`palette_y_mode_ctx`] (the §8.3.2 `has_palette_y` ctx is
/// `(above_palette_y as usize) + (left_palette_y as usize)`).
/// The walker doesn't yet track `PaletteSizes[][]`, so callers
/// that don't have the neighbour-palette state pass `false /
/// false` (the conformant identity for an all-fresh-state
/// tile-start block).
///
/// On success returns the full [`DecodedIntraBlockModeInfo`]
/// aggregate. Returns
/// [`crate::Error::PaletteEntriesUnsupported`] when a non-zero
/// `palette_size_*` is decoded (the §5.11.46 entry reads land in a
/// subsequent arc). Caller-bug arguments surface
/// [`crate::Error::PartitionWalkOutOfRange`]; bitstream errors
/// surface [`crate::Error::UnexpectedEnd`] /
/// [`crate::Error::SymbolExitUnderflow`].
#[allow(clippy::too_many_arguments)]
pub fn decode_intra_block_mode_info(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
// §5.11.18 / §5.11.7 already-derived per-block state:
lossless: bool,
has_chroma: bool,
// §5.9.5 / §5.5.2 sequence-header gates:
allow_screen_content_tools: bool,
enable_filter_intra: bool,
// §5.7.2 sequence-header chroma-subsampling flags (only
// consumed via §5.11.38 `get_plane_residual_size` on the
// `lossless` branch of the §8.3.2 `uv_mode` CFL-allowance
// derivation; pass `0` / `0` for an unsubsampled stream):
subsampling_x: bool,
subsampling_y: bool,
// §8.3.2 `has_palette_y` neighbour ctx booleans (`true` iff
// the corresponding neighbour block has a non-empty Y palette).
// Callers operating at a tile-start mi-aligned block with no
// pre-existing palette state pass `false / false` (the spec
// identity for unavailable neighbours).
above_palette_y: bool,
left_palette_y: bool,
// §5.11.46 / §6.7.2 `BitDepth` — the sequence-header bit-depth
// (8 / 10 / 12) the `L(BitDepth)` palette-entry literals + the
// §5.11.46 `minBits = BitDepth - 3` / `minBits = BitDepth - 4`
// derivations consume. Must be one of 8 / 10 / 12 per §5.5.2
// `color_config()` (`bit_depth = 8 + bitDepthBoolean ? 2 : 0`
// with the 12-bit conditional). The §5.11.46 palette gate
// is reached only on `allow_screen_content_tools == true`
// blocks, so callers without a real bit-depth still pass 8 on
// gate-off blocks (the value is unobserved).
bit_depth: u8,
) -> Result<DecodedIntraBlockModeInfo, crate::Error> {
// §5.11.22 caller-bug guards: every inner method re-checks
// its own bounds, but failing fast at the dispatcher keeps
// the bit stream untouched on a caller bug (no partial reads).
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.46 caller-bug guard on `bit_depth`. The §5.5.2
// `color_config()` constrains `bit_depth` to one of 8 / 10 / 12.
// The §5.11.46 palette gate (`allow_screen_content_tools`) is
// checked first below; on the gate-off path `bit_depth` is not
// consulted, so reject invalid values here as a caller-bug
// signal regardless of the gate.
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.22 line 2: `y_mode S()`. The §8.3.2 selection is
// `TileYModeCdf[ ctx = Size_Group[ MiSize ] ]`; the row is
// `INTRA_MODES + 1 = 14` wide (13 symbol slots + the §8.2.6
// counter). The §3 enumeration bounds the decoded value to
// `0..INTRA_MODES`.
let y_mode_ctx = size_group(sub_size);
let y_mode_cdf = cdfs
.y_mode_cdf(y_mode_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let y_mode = decoder.read_symbol(y_mode_cdf)? as u8;
debug_assert!(
(y_mode as usize) < INTRA_MODES,
"S() over Default_Y_Mode_Cdf yields 0..INTRA_MODES"
);
// §5.11.22 line 3: `YModes[r + y][c + x] = YMode` over the
// bh4 * bw4 footprint (av1-spec p.378 / §5.11.5). Mirrors
// [`Self::decode_intra_frame_y_mode`]'s grid-fill so the next
// block's §8.3.2 `intra_frame_y_mode` ctx walk (when the
// §5.11.7 keyframe path runs adjacent in a future tile)
// observes the value.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.y_modes[(rr * self.mi_cols + cc) as usize] = y_mode;
}
}
// §5.11.22 lines 4-16: shared with the §5.11.7 `else` arm.
self.decode_intra_mode_info_tail(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
y_mode,
lossless,
has_chroma,
allow_screen_content_tools,
enable_filter_intra,
subsampling_x,
subsampling_y,
above_palette_y,
left_palette_y,
bit_depth,
)
}
/// `intra_frame_mode_info( )` `else` (non-intrabc) arm per §5.11.7
/// (av1-spec p.65) — r280. Reached when the §5.11.7
/// [`Self::decode_use_intrabc`] read (or its `allow_intrabc ==
/// false` fall-through) yields `use_intrabc == 0`:
///
/// ```text
/// } else {
/// is_inter = 0
/// intra_frame_y_mode S()
/// YMode = intra_frame_y_mode
/// intra_angle_info_y( )
/// if ( HasChroma ) {
/// uv_mode S()
/// UVMode = uv_mode
/// if ( UVMode == UV_CFL_PRED ) {
/// read_cfl_alphas( )
/// }
/// intra_angle_info_uv( )
/// }
/// PaletteSizeY = 0
/// PaletteSizeUV = 0
/// if ( MiSize >= BLOCK_8X8 &&
/// Block_Width[ MiSize ] <= 64 &&
/// Block_Height[ MiSize ] <= 64 &&
/// allow_screen_content_tools ) {
/// palette_mode_info( )
/// }
/// filter_intra_mode_info( )
/// }
/// ```
///
/// The body is identical to the §5.11.22
/// [`Self::decode_intra_block_mode_info`] composite from
/// `intra_angle_info_y( )` onward (same syntax elements, same CDF
/// rows — shared via the factored tail reader); the two differ
/// ONLY in the leading Y-mode element. §5.11.7 codes
/// `intra_frame_y_mode` against
/// `TileIntraFrameYModeCdf[ abovemode ][ leftmode ]` — the
/// §8.3.2 neighbour-mode ctx pair the [`Self::decode_intra_frame_y_mode`]
/// leaf derives from the walker's own `YModes[][]` grid — where
/// §5.11.22 codes `y_mode` against
/// `TileYModeCdf[ Size_Group[ MiSize ] ]`. The `is_inter = 0`
/// assignment is a no-bit fixed value (callers stamp the §5.11.5
/// `IsInters[][]` grid with `0`; the leaf's `YModes[][]` stamp
/// happens inside [`Self::decode_intra_frame_y_mode`]).
///
/// Caller-passed arguments are exactly the
/// [`Self::decode_intra_block_mode_info`] set (see that method's
/// documentation for the per-argument §5.11.x semantics); the
/// returned [`DecodedIntraBlockModeInfo`] aggregate is likewise
/// shared (its `ref_frame` carries the §5.11.7-prefix
/// `RefFrame[ 0 ] = INTRA_FRAME` / `RefFrame[ 1 ] = NONE` pair,
/// which the prefix sets on both §5.11.7 arms before the
/// `use_intrabc` element).
///
/// Mirrored by the encoder's
/// [`crate::encoder::block_mode_info::write_intra_frame_else_arm`].
/// Error surface matches [`Self::decode_intra_block_mode_info`].
#[allow(clippy::too_many_arguments)]
pub fn decode_intra_frame_mode_info_else_arm(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
lossless: bool,
has_chroma: bool,
allow_screen_content_tools: bool,
enable_filter_intra: bool,
subsampling_x: bool,
subsampling_y: bool,
above_palette_y: bool,
left_palette_y: bool,
bit_depth: u8,
) -> Result<DecodedIntraBlockModeInfo, crate::Error> {
// §5.11.46 caller-bug guard on `bit_depth` (mirrors the
// §5.11.22 dispatcher: fail fast BEFORE the first S() read so
// the bit stream stays untouched on a caller bug). The
// `sub_size` / `mi_row` / `mi_col` bounds are guarded inside
// [`Self::decode_intra_frame_y_mode`] before its read fires.
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.7: `intra_frame_y_mode S()` — the leaf derives the
// §8.3.2 `abovemode` / `leftmode` ctx pair from the walker's
// `YModes[][]` grid + tile geometry and stamps the decoded
// `YMode` over the block's footprint.
let y_mode = self.decode_intra_frame_y_mode(decoder, cdfs, mi_row, mi_col, sub_size)?;
// §5.11.7 body from `intra_angle_info_y( )` onward — identical
// to the §5.11.22 tail element-for-element.
self.decode_intra_mode_info_tail(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
y_mode,
lossless,
has_chroma,
allow_screen_content_tools,
enable_filter_intra,
subsampling_x,
subsampling_y,
above_palette_y,
left_palette_y,
bit_depth,
)
}
/// Shared §5.11.22 / §5.11.7-`else`-arm tail reader — everything
/// after the per-arm Y-mode S() (`y_mode` with the
/// `Size_Group[ MiSize ]` ctx on §5.11.22, `intra_frame_y_mode`
/// with the §8.3.2 neighbour-mode ctx pair on §5.11.7). Both spec
/// bodies continue identically from `intra_angle_info_y( )`
/// onward, so the tail is factored once and called by both
/// composites. The caller has already decoded `y_mode`, stamped
/// the `YModes[][]` grid, and validated `sub_size` / `mi_row` /
/// `mi_col` / `bit_depth`.
#[allow(clippy::too_many_arguments)]
fn decode_intra_mode_info_tail(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
y_mode: u8,
lossless: bool,
has_chroma: bool,
allow_screen_content_tools: bool,
enable_filter_intra: bool,
subsampling_x: bool,
subsampling_y: bool,
above_palette_y: bool,
left_palette_y: bool,
bit_depth: u8,
) -> Result<DecodedIntraBlockModeInfo, crate::Error> {
// §5.11.22 line 1 / §5.11.7 prefix: `RefFrame[ 0 ] =
// INTRA_FRAME, RefFrame[ 1 ] = NONE`. INTRA_FRAME = 0 per
// §6.10.1, NONE = -1 per §3. Both composites stamp the same
// pair, so the assignment lives in the shared tail.
let ref_frame: [i32; 2] = [0, -1];
// §5.11.22 line 4: `intra_angle_info_y( )` per §5.11.42.
// Short-circuits to `AngleDeltaY = 0` when `MiSize < BLOCK_8X8`
// OR `YMode` is not directional (§5.11.44: `V_PRED = 1 <= YMode
// <= D67_PRED = 8`). Otherwise reads `S()` against
// `TileAngleDeltaCdf[ YMode - V_PRED ]`; decoded value (in
// `0..(2 * MAX_ANGLE_DELTA + 1) = 7`) is biased by
// `MAX_ANGLE_DELTA = 3` so `AngleDeltaY` is in `-3..=3`.
let angle_delta_y: i8 = if sub_size >= BLOCK_8X8 && is_directional(y_mode) {
let row = cdfs
.angle_delta_cdf(y_mode as usize)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let raw = decoder.read_symbol(row)? as i32;
debug_assert!(
(0..(2 * MAX_ANGLE_DELTA + 1) as i32).contains(&raw),
"S() over Default_Angle_Delta_Cdf yields 0..(2 * MAX_ANGLE_DELTA + 1)"
);
(raw - MAX_ANGLE_DELTA as i32) as i8
} else {
0
};
// §5.11.22 lines 5-12: `if ( HasChroma )` arm.
let mut uv_mode_out: Option<u8> = None;
let mut cfl_alpha_u_out: Option<i8> = None;
let mut cfl_alpha_v_out: Option<i8> = None;
let mut angle_delta_uv_out: Option<i8> = None;
if has_chroma {
// §5.11.22 line 6: `uv_mode S()`. §8.3.2 selection picks
// between the CFL-allowed and CFL-not-allowed rows per the
// §8.3.2 paragraph:
//
// • Lossless == 1 AND get_plane_residual_size(MiSize, 1)
// == BLOCK_4X4 ⇒ CFL allowed
// • Lossless == 0 AND Max(Block_Width, Block_Height) <=
// 32 ⇒ CFL allowed
// • else ⇒ CFL not allowed
//
// [`cfl_allowed_for_uv_mode`] performs the derivation
// against `lossless` / `mi_size` / subsampling flags.
let cfl_allowed =
cfl_allowed_for_uv_mode(lossless, sub_size, subsampling_x, subsampling_y);
let uv_cdf = cdfs
.uv_mode_cdf(cfl_allowed, y_mode as usize)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let uv_mode = decoder.read_symbol(uv_cdf)? as u8;
// §3 enumeration bound: CFL-allowed row width is 14
// (UV_CFL_PRED = 13 reachable); CFL-not-allowed row width
// is 13 (UV_CFL_PRED excluded).
debug_assert!(
(uv_mode as usize)
< if cfl_allowed {
UV_INTRA_MODES_CFL_ALLOWED
} else {
UV_INTRA_MODES_CFL_NOT_ALLOWED
},
"S() over Default_Uv_Mode_*_Cdf yields 0..width"
);
uv_mode_out = Some(uv_mode);
// §5.11.22 line 7: `UVModes[ r + y ][ c + x ] = UVMode` over
// the block's `bh4 * bw4` mi footprint (mirrors the §5.11.22
// `YModes[]` stamp). Feeds the §7.11.2.8 chroma
// `get_filter_type( plane )` neighbour smooth-mode check.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.uv_modes[(rr * self.mi_cols + cc) as usize] = uv_mode;
}
}
// §5.11.22 line 8: `if ( UVMode == UV_CFL_PRED )
// read_cfl_alphas( )`. UV_CFL_PRED = 13 per §3.
if uv_mode as usize == UV_CFL_PRED {
// §5.11.45: `cfl_alpha_signs S()` over `TileCflSignCdf`
// (8-symbol context-free row).
let signs_cdf = cdfs.cfl_sign_cdf();
let signs = decoder.read_symbol(signs_cdf)?;
debug_assert!(
(signs as usize) < CFL_JOINT_SIGNS,
"S() over Default_Cfl_Sign_Cdf yields 0..CFL_JOINT_SIGNS"
);
// §5.11.45: signU = (signs + 1) / 3, signV = (signs +
// 1) % 3. Each in `0..3` (CFL_SIGN_ZERO = 0,
// CFL_SIGN_NEG = 1, CFL_SIGN_POS = 2).
let sign_u = ((signs + 1) / 3) as usize;
let sign_v = ((signs + 1) % 3) as usize;
// §5.11.45 U arm: `if ( signU != CFL_SIGN_ZERO )
// cfl_alpha_u S(); CflAlphaU = 1 + cfl_alpha_u; if (
// signU == CFL_SIGN_NEG ) CflAlphaU = -CflAlphaU. else
// CflAlphaU = 0.` CFL_SIGN_ZERO = 0; CFL_SIGN_NEG = 1.
let cfl_alpha_u: i8 = if sign_u != 0 {
let ctx_u = cfl_alpha_u_ctx(sign_u, sign_v);
let row_u = cdfs.cfl_alpha_cdf(ctx_u);
let raw_u = decoder.read_symbol(row_u)? as i32;
debug_assert!(
(raw_u as usize) < CFL_ALPHABET_SIZE,
"S() over Default_Cfl_Alpha_Cdf yields 0..CFL_ALPHABET_SIZE"
);
let signed = (1 + raw_u) as i8;
if sign_u == 1 {
-signed
} else {
signed
}
} else {
0
};
// §5.11.45 V arm: mirror of U.
let cfl_alpha_v: i8 = if sign_v != 0 {
let ctx_v = cfl_alpha_v_ctx(sign_u, sign_v);
let row_v = cdfs.cfl_alpha_cdf(ctx_v);
let raw_v = decoder.read_symbol(row_v)? as i32;
debug_assert!(
(raw_v as usize) < CFL_ALPHABET_SIZE,
"S() over Default_Cfl_Alpha_Cdf yields 0..CFL_ALPHABET_SIZE"
);
let signed = (1 + raw_v) as i8;
if sign_v == 1 {
-signed
} else {
signed
}
} else {
0
};
cfl_alpha_u_out = Some(cfl_alpha_u);
cfl_alpha_v_out = Some(cfl_alpha_v);
}
// §5.11.22 line 10: `intra_angle_info_uv( )` per §5.11.43.
// Mirror of the luma reader (same short-circuit + same
// `S()` against `TileAngleDeltaCdf[ UVMode - V_PRED ]`).
if sub_size >= BLOCK_8X8 && is_directional(uv_mode) {
let row = cdfs
.angle_delta_cdf(uv_mode as usize)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let raw = decoder.read_symbol(row)? as i32;
debug_assert!(
(0..(2 * MAX_ANGLE_DELTA + 1) as i32).contains(&raw),
"S() over Default_Angle_Delta_Cdf yields 0..(2 * MAX_ANGLE_DELTA + 1)"
);
angle_delta_uv_out = Some((raw - MAX_ANGLE_DELTA as i32) as i8);
} else {
angle_delta_uv_out = Some(0);
}
}
// §5.11.22 lines 11-12: `PaletteSizeY = 0, PaletteSizeUV = 0`.
// The §5.11.46 inner arms overwrite the locals when the
// luma / chroma `has_palette_*` arm fires; on every other
// success-path branch they stay at 0 so the §5.11.24 outer-gate
// `PaletteSizeY == 0` check below trivially passes.
let mut palette_size_y: u32 = 0;
let mut palette_size_uv: u32 = 0;
let mut has_palette_y_out: Option<u8> = None;
let mut palette_size_y_out: Option<u8> = None;
let mut palette_colors_y_out: Option<[u16; PALETTE_COLORS]> = None;
let mut has_palette_uv_out: Option<u8> = None;
let mut palette_size_uv_out: Option<u8> = None;
let mut palette_colors_u_out: Option<[u16; PALETTE_COLORS]> = None;
let mut palette_colors_v_out: Option<[u16; PALETTE_COLORS]> = None;
// §5.11.22 line 13-15: `palette_mode_info( )` outer gate.
// `MiSize >= BLOCK_8X8 && Block_Width <= 64 && Block_Height
// <= 64 && allow_screen_content_tools`. The first three are
// covered by the §9.3 mapping (BLOCK_8X8 = 3 onwards has
// Block_W/H >= 8; sizes with Block_W > 64 are
// BLOCK_64X128 / BLOCK_128X64 / BLOCK_128X128 indices 13..15;
// the extended sizes 16..21 all have Block_W <= 64 and
// Block_H <= 64 per the §9.3 table).
let bw = block_width(sub_size) as u32;
let bh = block_height(sub_size) as u32;
let palette_outer_gate =
sub_size >= BLOCK_8X8 && bw <= 64 && bh <= 64 && allow_screen_content_tools;
if palette_outer_gate {
// §5.11.46: `bsizeCtx = Mi_Width_Log2[ MiSize ] +
// Mi_Height_Log2[ MiSize ] - 2`. The §9.3 mapping ensures
// `bsizeCtx ∈ 0..PALETTE_BLOCK_SIZE_CONTEXTS = 7` for any
// MiSize that passed the outer gate (BLOCK_8X8 has
// (1, 1) ⇒ bsizeCtx = 0; the largest reachable is
// BLOCK_64X64's (4, 4) ⇒ bsizeCtx = 6).
let bsize_ctx = mi_width_log2(sub_size) + mi_height_log2(sub_size) - 2;
debug_assert!(
bsize_ctx < PALETTE_BLOCK_SIZE_CONTEXTS,
"§5.11.46 bsizeCtx must be in 0..PALETTE_BLOCK_SIZE_CONTEXTS"
);
// §5.11.46 luma arm: `if ( YMode == DC_PRED )`. DC_PRED = 0.
if y_mode == 0 {
// §5.11.46: `has_palette_y S()` against
// `TilePaletteYModeCdf[ bsizeCtx ][ ctx ]`. The §8.3.2
// ctx is `(above_palette_y as usize) +
// (left_palette_y as usize)`.
let ctx_y = palette_y_mode_ctx(above_palette_y, left_palette_y);
let row = cdfs.palette_y_mode_cdf(bsize_ctx, ctx_y);
let has_palette_y = decoder.read_symbol(row)? as u8;
debug_assert!(
has_palette_y <= 1,
"S() over Default_Palette_Y_Mode_Cdf yields 0 or 1"
);
has_palette_y_out = Some(has_palette_y);
if has_palette_y == 1 {
// §5.11.46: `palette_size_y_minus_2 S()` against
// `TilePaletteYSizeCdf[ bsizeCtx ]`. Decoded value
// is in `0..PALETTE_SIZES = 7`; PaletteSizeY = +2
// is in `2..=PALETTE_COLORS = 8`.
let row_sz = cdfs.palette_y_size_cdf(bsize_ctx);
let minus_2 = decoder.read_symbol(row_sz)?;
debug_assert!(
(minus_2 as usize) < PALETTE_SIZES,
"S() over Default_Palette_Y_Size_Cdf yields 0..PALETTE_SIZES"
);
palette_size_y = minus_2 + 2;
palette_size_y_out = Some(palette_size_y as u8);
// §5.11.46 palette_colors_y[] reads — §5.11.49
// cache merge + cache-coded indices + new-color
// literal + delta loop. Decoded entries are sorted
// in ascending order and committed to
// `palette_colors_y_out` + the walker grid.
let entries = read_palette_entries_y(
decoder,
bit_depth,
palette_size_y as usize,
self,
mi_row,
mi_col,
)?;
palette_colors_y_out = Some(entries);
}
}
// §5.11.46 chroma arm: `if ( HasChroma && UVMode ==
// DC_PRED )`. DC_PRED = 0. The chroma arm reads
// `palette_size_uv_minus_2` against a `bsizeCtx`-keyed row
// — the §8.3.2 chroma `bsizeCtx` is the SAME `bsizeCtx`
// derived from `MiSize` above. The chroma `has_palette_uv`
// ctx is `(PaletteSizeY > 0) ? 1 : 0` per [`palette_uv_mode_ctx`].
if has_chroma && uv_mode_out == Some(0) {
let ctx_uv = palette_uv_mode_ctx(palette_size_y as usize);
let row = cdfs.palette_uv_mode_cdf(ctx_uv);
let has_palette_uv = decoder.read_symbol(row)? as u8;
debug_assert!(
has_palette_uv <= 1,
"S() over Default_Palette_Uv_Mode_Cdf yields 0 or 1"
);
has_palette_uv_out = Some(has_palette_uv);
if has_palette_uv == 1 {
let row_sz = cdfs.palette_uv_size_cdf(bsize_ctx);
let minus_2 = decoder.read_symbol(row_sz)?;
debug_assert!(
(minus_2 as usize) < PALETTE_SIZES,
"S() over Default_Palette_Uv_Size_Cdf yields 0..PALETTE_SIZES"
);
palette_size_uv = minus_2 + 2;
palette_size_uv_out = Some(palette_size_uv as u8);
// §5.11.46 palette_colors_u[] + palette_colors_v[]
// reads — both planes share the same PaletteSizeUV.
// U uses the same cache + literal + delta path as Y
// (Y plane minus the `delta++` step per spec). V
// uses either `delta_encode_palette_colors_v == 1`
// (signed delta with sign bit + modular wrap +
// Clip1, NO trailing sort) or `== 0` (PaletteSizeUV
// direct L(BitDepth) literals).
let (entries_u, entries_v) = read_palette_entries_uv(
decoder,
bit_depth,
palette_size_uv as usize,
self,
mi_row,
mi_col,
)?;
palette_colors_u_out = Some(entries_u);
palette_colors_v_out = Some(entries_v);
}
}
}
// §5.11.46 / §5.11.49 grid-fill: stamp `PaletteSizes[plane][r +
// y][c + x] = PaletteSize{Y,UV}` and `PaletteColors[plane][r +
// y][c + x][..] = palette_colors_{y,u,v}[..]` over the
// `bh4 * bw4` footprint so subsequent §5.11.49
// `get_palette_cache(plane)` neighbour reads observe the
// decoded palette state. Stamping happens regardless of the
// luma / chroma arm firing because the §5.11.22 lines 11-12
// initialisers say `PaletteSizeY = 0` / `PaletteSizeUV = 0`
// when no arm fires — and the constructor already zero-filled
// every cell, so stamping zeroes back is a no-op on those
// cases (we only stamp non-zero entries).
if palette_size_y > 0 || palette_size_uv > 0 {
let bw4_pal = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4_pal = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
let area = (self.mi_rows as usize) * (self.mi_cols as usize);
for dr in 0..bh4_pal {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4_pal {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr as usize) * (self.mi_cols as usize) + (cc as usize);
// Luma plane.
if let Some(entries) = palette_colors_y_out {
self.ensure_palette_colors();
self.palette_sizes[cell] = palette_size_y as u8;
let base = cell * PALETTE_COLORS;
for (i, &v) in entries.iter().enumerate().take(PALETTE_COLORS) {
self.palette_colors[base + i] = v;
}
}
// U plane.
if let Some(entries) = palette_colors_u_out {
self.ensure_palette_colors();
let plane_cell = area + cell;
self.palette_sizes[plane_cell] = palette_size_uv as u8;
let base = plane_cell * PALETTE_COLORS;
for (i, &v) in entries.iter().enumerate().take(PALETTE_COLORS) {
self.palette_colors[base + i] = v;
}
}
// V plane.
if let Some(entries) = palette_colors_v_out {
self.ensure_palette_colors();
let plane_cell = 2 * area + cell;
self.palette_sizes[plane_cell] = palette_size_uv as u8;
let base = plane_cell * PALETTE_COLORS;
for (i, &v) in entries.iter().enumerate().take(PALETTE_COLORS) {
self.palette_colors[base + i] = v;
}
}
}
}
}
// §5.11.22 line 16: `filter_intra_mode_info( )` per §5.11.24.
// Outer gate: `enable_filter_intra && YMode == DC_PRED &&
// PaletteSizeY == 0 && Max(Block_Width, Block_Height) <= 32`.
let mut use_filter_intra_out: Option<u8> = None;
let mut filter_intra_mode_out: Option<u8> = None;
if enable_filter_intra && y_mode == 0 && palette_size_y == 0 && bw.max(bh) <= 32 {
// §5.11.24: `use_filter_intra S()` against
// `TileFilterIntraCdf[ MiSize ]`.
let row = cdfs.filter_intra_cdf(sub_size);
let use_filter_intra = decoder.read_symbol(row)? as u8;
debug_assert!(
use_filter_intra <= 1,
"S() over Default_Filter_Intra_Cdf yields 0 or 1"
);
use_filter_intra_out = Some(use_filter_intra);
if use_filter_intra == 1 {
// §5.11.24: `filter_intra_mode S()` against
// `TileFilterIntraModeCdf` (context-free row coding 5
// modes).
let row = cdfs.filter_intra_mode_cdf();
let mode = decoder.read_symbol(row)? as u8;
debug_assert!(
(mode as usize) < INTRA_FILTER_MODES,
"S() over Default_Filter_Intra_Mode_Cdf yields 0..INTRA_FILTER_MODES"
);
filter_intra_mode_out = Some(mode);
}
}
Ok(DecodedIntraBlockModeInfo {
mi_row,
mi_col,
mi_size: sub_size,
ref_frame,
y_mode,
angle_delta_y,
uv_mode: uv_mode_out,
cfl_alpha_u: cfl_alpha_u_out,
cfl_alpha_v: cfl_alpha_v_out,
angle_delta_uv: angle_delta_uv_out,
has_palette_y: has_palette_y_out,
palette_size_y: palette_size_y_out,
palette_colors_y: palette_colors_y_out,
has_palette_uv: has_palette_uv_out,
palette_size_uv: palette_size_uv_out,
palette_colors_u: palette_colors_u_out,
palette_colors_v: palette_colors_v_out,
use_filter_intra: use_filter_intra_out,
filter_intra_mode: filter_intra_mode_out,
})
}
/// `inter_block_mode_info()` per §5.11.23 (av1-spec p.74) — the
/// per-block inter-mode syntax composite. Reached as the
/// `if ( is_inter )` arm of the §5.11.18 dispatcher (for an
/// inter-frame block whose §5.11.20 `read_is_inter` returned `1`);
/// the `else` arm routes to [`Self::decode_intra_block_mode_info`].
///
/// The spec body (av1-spec p.74) reads:
///
/// ```text
/// inter_block_mode_info( ) {
/// PaletteSizeY = 0
/// PaletteSizeUV = 0
/// read_ref_frames( )
/// isCompound = RefFrame[ 1 ] > INTRA_FRAME
/// find_mv_stack( isCompound )
/// … YMode dispatch (skip_mode / SEG_LVL_SKIP|GLOBALMV /
/// isCompound / single-pred) …
/// RefMvIdx = 0
/// … drl_mode loops over NumMvFound …
/// assign_mv( isCompound )
/// read_interintra_mode( isCompound )
/// read_motion_mode( isCompound )
/// read_compound_type( isCompound )
/// … interpolation_filter dispatch …
/// }
/// ```
///
/// ## r170 scope
///
/// This reader lands the §5.11.23 prologue:
///
/// 1. `PaletteSizeY = 0`, `PaletteSizeUV = 0` (spec lines 1-2 —
/// just zero-init; the values aren't surfaced as we don't yet
/// track per-block palette state on the inter path).
/// 2. **§5.11.25 `read_ref_frames()`** — fully implemented:
/// - `skip_mode` arm: `RefFrame[ 0 ] = SkipModeFrame[ 0 ]`,
/// `RefFrame[ 1 ] = SkipModeFrame[ 1 ]` (no bits read; values
/// taken from the frame-header §5.9.22 derivation).
/// - `seg_feature_active(SEG_LVL_REF_FRAME)` arm:
/// `RefFrame[ 0 ] = FeatureData[ segment_id ][ SEG_LVL_REF_FRAME ]`,
/// `RefFrame[ 1 ] = NONE` (no bits read).
/// - `seg_feature_active(SEG_LVL_SKIP | SEG_LVL_GLOBALMV)` arm:
/// `RefFrame[ 0 ] = LAST_FRAME`, `RefFrame[ 1 ] = NONE` (no
/// bits read).
/// - Default arm: reads `comp_mode` (`S()` over
/// `TileCompModeCdf[ comp_mode_ctx ]`) when
/// `reference_select && Min(bw4, bh4) >= 2`, else
/// `comp_mode = SINGLE_REFERENCE`. Then:
/// * `COMPOUND_REFERENCE` ⇒ `comp_ref_type` S() (`comp_ref_type_ctx`),
/// then `UNIDIR_COMP_REFERENCE` ⇒ `uni_comp_ref` /
/// `uni_comp_ref_p1` / `uni_comp_ref_p2` cascade against
/// `TileUniCompRefCdf[ ctx ][ p ]`, or `BIDIR_COMP_REFERENCE`
/// ⇒ `comp_ref` / `comp_ref_p1` / `comp_ref_p2` /
/// `comp_bwdref` / `comp_bwdref_p1` cascade against
/// `TileCompRefCdf[ ctx ][ p ]` and `TileCompBwdRefCdf[ ctx ][ p ]`.
/// * `SINGLE_REFERENCE` ⇒ `single_ref_p1..p6` cascade against
/// `TileSingleRefCdf[ ctx ][ p ]`, with `RefFrame[ 1 ] = NONE`.
/// 3. `isCompound = RefFrame[ 1 ] > INTRA_FRAME` (spec line 4).
/// 4. Walker grid stamp: `RefFrames[ r + y ][ c + x ][ 0..2 ]` is
/// written across the block's `bh4 * bw4` footprint so the next
/// block's §5.11.18 prologue / §8.3.2 ref-frame ctx walks
/// observe the value.
///
/// The reader then short-circuits at **§7.10.2 `find_mv_stack`**
/// with [`crate::Error::FindMvStackUnsupported`] — the
/// motion-vector-stack derivation (scan-row, scan-col,
/// temporal-scan, sorting, extra-search, context-and-clamping)
/// and every §5.11.23 reader that consumes its outputs
/// (`compound_mode` / `new_mv` / `zero_mv` / `ref_mv` / `drl_mode`
/// / `assign_mv` / `read_motion_mode` / `read_interintra_mode` /
/// `read_compound_type` / interpolation-filter dispatch) are
/// subsequent-arc targets. All §5.11.25 bit reads + the walker
/// `RefFrames[][][..]` stamp commit before the stub fires; the
/// `is_compound` derivation is also surfaced.
///
/// ## Caller-passed arguments
///
/// The §5.11.25 body consumes seven frame-header / per-segment
/// scalars the walker does not yet track:
///
/// * `skip_mode` — `0` or `1`. When `1` the spec's `skip_mode` arm
/// fires (no `S()` reads; `RefFrame[ 0..2 ]` adopted from
/// `SkipModeFrame[ 0..2 ]`).
/// * `skip_mode_frame` — `[SkipModeFrame[0], SkipModeFrame[1]]`
/// per §5.9.22 (the `skipModeAllowed` derivation of the
/// uncompressed header). Only consumed on the `skip_mode == 1`
/// arm; pass `[INTRA_FRAME = 0, NONE = -1]` for callers that
/// never set `skip_mode = 1`.
/// * `seg_ref_frame_active` — `seg_feature_active(SEG_LVL_REF_FRAME)`
/// for this block's `segment_id`.
/// * `seg_ref_frame_data` — `FeatureData[ segment_id ][ SEG_LVL_REF_FRAME ]`
/// in `0..=ALTREF_FRAME = 0..=7`. Consumed when
/// `seg_ref_frame_active == 1` AND `skip_mode == 0`.
/// * `seg_skip_active` — `seg_feature_active(SEG_LVL_SKIP)`. The
/// §5.11.25 `RefFrame[ 0 ] = LAST_FRAME` arm fires on
/// `seg_skip_active || seg_globalmv_active`.
/// * `seg_globalmv_active` — `seg_feature_active(SEG_LVL_GLOBALMV)`.
/// * `reference_select` — §5.9.23 `frame_reference_mode` bit. When
/// `false` the §5.11.25 default arm forces
/// `comp_mode = SINGLE_REFERENCE` with no bit read.
#[allow(clippy::too_many_arguments)]
pub fn decode_inter_block_mode_info(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
// §5.11.18 / §5.11.20 already-derived state:
skip_mode: u8,
// §5.11.18 prologue derivations needed for §8.3.2 ref-frame ctxes:
avail_u: bool,
avail_l: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
above_intra: bool,
left_intra: bool,
above_single: bool,
left_single: bool,
// §5.11.25 caller-supplied frame-header / per-segment scalars:
skip_mode_frame: [i32; 2],
seg_ref_frame_active: bool,
seg_ref_frame_data: i32,
seg_skip_active: bool,
seg_globalmv_active: bool,
reference_select: bool,
// r173 §7.10 caller-supplied frame-header scalars (see
// [`Self::find_mv_stack`]).
gm_type: [i32; 8],
gm_params: [[i32; 6]; 8],
ref_frame_sign_bias: [i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
use_ref_frame_mvs: bool,
// r175 §5.11.27 caller-supplied frame-header scalars.
//
// * `is_motion_mode_switchable` — §5.9.2 frame-header bit. When
// `false` the §5.11.27 body short-circuits to
// `motion_mode = SIMPLE` with no S() read.
// * `allow_warped_motion` — §5.9.2 frame-header bit. When
// `false` the §5.11.27 post-`find_warp_samples` arm forces
// the `use_obmc` S() reader (instead of `motion_mode` S()).
// * `is_scaled_per_ref` — frame-header derived: for each
// `refFrame in LAST_FRAME..=ALTREF_FRAME` (1..=7), whether
// the §5.11.27 `is_scaled(refFrame)` predicate (av1-spec
// p.79) is true. The caller computes this from
// `RefUpscaledWidth[ref_frame_idx[refFrame - LAST_FRAME]]` /
// `RefFrameHeight[..]` / `FrameWidth` / `FrameHeight` once
// per frame; passing `[false; 7]` matches a no-scaling-
// reference-pool case and is the conformant default for
// single-tile / single-resolution streams.
is_motion_mode_switchable: bool,
allow_warped_motion: bool,
is_scaled_per_ref: [bool; 7],
// r176 §5.11.28 caller-supplied frame-header scalar.
//
// * `enable_interintra_compound` — §5.5.2 sequence-header bit
// (`SequenceHeader::enable_interintra_compound`). When
// `false` the §5.11.28 outer gate is closed, no S() bits are
// read, and the `interintra` field of the returned
// [`DecodedInterBlockModeInfo::interintra`] aggregate is `0`.
enable_interintra_compound: bool,
// r177 §5.11.29 caller-supplied frame-header / per-block
// scalars.
//
// * `enable_masked_compound` — §5.5.2 sequence-header bit
// (`SequenceHeader::enable_masked_compound`). When `false`
// the §5.11.29 inner `comp_group_idx` S() is skipped (the
// pre-set `comp_group_idx = 0` is kept).
// * `enable_jnt_comp` — §5.5.2 sequence-header bit
// (`SequenceHeader::enable_jnt_comp`). When `false` the
// §5.11.29 `comp_group_idx == 0` arm short-circuits to
// `compound_type = COMPOUND_AVERAGE` (no `compound_idx`
// S() read).
// * `order_hints` — the §5.9.2 `OrderHint` / `OrderHints[]`
// frame-header scalars. The §8.3.2 `compound_idx` ctx seed
// (`fwd = Abs(get_relative_dist(OrderHints[RefFrame[0]],
// OrderHint))`, `bck = ..RefFrame[1]..`, `ctx = (fwd == bck)
// ? 3 : 0`) is derived per BLOCK from the block's own
// reference pair at the §5.11.29 read (consumed only on the
// `enable_jnt_comp` arm). For callers that never read
// `compound_idx`, [`FrameInterOrderHints::IDENTITY`] is
// conformant.
enable_masked_compound: bool,
enable_jnt_comp: bool,
order_hints: FrameInterOrderHints,
// r178 §5.11.x caller-supplied scalars.
//
// * `interpolation_filter` — §5.9.10 frame-header value in
// `EIGHTTAP..=SWITCHABLE = 0..=4`. When != `SWITCHABLE` every
// inter block on the frame inherits the value with no per-
// block bits read; when == `SWITCHABLE` the §5.11.x reader
// selects per-direction filters via S() against
// `TileInterpFilterCdf[ ctx ]`.
// * `enable_dual_filter` — §5.5.2 sequence-header bit. When
// `false` the §5.11.x switchable arm reads only one symbol
// (slot 0) and mirrors it into slot 1; when `true` it reads
// two independent symbols.
interpolation_filter: u8,
enable_dual_filter: bool,
// r205 §7.10.2.5 / §7.10.2.6 caller-supplied motion-field
// grid. Only consulted when `use_ref_frame_mvs == true`; on a
// freshly-initialised `MotionFieldMvs::new_invalid` grid
// every cell is the `MFMV_INVALID` sentinel so the §7.10.2.6
// body short-circuits at every step.
motion_field_mvs: &MotionFieldMvs,
) -> Result<DecodedInterBlockModeInfo, crate::Error> {
// §5.11.23 caller-bug guards. Each inner method re-checks its
// own bounds, but failing fast at the dispatcher keeps the
// bit stream untouched on a caller bug.
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(0..=7).contains(&seg_ref_frame_data) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(-1..=7).contains(&skip_mode_frame[0]) || !(-1..=7).contains(&skip_mode_frame[1]) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
// §5.11.23 lines 1-2: `PaletteSizeY = 0`, `PaletteSizeUV = 0`.
// Local zero-init; not surfaced through the aggregate (the
// §5.11.46 inter-arm palette reads are excluded from §5.11.23
// entirely — palette mode is intra-only).
let _palette_size_y: u32 = 0;
let _palette_size_uv: u32 = 0;
// §5.11.23 line 3: `read_ref_frames( )` per §5.11.25.
let ref_frame = self.read_ref_frames(
decoder,
cdfs,
sub_size,
skip_mode,
avail_u,
avail_l,
above_ref_frame,
left_ref_frame,
above_intra,
left_intra,
above_single,
left_single,
skip_mode_frame,
seg_ref_frame_active,
seg_ref_frame_data,
seg_skip_active,
seg_globalmv_active,
reference_select,
)?;
// §5.11.23 line 4: `isCompound = RefFrame[ 1 ] > INTRA_FRAME`.
// INTRA_FRAME = 0; NONE = -1. `NONE > 0` is false; any
// `RefFrame[1]` of `LAST_FRAME = 1..=ALTREF_FRAME = 7` makes
// isCompound true. `RefFrame[1] = INTRA_FRAME = 0` (the
// §5.11.23 inter-intra path) makes isCompound false even
// though slot 1 is occupied — this matches the spec literal.
let is_compound = ref_frame[1] > 0;
// §5.11.5 / §5.11.18 grid stamp: write `RefFrames[ r + y ][ c +
// x ][ 0..2 ] = RefFrame[ 0..2 ]` over the bh4 * bw4 footprint
// so the next block's §5.11.18 prologue / §8.3.2 ref-frame ctx
// walks observe the value. Mirrors the §5.11.7 grid-stamp
// convention applied to the y_modes / skips / etc. grids.
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = ((rr * self.mi_cols + cc) as usize) * 2;
self.ref_frames[cell] = ref_frame[0] as i8;
self.ref_frames[cell + 1] = ref_frame[1] as i8;
}
}
// §5.11.23 line 5: `find_mv_stack( isCompound )` per §7.10.2.
// r205: full §7.10.2 driver wired in (r173 spatial scans plus
// §7.10.2.5 / §7.10.2.6 temporal scan + sample, with the
// caller-supplied `motion_field_mvs` grid consulted on the
// `use_ref_frame_mvs == true` path).
let mv_stack = self.find_mv_stack(
mi_row,
mi_col,
sub_size,
ref_frame,
is_compound,
use_ref_frame_mvs,
gm_type,
gm_params,
ref_frame_sign_bias,
allow_high_precision_mv,
force_integer_mv,
motion_field_mvs,
)?;
// §5.11.23 lines 6-18: YMode dispatch (av1-spec p.74). Four
// arms in spec order — the first match fires; no bit is read
// on the short-circuit arms.
//
// The §5.11.23 spec body's segmentation conditions
// (`seg_feature_active(SEG_LVL_SKIP|GLOBALMV)`) are caller-
// supplied via `seg_skip_active` / `seg_globalmv_active`
// (matching the §5.11.20 read_is_inter convention).
let y_mode: u8 = if skip_mode != 0 {
// Arm 1: skip_mode = 1 ⇒ NEAREST_NEARESTMV (no S()).
MODE_NEAREST_NEARESTMV
} else if seg_skip_active || seg_globalmv_active {
// Arm 2: SEG_LVL_SKIP / SEG_LVL_GLOBALMV ⇒ GLOBALMV
// (no S()).
MODE_GLOBALMV
} else if is_compound {
// Arm 3: compound ⇒ `compound_mode` S() against
// `TileCompoundModeCdf[ ctx ]`, then
// `YMode = NEAREST_NEARESTMV + compound_mode`.
//
// §8.3.2 `compound_mode` ctx (av1-spec p.378): see
// [`compound_mode_ctx`].
let cm_ctx = compound_mode_ctx(
mv_stack.ref_mv_context as usize,
mv_stack.new_mv_context as usize,
);
let row = cdfs.compound_mode_cdf(cm_ctx);
let cm = decoder.read_symbol(row)? as u8;
debug_assert!(
(cm as usize) < COMPOUND_MODES,
"S() over TileCompoundModeCdf yields 0..COMPOUND_MODES"
);
MODE_NEAREST_NEARESTMV + cm
} else {
// Arm 4: single-pred ⇒ NEWMV / GLOBALMV / NEARESTMV /
// NEARMV cascade. Three S() reads gated on the binary
// outcomes of `new_mv` / `zero_mv` / `ref_mv`.
let new_mv_row = cdfs.new_mv_cdf(mv_stack.new_mv_context as usize);
let new_mv = decoder.read_symbol(new_mv_row)? as u8;
debug_assert!(new_mv <= 1, "S() over TileNewMvCdf yields 0 or 1");
if new_mv == 0 {
MODE_NEWMV
} else {
let zero_mv_row = cdfs.zero_mv_cdf(mv_stack.zero_mv_context as usize);
let zero_mv = decoder.read_symbol(zero_mv_row)? as u8;
debug_assert!(zero_mv <= 1, "S() over TileZeroMvCdf yields 0 or 1");
if zero_mv == 0 {
MODE_GLOBALMV
} else {
let ref_mv_row = cdfs.ref_mv_cdf(mv_stack.ref_mv_context as usize);
let ref_mv = decoder.read_symbol(ref_mv_row)? as u8;
debug_assert!(ref_mv <= 1, "S() over TileRefMvCdf yields 0 or 1");
if ref_mv == 0 {
MODE_NEARESTMV
} else {
MODE_NEARMV
}
}
}
};
// §5.11.23 lines 19-32: `RefMvIdx = 0`; per-mode drl_mode
// loops. Two outer arms — `YMode == NEWMV || YMode ==
// NEW_NEWMV` (av1-spec p.74) and the `has_nearmv( )` arm
// (av1-spec p.74-75 `has_nearmv` helper). On each iteration the
// §8.3.2 `drl_mode` selection is `TileDrlModeCdf[ DrlCtxStack[
// idx ] ]` (the §7.10.2.14 per-position context).
let mut ref_mv_idx: u32 = 0;
if y_mode == MODE_NEWMV || y_mode == MODE_NEW_NEWMV {
// Spec body:
// for ( idx = 0; idx < 2; idx++ )
// if ( NumMvFound > idx + 1 ) {
// drl_mode S()
// if ( drl_mode == 0 ) { RefMvIdx = idx; break }
// RefMvIdx = idx + 1
// }
for idx in 0u32..2 {
if mv_stack.num_mv_found > idx + 1 {
let drl_ctx = mv_stack.drl_ctx_stack[idx as usize] as usize;
let row = cdfs.drl_mode_cdf(drl_ctx);
let drl = decoder.read_symbol(row)? as u8;
debug_assert!(drl <= 1, "S() over TileDrlModeCdf yields 0 or 1");
if drl == 0 {
ref_mv_idx = idx;
break;
}
ref_mv_idx = idx + 1;
}
}
} else if has_nearmv(y_mode) {
// Spec body:
// RefMvIdx = 1
// for ( idx = 1; idx < 3; idx++ )
// if ( NumMvFound > idx + 1 ) {
// drl_mode S()
// if ( drl_mode == 0 ) { RefMvIdx = idx; break }
// RefMvIdx = idx + 1
// }
ref_mv_idx = 1;
for idx in 1u32..3 {
if mv_stack.num_mv_found > idx + 1 {
let drl_ctx = mv_stack.drl_ctx_stack[idx as usize] as usize;
let row = cdfs.drl_mode_cdf(drl_ctx);
let drl = decoder.read_symbol(row)? as u8;
debug_assert!(drl <= 1, "S() over TileDrlModeCdf yields 0 or 1");
if drl == 0 {
ref_mv_idx = idx;
break;
}
ref_mv_idx = idx + 1;
}
}
}
// §5.11.23 line 33: `assign_mv( isCompound )` per §5.11.31.
// r174 wires the §5.11.31 / §5.11.32 `read_mv` /
// `read_mv_component` cascade. The inter path always has
// `use_intrabc == 0` (the §5.11.7 intra-bc arm is the only
// caller for `use_intrabc == 1` and lives behind a separate
// dispatcher). Pass `use_intrabc = false` so the §5.11.31 body's
// GLOBALMV / NEAREST / NEAR / NEWMV per-list cascade fires.
let mv = self.assign_mv(
decoder,
cdfs,
is_compound,
/* use_intrabc = */ false,
y_mode,
ref_mv_idx,
mv_stack.num_mv_found,
&mv_stack.ref_stack_mv,
&mv_stack.global_mvs,
allow_high_precision_mv,
force_integer_mv,
)?;
// §5.11.5 / §5.11.31 grid stamp: write the per-list MV over
// the bh4 * bw4 footprint so §7.10.2 neighbour walks for the
// next block observe the decoded values. The §7.10 component
// ordering is `mvs[ref][row][col]`; cast i32 → i16 (the
// §6.10.27 conformance bound `Abs( Mv[ i ][ comp ] ) < ( 1 <<
// 14 )` keeps the value in i16 range).
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr * self.mi_cols + cc) as usize;
// [row][list][comp] flat layout: `(cell * 2 + list) * 2 + comp`.
self.mvs[(cell * 2) * 2] = mv[0][0] as i16;
self.mvs[(cell * 2) * 2 + 1] = mv[0][1] as i16;
self.mvs[(cell * 2 + 1) * 2] = mv[1][0] as i16;
self.mvs[(cell * 2 + 1) * 2 + 1] = mv[1][1] as i16;
// §5.11.5 `YModes[ r + y ][ c + x ] = YMode` — the
// inter-arm grid-fill. §7.10.2.8 reads it back as
// `candMode` (the `has_newmv( candMode )` NewMvCount
// increment driving the §7.10.2.14 `NewMvContext`
// derivation, and the GLOBALMV `candMv` special case),
// and the §7.14.4 deblock strength selection reads it
// as `mode` for the `Mode_Deltas` modeType split.
// (r390 fix: the inter arm previously left the grid at
// its DC_PRED prefill, so a NEWMV neighbour never
// advanced `NewMvCount` and every `new_mv` read after
// one used ctx `5` where §7.10.2.14 requires `4` —
// desynchronising the arithmetic decoder on the first
// inter frame with a NEWMV-coded neighbour.)
self.y_modes[cell] = y_mode;
}
}
// §5.11.23 line 33: `read_interintra_mode( isCompound )` per
// §5.11.28 (av1-spec p.79-80). Reads the inter-intra blending
// triple gated on `enable_interintra_compound && !isCompound &&
// BLOCK_8X8 <= MiSize <= BLOCK_32X32`.
//
// ORDER MATTERS: §5.11.23 (av1-spec p.74) lists
// `read_interintra_mode( )` BEFORE `read_motion_mode( )` —
// the inner `if ( interintra )` arm overrides `RefFrame[ 1 ] =
// INTRA_FRAME`, and the §5.11.27 body's `RefFrame[ 1 ] ==
// INTRA_FRAME` short-circuit consumes that override (it is the
// ONLY way slot 1 ever becomes INTRA_FRAME — the §5.11.25
// reader produces `NONE = -1` or `1..=7`). Reading motion mode
// first would consume a `use_obmc` / `motion_mode` symbol the
// encoder never coded on interintra blocks. (r277 fix; the
// r175-r178 wiring had the two calls swapped.)
let interintra = self.read_interintra_mode(
decoder,
cdfs,
sub_size,
skip_mode,
is_compound,
enable_interintra_compound,
)?;
let ref_frame = if interintra.interintra == 1 {
// §5.11.28: `RefFrame[ 1 ] = INTRA_FRAME = 0`. Restamp the
// grid's slot-1 entry over the block footprint so the next
// block's `RefFrames[ ..][ ..][ 1 ]` reads observe the
// override, and shadow the local pair so the §5.11.27 /
// §5.11.x readers below see the post-override state
// exactly as the spec's imperative assignment implies.
// Slot 0 is unchanged.
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = ((rr * self.mi_cols + cc) as usize) * 2;
self.ref_frames[cell + 1] = 0; // INTRA_FRAME
}
}
[ref_frame[0], 0]
} else {
ref_frame
};
// §5.11.23 line 34: `read_motion_mode( isCompound )` per §5.11.27
// (av1-spec p.79). r175 wires the §5.11.27 body and its
// §7.10.3 `has_overlappable_candidates` + §7.10.4
// `find_warp_samples` helpers; post-r178 the full
// post-`read_motion_mode` cascade (`read_compound_type` per
// §5.11.29, `read_interpolation_filter` per §5.11.x) is wired.
let num_samples = self.find_warp_samples(mi_row, mi_col, sub_size, ref_frame[0], mv);
let motion_mode = self.read_motion_mode(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip_mode,
is_compound,
ref_frame,
y_mode,
num_samples,
is_motion_mode_switchable,
allow_warped_motion,
force_integer_mv,
gm_type,
is_scaled_per_ref,
)?;
// §5.11.23 line 36: `read_compound_type( isCompound )` per
// §5.11.29 (av1-spec p.80-81). Reads the inter-inter compound
// controls (`comp_group_idx` / `compound_idx` / `compound_type`)
// plus the wedge / diffwtd sub-branch (`wedge_index` /
// `wedge_sign` or `mask_type`). On the `!isCompound`
// else-arm the §5.11.28 outcome alone selects the type
// (`interintra && wedge_interintra ? COMPOUND_WEDGE :
// COMPOUND_INTRA` or `COMPOUND_AVERAGE`); on the `skip_mode`
// short-circuit the type is forced to `COMPOUND_AVERAGE` with
// no bits read.
//
// The §8.3.2 `comp_group_idx` / `compound_idx` ctx walks
// consult `AboveRefFrame[0] == ALTREF_FRAME` / `LeftRefFrame[0]
// == ALTREF_FRAME` on their `AboveSingle` / `LeftSingle` arms;
// resolve those scalars here from the already-passed
// `above_ref_frame` / `left_ref_frame` arrays. ALTREF_FRAME =
// 7 per §6.10.x.
let above_ref_0_altref = above_ref_frame[0] == 7;
let left_ref_0_altref = left_ref_frame[0] == 7;
// §8.3.2 `compound_idx` ctx seed — per BLOCK, from the block's
// own reference pair (av1-spec p.384):
// fwd = Abs( get_relative_dist( OrderHints[ RefFrame[ 0 ] ],
// OrderHint ) )
// bck = Abs( get_relative_dist( OrderHints[ RefFrame[ 1 ] ],
// OrderHint ) )
// ctx = ( fwd == bck ) ? 3 : 0
// Only the `isCompound && comp_group_idx == 0 &&
// enable_jnt_comp` arm ever reads the ctx; the non-compound
// clamp below (a non-compound `RefFrame[1] <= 0` indexes slot
// 0) is therefore never observed.
let dist_equal = {
let hint_of = |r: i32| -> i32 {
let idx = r.clamp(0, crate::uncompressed_header_tail::ALTREF_FRAME as i32);
order_hints.order_hints_by_ref[idx as usize]
};
let fwd = crate::inter_pred::get_relative_dist(
hint_of(ref_frame[0]),
order_hints.current_order_hint,
order_hints.order_hint_bits,
)
.abs();
let bck = crate::inter_pred::get_relative_dist(
hint_of(ref_frame[1]),
order_hints.current_order_hint,
order_hints.order_hint_bits,
)
.abs();
fwd == bck
};
let compound_type = self.read_compound_type(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip_mode,
is_compound,
interintra.interintra,
interintra.wedge_interintra.unwrap_or(0),
enable_masked_compound,
enable_jnt_comp,
dist_equal,
avail_u,
avail_l,
above_single,
left_single,
above_ref_0_altref,
left_ref_0_altref,
)?;
// §5.11.29 / §8.3.2 grid stamp: write `CompGroupIdxs[ r + y ][
// c + x ] = comp_group_idx` and `CompoundIdxs[ r + y ][ c + x
// ] = compound_idx` over the bh4 * bw4 footprint so the next
// block's §8.3.2 `comp_group_idx` / `compound_idx` ctx walks
// observe the values. The §5.11.29 default-arm short-circuit
// (`skip_mode == 1` or `!isCompound`) still stamps the pre-set
// defaults (`0` / `1`) — matching the §5.11.29 line-1 / line-2
// imperatives.
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr * self.mi_cols + cc) as usize;
self.comp_group_idxs[cell] = compound_type.comp_group_idx;
self.compound_idxs[cell] = compound_type.compound_idx;
}
}
// §5.11.23 line 37: `read_interpolation_filter( )` per §5.11.x
// (av1-spec p.74). The LAST leaf of the §5.11.23 inter cascade.
// Reads per-direction `interp_filter[ 0..2 ]` against
// `TileInterpFilterCdf[ ctx ]` when `interpolation_filter ==
// SWITCHABLE && needs_interp_filter( )`; else forces the
// value from the frame header or the `EIGHTTAP` fallback.
let interp_filter = self.read_interpolation_filter(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
skip_mode,
is_compound,
ref_frame,
y_mode,
motion_mode,
interpolation_filter,
enable_dual_filter,
gm_type,
)?;
// §5.11.x / §8.3.2 grid stamp: write `InterpFilters[ r + y ][ c
// + x ][ dir ] = interp_filter[ dir ]` over the bh4 * bw4
// footprint so the next inter block's §8.3.2 `interp_filter`
// ctx walk observes the values. Both dirs are stamped on every
// path (the §5.11.x short-circuit arms set both slots, with the
// `!enable_dual_filter` mirror already applied inside
// [`Self::read_interpolation_filter`]).
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr * self.mi_cols + cc) as usize;
self.interp_filters[cell * 2] = interp_filter.interp_filter[0];
self.interp_filters[cell * 2 + 1] = interp_filter.interp_filter[1];
}
}
// §5.11.27 / §5.11.28 / §5.11.29 side-data grid stamp: write the
// already-resolved `motion_mode` / inter-intra blend triple /
// compound combine descriptor over the block's `bh4 * bw4`
// footprint so the §5.11.33 frame walk
// ([`Self::reconstruct_inter_frame_into_curr_frame`]) can read
// each leaf's origin cell back out and dispatch the compound /
// inter-intra / warped arms automatically (the single-ref
// translational arm already reads `ref_frames` / `mvs` /
// `interp_filters`). The `Option` per-symbol fields collapse to
// the pre-fill default on their closed arms (`None` ⇒ `0`),
// which the §5.11.33 walk's leaf-gate never reads on a cell
// whose `compound_type` / `RefFrame[1]` selects a different arm.
let comp_type = compound_type.compound_type;
let comp_wedge_index = compound_type.wedge_index.unwrap_or(0);
let comp_wedge_sign = compound_type.wedge_sign.unwrap_or(0);
let comp_mask_type = compound_type.mask_type.unwrap_or(0);
let ii_mode = interintra.interintra_mode.unwrap_or(0);
let ii_wedge = interintra.wedge_interintra.unwrap_or(0);
let ii_wedge_index = interintra.wedge_index.unwrap_or(0);
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr * self.mi_cols + cc) as usize;
self.motion_modes[cell] = motion_mode;
self.compound_types[cell] = comp_type;
self.compound_wedge_indices[cell] = comp_wedge_index;
self.compound_wedge_signs[cell] = comp_wedge_sign;
self.compound_mask_types[cell] = comp_mask_type;
self.interintra_modes[cell] = ii_mode;
self.wedge_interintras[cell] = ii_wedge;
self.interintra_wedge_indices[cell] = ii_wedge_index;
}
}
Ok(DecodedInterBlockModeInfo {
mi_row,
mi_col,
mi_size: sub_size,
ref_frame,
is_compound,
y_mode,
ref_mv_idx,
num_mv_found: mv_stack.num_mv_found,
new_mv_context: mv_stack.new_mv_context,
ref_mv_context: mv_stack.ref_mv_context,
zero_mv_context: mv_stack.zero_mv_context,
mv,
motion_mode,
num_warp_samples: num_samples,
interintra,
compound_type,
interp_filter,
})
}
/// `has_overlappable_candidates( )` per §7.10.3 (av1-spec p.236).
/// Returns `true` ⇔ the block has at least one inter-block
/// neighbour above or to the left at 8x8 granularity (the §7.10.3
/// "check is only made at 8x8 granularity" line). Both walks step
/// in 2-cell increments and probe the cell with the low bit forced
/// to `1` (`x4 | 1` / `y4 | 1`), giving the 8x8-anchor's bottom
/// (resp. right) edge.
///
/// The §7.10.3 spec body is:
///
/// ```text
/// if ( AvailU ) {
/// w4 = Num_4x4_Blocks_Wide[ MiSize ]
/// for ( x4 = MiCol; x4 < Min( MiCols, MiCol + w4 ); x4 += 2 )
/// if ( RefFrames[ MiRow - 1 ][ x4 | 1 ][ 0 ] > INTRA_FRAME )
/// return 1
/// }
/// if ( AvailL ) {
/// h4 = Num_4x4_Blocks_High[ MiSize ]
/// for ( y4 = MiRow; y4 < Min( MiRows, MiRow + h4 ); y4 += 2 )
/// if ( RefFrames[ y4 | 1 ][ MiCol - 1 ][ 0 ] > INTRA_FRAME )
/// return 1
/// }
/// return 0
/// ```
///
/// `AvailU` / `AvailL` derive from [`TileGeometry::is_inside`]
/// just as in the §5.11.18 prologue. `INTRA_FRAME = 0`, so the
/// `> INTRA_FRAME` test is "slot 0 of `RefFrames` is a non-intra
/// inter reference" — i.e. the neighbour was decoded as an inter
/// block and stamped its ref-frame into the grid.
pub fn has_overlappable_candidates(&self, mi_row: u32, mi_col: u32, sub_size: usize) -> bool {
debug_assert!(sub_size < BLOCK_SIZES);
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
if avail_u {
let w4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let lim = core::cmp::min(self.mi_cols, mi_col + w4);
let mut x4 = mi_col;
while x4 < lim {
let probe = (x4 | 1) as i32;
let r = self.ref_frame_at(mi_row as i32 - 1, probe, 0);
if r > 0 {
return true;
}
x4 += 2;
}
}
if avail_l {
let h4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
let lim = core::cmp::min(self.mi_rows, mi_row + h4);
let mut y4 = mi_row;
while y4 < lim {
let probe = (y4 | 1) as i32;
let r = self.ref_frame_at(probe, mi_col as i32 - 1, 0);
if r > 0 {
return true;
}
y4 += 2;
}
}
false
}
/// `find_warp_samples( )` per §7.10.4 (av1-spec p.237). Returns
/// `NumSamples` (the §7.10.4 "number of valid candidates found").
/// Each candidate is one §7.10.4.2 `add_sample` invocation that
/// passed the inter-neighbour / matching-ref-frame / single-list /
/// in-tile gates AND the §7.10.4.2 magnitude check
/// (`(|mvDiffRow| + |mvDiffCol|) <= threshold` with `threshold =
/// Clip3(16, 112, Max(Block_Width, Block_Height))`). The
/// "first large MV is kept if no small one matches" special case
/// of §7.10.4.1 is honoured: on the all-large path the function
/// returns `1` instead of `0`.
///
/// `mv` is the per-list `Mv[ 0..2 ][ 0..2 ]` for the current block,
/// freshly returned by §5.11.31 `assign_mv`. Only `mv[0]` is read:
/// the §7.10.4.2 spec compares `Mvs[..][0][0..2]` (the §7.10
/// neighbour list 0 MV) against `Mv[0]` (the current block's list
/// 0 MV).
///
/// `CandList` itself is not surfaced — the §7.10.4 caller (§5.11.27
/// `read_motion_mode`) only consults `NumSamples` (zero ⇒ no
/// warp), and the WARPED_CAUSAL sample-list consumer is the
/// §7.11.4 `warp_estimation()` driver that lives in a downstream
/// arc. The candidate-list aggregation is the §7.11.4 / §7.11.6
/// driver's responsibility, and the data needed to repopulate it
/// from this function's inputs is available in
/// `Mvs[..][..][0][0..2]` / `MiSizes[..][..]` on the walker grid.
pub fn find_warp_samples(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
ref_frame_0: i32,
mv: [[i32; 2]; 2],
) -> u32 {
self.find_warp_samples_impl(mi_row, mi_col, sub_size, ref_frame_0, mv, None)
}
/// [`Self::find_warp_samples`] with the §7.10.4.2 step-3
/// `CandList[ NumSamples ][ 0..4 ]` retained — the §7.11.3.8
/// `warp_estimation` input. `out` is cleared, filled in scan order
/// (each step-3 write lands at index `NumSamples`, so an invalid
/// first sample is overwritten by the next scan exactly like the
/// spec's array store), and truncated to the returned `NumSamples`
/// (which includes the §7.10.4.1 "first large MV is kept when no
/// small one matches" fixup).
pub fn find_warp_samples_list(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
ref_frame_0: i32,
mv: [[i32; 2]; 2],
out: &mut Vec<crate::inter_pred::WarpSampleCand>,
) -> u32 {
out.clear();
let n = self.find_warp_samples_impl(mi_row, mi_col, sub_size, ref_frame_0, mv, Some(out));
out.truncate(n as usize);
n
}
#[allow(clippy::too_many_arguments)]
fn find_warp_samples_impl(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
ref_frame_0: i32,
mv: [[i32; 2]; 2],
mut cand_out: Option<&mut Vec<crate::inter_pred::WarpSampleCand>>,
) -> u32 {
debug_assert!(sub_size < BLOCK_SIZES);
debug_assert!((0..=7).contains(&ref_frame_0));
let w4 = NUM_4X4_BLOCKS_WIDE[sub_size] as i32;
let h4 = NUM_4X4_BLOCKS_HIGH[sub_size] as i32;
// §7.10.4 state.
let mut num_samples: u32 = 0;
let mut num_samples_scanned: u32 = 0;
let mut do_top_left = true;
let mut do_top_right = true;
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
// §7.10.4.1: above-neighbour walk.
if avail_u {
let src_size = self.mi_size_at(mi_row as i32 - 1, mi_col as i32);
let src_w = if src_size < BLOCK_SIZES {
NUM_4X4_BLOCKS_WIDE[src_size] as i32
} else {
// §5.11.4 unwritten cell — treat as a single 4x4
// (smallest §7.10.4 walk step). The §5.11.4 walker
// does not pre-decode neighbours outside the current
// partition tree, so the unwritten case is reached on
// top-row blocks where AvailU is also false — the
// outer `avail_u` gate would have prevented entry,
// but stay defensive.
1
};
if w4 <= src_w {
let col_offset = -((mi_col as i32) & (src_w - 1));
if col_offset < 0 {
do_top_left = false;
}
if col_offset + src_w > w4 {
do_top_right = false;
}
self.add_sample(
mi_row,
mi_col,
sub_size,
ref_frame_0,
mv,
-1,
0,
&mut num_samples,
&mut num_samples_scanned,
cand_out.as_deref_mut(),
);
} else {
let lim = core::cmp::min(w4, (self.mi_cols as i32) - (mi_col as i32));
let mut i: i32 = 0;
while i < lim {
let src_size = self.mi_size_at(mi_row as i32 - 1, mi_col as i32 + i);
let src_w = if src_size < BLOCK_SIZES {
NUM_4X4_BLOCKS_WIDE[src_size] as i32
} else {
1
};
let mi_step = core::cmp::min(w4, src_w);
self.add_sample(
mi_row,
mi_col,
sub_size,
ref_frame_0,
mv,
-1,
i,
&mut num_samples,
&mut num_samples_scanned,
cand_out.as_deref_mut(),
);
if mi_step <= 0 {
break;
}
i += mi_step;
}
}
}
// §7.10.4.1: left-neighbour walk.
if avail_l {
let src_size = self.mi_size_at(mi_row as i32, mi_col as i32 - 1);
let src_h = if src_size < BLOCK_SIZES {
NUM_4X4_BLOCKS_HIGH[src_size] as i32
} else {
1
};
if h4 <= src_h {
let row_offset = -((mi_row as i32) & (src_h - 1));
if row_offset < 0 {
do_top_left = false;
}
self.add_sample(
mi_row,
mi_col,
sub_size,
ref_frame_0,
mv,
0,
-1,
&mut num_samples,
&mut num_samples_scanned,
cand_out.as_deref_mut(),
);
} else {
let lim = core::cmp::min(h4, (self.mi_rows as i32) - (mi_row as i32));
let mut i: i32 = 0;
while i < lim {
let src_size = self.mi_size_at(mi_row as i32 + i, mi_col as i32 - 1);
let src_h = if src_size < BLOCK_SIZES {
NUM_4X4_BLOCKS_HIGH[src_size] as i32
} else {
1
};
let mi_step = core::cmp::min(h4, src_h);
self.add_sample(
mi_row,
mi_col,
sub_size,
ref_frame_0,
mv,
i,
-1,
&mut num_samples,
&mut num_samples_scanned,
cand_out.as_deref_mut(),
);
if mi_step <= 0 {
break;
}
i += mi_step;
}
}
}
// §7.10.4.1: top-left + top-right corner samples.
if do_top_left {
self.add_sample(
mi_row,
mi_col,
sub_size,
ref_frame_0,
mv,
-1,
-1,
&mut num_samples,
&mut num_samples_scanned,
cand_out.as_deref_mut(),
);
}
if do_top_right && core::cmp::max(w4, h4) <= 16 {
self.add_sample(
mi_row,
mi_col,
sub_size,
ref_frame_0,
mv,
-1,
w4,
&mut num_samples,
&mut num_samples_scanned,
cand_out,
);
}
// §7.10.4.1: "first large MV is kept if no small one matches".
if num_samples == 0 && num_samples_scanned > 0 {
num_samples = 1;
}
num_samples
}
/// `add_sample( deltaRow, deltaCol )` per §7.10.4.2 (av1-spec
/// p.238). Increments `NumSamplesScanned`, applies the
/// §7.10.4.2 gates, and increments `NumSamples` iff `valid == 1`.
/// Exits immediately if `NumSamplesScanned >=
/// LEAST_SQUARES_SAMPLES_MAX` per the §7.10.4.2 first-line guard.
///
/// The function does NOT populate the `CandList` — see
/// [`Self::find_warp_samples`] for the rationale (the consumer
/// lives in a downstream arc and the data is recoverable from the
/// walker grids).
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
fn add_sample(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
ref_frame_0: i32,
mv: [[i32; 2]; 2],
delta_row: i32,
delta_col: i32,
num_samples: &mut u32,
num_samples_scanned: &mut u32,
cand_out: Option<&mut Vec<crate::inter_pred::WarpSampleCand>>,
) {
if (*num_samples_scanned as usize) >= LEAST_SQUARES_SAMPLES_MAX {
return;
}
let mv_row = mi_row as i32 + delta_row;
let mv_col = mi_col as i32 + delta_col;
if !self.geometry.is_inside(mv_row, mv_col) {
return;
}
// RefFrames[mvRow][mvCol][0] "not written" check: the walker
// pre-fill leaves both slots at [INTRA_FRAME = 0, NONE = -1]
// for unwritten cells, so the §7.10.4.2 "not written" line
// collapses to a `IsInters[mvRow][mvCol] == 0 || RefFrames[..]
// [0] == INTRA_FRAME` test (an intra block in the grid is
// never a valid warp neighbour either way).
let cell = (mv_row as u32 * self.mi_cols + mv_col as u32) as usize;
if self.is_inters[cell] == 0 {
return;
}
let nb_ref_0 = self.ref_frames[cell * 2];
let nb_ref_1 = self.ref_frames[cell * 2 + 1];
if (nb_ref_0 as i32) != ref_frame_0 {
return;
}
if nb_ref_1 != -1 {
return;
}
let cand_sz = self.mi_size_at(mv_row, mv_col);
if cand_sz >= BLOCK_SIZES {
return;
}
let cand_w4 = NUM_4X4_BLOCKS_WIDE[cand_sz] as i32;
let cand_h4 = NUM_4X4_BLOCKS_HIGH[cand_sz] as i32;
let cand_row = mv_row & !(cand_h4 - 1);
let cand_col = mv_col & !(cand_w4 - 1);
// Magnitude validity check.
let bw = block_width(sub_size) as i32;
let bh = block_height(sub_size) as i32;
let threshold = core::cmp::max(bw, bh).clamp(16, 112);
let nb_cell = (cand_row as u32 * self.mi_cols + cand_col as u32) as usize;
let nb_mv_row = self.mvs[(nb_cell * 2) * 2] as i32;
let nb_mv_col = self.mvs[(nb_cell * 2) * 2 + 1] as i32;
let mv_diff_row = (nb_mv_row - mv[0][0]).unsigned_abs() as i64;
let mv_diff_col = (nb_mv_col - mv[0][1]).unsigned_abs() as i64;
let valid = mv_diff_row + mv_diff_col <= threshold as i64;
*num_samples_scanned += 1;
if !valid && *num_samples_scanned > 1 {
return;
}
// §7.10.4.2 step 3: `CandList[ NumSamples ][ j ] = cand[ j ]`
// — `midY = candRow * 4 + candH4 * 2 - 1`, `midX = candCol * 4
// + candW4 * 2 - 1`; source = mid * 8, destination = source +
// `Mvs[ candRow ][ candCol ][ 0 ]`. The slot index is
// `NumSamples`, so an invalid (but recorded — step 2 only
// exits on `NumSamplesScanned > 1`) first sample is
// overwritten by the next scan unless the §7.10.4.1 fixup
// promotes it.
if let Some(out) = cand_out {
let mid_y = cand_row * 4 + cand_h4 * 2 - 1;
let mid_x = cand_col * 4 + cand_w4 * 2 - 1;
let cand = crate::inter_pred::WarpSampleCand {
sy: mid_y * 8,
sx: mid_x * 8,
dy: mid_y * 8 + nb_mv_row,
dx: mid_x * 8 + nb_mv_col,
};
let slot = *num_samples as usize;
if slot < out.len() {
out[slot] = cand;
} else {
out.push(cand);
}
}
if valid {
*num_samples += 1;
}
}
/// `read_motion_mode( isCompound )` per §5.11.27 (av1-spec p.79).
/// Returns the §6.10.26 `motion_mode` ordinal (`SIMPLE = 0` /
/// `OBMC = 1` / `WARPED_CAUSAL = 2`).
///
/// The spec body short-circuits to `SIMPLE` on:
/// * `skip_mode == 1`,
/// * `!is_motion_mode_switchable`,
/// * `Min(Block_Width, Block_Height) < 8`,
/// * `!force_integer_mv && (YMode in {GLOBALMV, GLOBAL_GLOBALMV})
/// && GmType[RefFrame[0]] > TRANSLATION`,
/// * `isCompound || RefFrame[1] == INTRA_FRAME ||
/// !has_overlappable_candidates()`.
///
/// Otherwise invokes §7.10.4 `find_warp_samples()` (the caller's
/// `num_samples` argument) and chooses between two arms:
/// * `force_integer_mv || NumSamples == 0 || !allow_warped_motion
/// || is_scaled(RefFrame[0])` ⇒ `use_obmc` S(); result is
/// `OBMC` (when `use_obmc == 1`) or `SIMPLE`.
/// * Otherwise ⇒ `motion_mode` S(); result is one of `SIMPLE` /
/// `OBMC` / `WARPED_CAUSAL` directly.
#[allow(clippy::too_many_arguments)]
pub fn read_motion_mode(
&self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip_mode: u8,
is_compound: bool,
ref_frame: [i32; 2],
y_mode: u8,
num_samples: u32,
is_motion_mode_switchable: bool,
allow_warped_motion: bool,
force_integer_mv: bool,
gm_type: [i32; 8],
is_scaled_per_ref: [bool; 7],
) -> Result<u8, crate::Error> {
// §5.11.27 short-circuit arms.
if skip_mode != 0 {
return Ok(MOTION_MODE_SIMPLE);
}
if !is_motion_mode_switchable {
return Ok(MOTION_MODE_SIMPLE);
}
if core::cmp::min(block_width(sub_size), block_height(sub_size)) < 8 {
return Ok(MOTION_MODE_SIMPLE);
}
if !force_integer_mv && (y_mode == MODE_GLOBALMV || y_mode == MODE_GLOBAL_GLOBALMV) {
let ref0 = ref_frame[0];
if (0..8).contains(&ref0) && gm_type[ref0 as usize] > GM_TYPE_TRANSLATION {
return Ok(MOTION_MODE_SIMPLE);
}
}
// `RefFrame[1] == INTRA_FRAME` — INTRA_FRAME = 0; the §5.11.25
// reader does not currently produce slot-1-INTRA, but the test
// is included for spec literalness.
if is_compound
|| ref_frame[1] == 0
|| !self.has_overlappable_candidates(mi_row, mi_col, sub_size)
{
return Ok(MOTION_MODE_SIMPLE);
}
// §5.11.27 post-`find_warp_samples` arm dispatch.
//
// `is_scaled(RefFrame[0])` per §5.11.27: only valid when
// `RefFrame[0]` is in `LAST_FRAME..=ALTREF_FRAME = 1..=7`. The
// SIMPLE-shortcircuit above already filters compound + slot-1-
// INTRA; with single-pred + slot-0 != INTRA_FRAME the spec's
// `RefFrame[0] - LAST_FRAME` index is in `0..=6`.
let ref0 = ref_frame[0];
let scaled = if (1..=7).contains(&ref0) {
is_scaled_per_ref[(ref0 - 1) as usize]
} else {
false
};
if force_integer_mv || num_samples == 0 || !allow_warped_motion || scaled {
// Arm A: `use_obmc` S() against `TileUseObmcCdf[ MiSize ]`.
let row = cdfs
.use_obmc_cdf(sub_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let use_obmc = decoder.read_symbol(row)? as u8;
debug_assert!(use_obmc <= 1, "S() over TileUseObmcCdf yields 0 or 1");
Ok(if use_obmc == 1 {
MOTION_MODE_OBMC
} else {
MOTION_MODE_SIMPLE
})
} else {
// Arm B: `motion_mode` S() against `TileMotionModeCdf[ MiSize ]`.
let row = cdfs
.motion_mode_cdf(sub_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let mm = decoder.read_symbol(row)? as u8;
debug_assert!(
(mm as usize) < MOTION_MODES,
"S() over TileMotionModeCdf yields 0..MOTION_MODES"
);
Ok(mm)
}
}
/// `read_interintra_mode( isCompound )` per §5.11.28 (av1-spec
/// p.79-80). Reads the inter-intra blending triple
/// (`interintra` + `interintra_mode` + `wedge_interintra` +
/// optional `wedge_index`) and returns the outcome as a
/// [`InterIntraReadout`] aggregate.
///
/// The §5.11.28 outer gate is:
///
/// ```text
/// if ( !skip_mode && enable_interintra_compound && !isCompound &&
/// MiSize >= BLOCK_8X8 && MiSize <= BLOCK_32X32 ) {
/// interintra S()
/// if ( interintra ) {
/// interintra_mode S()
/// RefFrame[ 1 ] = INTRA_FRAME
/// AngleDeltaY = 0
/// AngleDeltaUV = 0
/// use_filter_intra = 0
/// wedge_interintra S()
/// if ( wedge_interintra ) {
/// wedge_index S()
/// wedge_sign = 0
/// }
/// }
/// } else {
/// interintra = 0
/// }
/// ```
///
/// When the outer gate is closed the spec sets `interintra = 0`
/// and reads no bits; the returned [`InterIntraReadout`] has
/// `interintra = 0` with the per-symbol fields at `None`.
///
/// When the inner `if ( interintra )` arm fires, `RefFrame[ 1 ]` is
/// overridden to `INTRA_FRAME = 0` per the spec; the caller is
/// responsible for restamping the walker grid if it cares
/// (downstream §7.10.3 / §7.10.4 / §8.3.2 neighbour walks consult
/// `RefFrames[..][..][0]` only, so the slot-1 override does not
/// affect any post-§5.11.28 reader covered through r175).
///
/// `mi_size` is the current block's `MiSize`; the §5.11.28 syntax
/// gate confines coded blocks to `BLOCK_8X8..=BLOCK_32X32`
/// (per §6.10.27). The §8.3.2 selection for `interintra` and
/// `interintra_mode` is `ctx = Size_Group[ MiSize ] - 1`,
/// resolved via [`interintra_ctx`]; the §8.3.2 selection for
/// `wedge_interintra` is the straight `MiSize` index; the §8.3.2
/// selection for `wedge_index` is also the straight `MiSize`
/// index.
#[allow(clippy::too_many_arguments)]
pub fn read_interintra_mode(
&self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
sub_size: usize,
skip_mode: u8,
is_compound: bool,
enable_interintra_compound: bool,
) -> Result<InterIntraReadout, crate::Error> {
// §5.11.28 outer gate: every clause must hold for the
// `interintra` S() to fire.
let gate_open = skip_mode == 0
&& enable_interintra_compound
&& !is_compound
&& (BLOCK_8X8..=BLOCK_32X32).contains(&sub_size);
if !gate_open {
return Ok(InterIntraReadout {
interintra: 0,
interintra_mode: None,
wedge_interintra: None,
wedge_index: None,
});
}
// §5.11.28 `interintra` S() against `TileInterIntraCdf[ ctx ]`,
// `ctx = Size_Group[ MiSize ] - 1`. The outer gate confines
// `MiSize` to `BLOCK_8X8..=BLOCK_32X32` where `Size_Group` is
// `{1, 2, 3}`, so `interintra_ctx` always returns `Some(_)`;
// the `None` arm is a defensive caller-bug fallback.
let ctx = interintra_ctx(sub_size).ok_or(crate::Error::PartitionWalkOutOfRange)?;
let row = cdfs
.inter_intra_cdf(ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let interintra = decoder.read_symbol(row)? as u8;
debug_assert!(interintra <= 1, "S() over TileInterIntraCdf yields 0 or 1");
if interintra == 0 {
return Ok(InterIntraReadout {
interintra: 0,
interintra_mode: None,
wedge_interintra: None,
wedge_index: None,
});
}
// §5.11.28 `interintra_mode` S() against
// `TileInterIntraModeCdf[ ctx ]`, same `ctx`.
let row = cdfs
.inter_intra_mode_cdf(ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let ii_mode = decoder.read_symbol(row)? as u8;
debug_assert!(
(ii_mode as usize) < INTERINTRA_MODES,
"S() over TileInterIntraModeCdf yields 0..INTERINTRA_MODES"
);
// §5.11.28 `wedge_interintra` S() against
// `TileWedgeInterIntraCdf[ MiSize ]` (straight `MiSize`
// index).
let row = cdfs
.wedge_inter_intra_cdf(sub_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let wedge_ii = decoder.read_symbol(row)? as u8;
debug_assert!(
wedge_ii <= 1,
"S() over TileWedgeInterIntraCdf yields 0 or 1"
);
let wedge_idx = if wedge_ii == 1 {
// §5.11.28 `wedge_index` S() against
// `TileWedgeIndexCdf[ MiSize ]` — same default CDF as the
// §5.11.29 `COMPOUND_WEDGE` branch.
let row = cdfs
.wedge_index_cdf(sub_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let wi = decoder.read_symbol(row)? as u8;
debug_assert!(
(wi as usize) < WEDGE_TYPES,
"S() over TileWedgeIndexCdf yields 0..WEDGE_TYPES"
);
Some(wi)
} else {
None
};
Ok(InterIntraReadout {
interintra: 1,
interintra_mode: Some(ii_mode),
wedge_interintra: Some(wedge_ii),
wedge_index: wedge_idx,
})
}
/// `read_compound_type( isCompound )` per §5.11.29 (av1-spec
/// p.80-81). Reads the inter-inter compound-blending controls and
/// returns the outcome as a [`CompoundTypeReadout`] aggregate.
///
/// The §5.11.29 spec body is:
///
/// ```text
/// read_compound_type( isCompound ) {
/// comp_group_idx = 0
/// compound_idx = 1
/// if ( skip_mode ) {
/// compound_type = COMPOUND_AVERAGE
/// return
/// }
/// if ( isCompound ) {
/// n = Wedge_Bits[ MiSize ]
/// if ( enable_masked_compound ) {
/// comp_group_idx S()
/// }
/// if ( comp_group_idx == 0 ) {
/// if ( enable_jnt_comp ) {
/// compound_idx S()
/// compound_type = compound_idx ? COMPOUND_AVERAGE
/// : COMPOUND_DISTANCE
/// } else {
/// compound_type = COMPOUND_AVERAGE
/// }
/// } else {
/// if ( n == 0 ) {
/// compound_type = COMPOUND_DIFFWTD
/// } else {
/// compound_type S()
/// }
/// }
/// if ( compound_type == COMPOUND_WEDGE ) {
/// wedge_index S()
/// wedge_sign L(1)
/// } else if ( compound_type == COMPOUND_DIFFWTD ) {
/// mask_type L(1)
/// }
/// } else {
/// if ( interintra ) {
/// compound_type = wedge_interintra ? COMPOUND_WEDGE
/// : COMPOUND_INTRA
/// } else {
/// compound_type = COMPOUND_AVERAGE
/// }
/// }
/// }
/// ```
///
/// The §8.3.2 selections are:
/// * `comp_group_idx`: `TileCompGroupIdxCdf[ ctx ]` with ctx via
/// [`comp_group_idx_ctx`] (`CompGroupIdxs` neighbour values,
/// `AvailU` / `AvailL`, `AboveSingle` / `LeftSingle`,
/// `ALTREF_FRAME` test, clamped at `Min(5, ctx)`).
/// * `compound_idx`: `TileCompoundIdxCdf[ ctx ]` with ctx via
/// [`compound_idx_ctx`] (`get_relative_dist` equality test,
/// `CompoundIdxs` neighbour values, `AvailU` / `AvailL`,
/// `AboveSingle` / `LeftSingle`, `ALTREF_FRAME` test).
/// * `compound_type`: `TileCompoundTypeCdf[ MiSize ]` (straight
/// `MiSize` index).
/// * `wedge_index`: `TileWedgeIndexCdf[ MiSize ]` (shared with the
/// §5.11.28 inter-intra wedge sub-branch).
/// * `wedge_sign` / `mask_type`: literal `L(1)`, no CDF.
///
/// Caller-supplied scalars beyond the §5.11.28 set:
/// * `interintra` / `wedge_interintra` — the §5.11.28 outcome
/// (used only on the `!isCompound` else-arm).
/// * `enable_masked_compound` — §5.5.2 sequence-header bit.
/// * `enable_jnt_comp` — §5.5.2 sequence-header bit.
/// * `dist_equal` — the `Abs(get_relative_dist(OrderHints[
/// RefFrame[0] ], OrderHint)) == Abs(get_relative_dist(
/// OrderHints[ RefFrame[1] ], OrderHint))` outcome the caller
/// computed from the frame-header `OrderHints[]` / `OrderHint`
/// state (used only on the `enable_jnt_comp` arm).
/// * `above_ref_0_altref` / `left_ref_0_altref` — the §5.11.18
/// `AboveRefFrame[0] == ALTREF_FRAME` / `LeftRefFrame[0] ==
/// ALTREF_FRAME` results (used only on the `AboveSingle` /
/// `LeftSingle` arm of the §8.3.2 ctx walks).
///
/// The walker reads neighbour `CompGroupIdxs` / `CompoundIdxs`
/// directly from its grids (the [`PartitionWalker::comp_group_idxs`]
/// / [`PartitionWalker::compound_idxs`] caches stamped by previous
/// blocks).
#[allow(clippy::too_many_arguments)]
pub fn read_compound_type(
&self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip_mode: u8,
is_compound: bool,
interintra: u8,
wedge_interintra: u8,
enable_masked_compound: bool,
enable_jnt_comp: bool,
dist_equal: bool,
avail_u: bool,
avail_l: bool,
above_single: bool,
left_single: bool,
above_ref_0_altref: bool,
left_ref_0_altref: bool,
) -> Result<CompoundTypeReadout, crate::Error> {
// §5.11.29 lines 1-2: `comp_group_idx = 0`, `compound_idx = 1`.
let mut comp_group_idx: u8 = 0;
let mut compound_idx: u8 = 1;
// §5.11.29 line 3-6: `if ( skip_mode )` short-circuit —
// `compound_type = COMPOUND_AVERAGE`, no bits read.
if skip_mode != 0 {
return Ok(CompoundTypeReadout {
comp_group_idx,
compound_idx,
compound_type: COMPOUND_AVERAGE,
wedge_index: None,
wedge_sign: None,
mask_type: None,
});
}
if !is_compound {
// §5.11.29 else-arm: no bits read, the §5.11.28 outcome
// alone selects the type.
let compound_type = if interintra != 0 {
if wedge_interintra != 0 {
COMPOUND_WEDGE
} else {
COMPOUND_INTRA
}
} else {
COMPOUND_AVERAGE
};
return Ok(CompoundTypeReadout {
comp_group_idx,
compound_idx,
compound_type,
wedge_index: None,
wedge_sign: None,
mask_type: None,
});
}
// §5.11.29 `n = Wedge_Bits[ MiSize ]`.
let n = wedge_bits(sub_size);
// §5.11.29 `if ( enable_masked_compound )` ⇒ `comp_group_idx S()`.
if enable_masked_compound {
// §8.3.2 `comp_group_idx` ctx via §7.10-style neighbour
// walk. Neighbour `CompGroupIdxs[ MiRow - 1 ][ MiCol ]` /
// `CompGroupIdxs[ MiRow ][ MiCol - 1 ]` reads are gated on
// `AvailU` / `AvailL`; an out-of-grid neighbour reduces to
// the `0` identity (the §5.11.29 pre-fill value).
let above_cg = if avail_u && mi_row > 0 {
let cell = ((mi_row - 1) * self.mi_cols + mi_col) as usize;
self.comp_group_idxs[cell]
} else {
0
};
let left_cg = if avail_l && mi_col > 0 {
let cell = (mi_row * self.mi_cols + (mi_col - 1)) as usize;
self.comp_group_idxs[cell]
} else {
0
};
let ctx = comp_group_idx_ctx(
avail_u,
above_single,
above_cg,
above_ref_0_altref,
avail_l,
left_single,
left_cg,
left_ref_0_altref,
);
let row = cdfs.comp_group_idx_cdf(ctx);
let sym = decoder.read_symbol(row)? as u8;
debug_assert!(sym <= 1, "S() over TileCompGroupIdxCdf yields 0 or 1");
comp_group_idx = sym;
}
let compound_type: u8;
let mut wedge_index_out: Option<u8> = None;
let mut wedge_sign_out: Option<u8> = None;
let mut mask_type_out: Option<u8> = None;
if comp_group_idx == 0 {
// §5.11.29 `if ( enable_jnt_comp )` ⇒ `compound_idx S()`
// ⇒ `compound_type = compound_idx ? COMPOUND_AVERAGE :
// COMPOUND_DISTANCE`; else `compound_type = COMPOUND_AVERAGE`.
if enable_jnt_comp {
// §8.3.2 `compound_idx` ctx via the same neighbour
// walk as `comp_group_idx`, seeded with the
// `dist_equal ? 3 : 0` distance-equality outcome and
// adding `+1` (not `+CompoundIdxs`) on the
// `AboveSingle`/`LeftSingle` `ALTREF_FRAME` arm per
// av1-spec p.384.
let above_ci = if avail_u && mi_row > 0 {
let cell = ((mi_row - 1) * self.mi_cols + mi_col) as usize;
self.compound_idxs[cell]
} else {
1
};
let left_ci = if avail_l && mi_col > 0 {
let cell = (mi_row * self.mi_cols + (mi_col - 1)) as usize;
self.compound_idxs[cell]
} else {
1
};
let ctx = compound_idx_ctx(
dist_equal,
avail_u,
above_single,
above_ci,
above_ref_0_altref,
avail_l,
left_single,
left_ci,
left_ref_0_altref,
);
let row = cdfs.compound_idx_cdf(ctx);
let sym = decoder.read_symbol(row)? as u8;
debug_assert!(sym <= 1, "S() over TileCompoundIdxCdf yields 0 or 1");
compound_idx = sym;
compound_type = if compound_idx != 0 {
COMPOUND_AVERAGE
} else {
COMPOUND_DISTANCE
};
} else {
compound_type = COMPOUND_AVERAGE;
}
} else {
// §5.11.29 `comp_group_idx == 1`: `n == 0` ⇒
// `compound_type = COMPOUND_DIFFWTD`; else `compound_type
// S()` against `TileCompoundTypeCdf[ MiSize ]`.
if n == 0 {
compound_type = COMPOUND_DIFFWTD;
} else {
let row = cdfs
.compound_type_cdf(sub_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let sym = decoder.read_symbol(row)? as u8;
debug_assert!(
(sym as usize) < COMPOUND_TYPES,
"S() over TileCompoundTypeCdf yields 0..COMPOUND_TYPES"
);
compound_type = sym;
}
}
// §5.11.29 wedge / diffwtd sub-branches.
if compound_type == COMPOUND_WEDGE {
let row = cdfs
.wedge_index_cdf(sub_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let wi = decoder.read_symbol(row)? as u8;
debug_assert!(
(wi as usize) < WEDGE_TYPES,
"S() over TileWedgeIndexCdf yields 0..WEDGE_TYPES"
);
wedge_index_out = Some(wi);
// §5.11.29 `wedge_sign L(1)`.
let ws = decoder.read_literal(1)? as u8;
debug_assert!(ws <= 1, "L(1) over wedge_sign yields 0 or 1");
wedge_sign_out = Some(ws);
} else if compound_type == COMPOUND_DIFFWTD {
// §5.11.29 `mask_type L(1)`.
let mt = decoder.read_literal(1)? as u8;
debug_assert!(mt <= 1, "L(1) over mask_type yields 0 or 1");
mask_type_out = Some(mt);
}
Ok(CompoundTypeReadout {
comp_group_idx,
compound_idx,
compound_type,
wedge_index: wedge_index_out,
wedge_sign: wedge_sign_out,
mask_type: mask_type_out,
})
}
/// `read_interpolation_filter` per §5.11.x — the per-block
/// interpolation-filter selection that sits at the bottom of the
/// §5.11.23 inter cascade (av1-spec p.74, the
/// `if ( interpolation_filter == SWITCHABLE )` block at the end of
/// `inter_block_mode_info`).
///
/// Spec body (av1-spec p.74):
///
/// ```text
/// if ( interpolation_filter == SWITCHABLE ) {
/// for ( dir = 0; dir < ( enable_dual_filter ? 2 : 1 ); dir++ ) {
/// if ( needs_interp_filter( ) ) {
/// interp_filter[ dir ] S()
/// } else {
/// interp_filter[ dir ] = EIGHTTAP
/// }
/// }
/// if ( !enable_dual_filter )
/// interp_filter[ 1 ] = interp_filter[ 0 ]
/// } else {
/// for ( dir = 0; dir < 2; dir++ )
/// interp_filter[ dir ] = interpolation_filter
/// }
/// ```
///
/// `needs_interp_filter( )` is defined at av1-spec p.75:
///
/// ```text
/// needs_interp_filter( ) {
/// large = ( Min( Block_Width[ MiSize ], Block_Height[ MiSize ] ) >= 8 )
/// if ( skip_mode || motion_mode == LOCALWARP ) return 0
/// else if ( large && YMode == GLOBALMV )
/// return GmType[ RefFrame[ 0 ] ] == TRANSLATION
/// else if ( large && YMode == GLOBAL_GLOBALMV )
/// return GmType[ RefFrame[ 0 ] ] == TRANSLATION ||
/// GmType[ RefFrame[ 1 ] ] == TRANSLATION
/// else return 1
/// }
/// ```
///
/// The §8.3.2 `interp_filter` ctx walk (av1-spec p.369) is
/// expressed via [`interp_filter_ctx`]; it consults the walker's
/// `interp_filters` and `ref_frames` grids on the `AvailL` /
/// `AvailU` arms, accepting the neighbour's slot only when one of
/// `RefFrames[ neighbour ][ 0..2 ]` matches `RefFrame[ 0 ]`. Out-of-
/// grid or mismatched neighbours fall back to the spec's
/// `aboveType = 3` / `leftType = 3` sentinel ([`INTERP_FILTER_NONE`]).
///
/// `interpolation_filter` is the §5.9.10 frame-header value in
/// `EIGHTTAP..=SWITCHABLE = 0..=4`. `enable_dual_filter` is the
/// §5.5.2 sequence-header bit. `motion_mode` is the §5.11.27
/// outcome. `y_mode` is the post-§5.11.23-dispatch `YMode`.
/// `gm_type` is the §5.9.24 per-ref global-motion type table.
/// `ref_frame` is the §5.11.25 reader output (`[ RefFrame[ 0 ],
/// RefFrame[ 1 ] ]`). `is_compound` mirrors `RefFrame[ 1 ] >
/// INTRA_FRAME`. `avail_u` / `avail_l` and the walker's grid
/// state seed the §8.3.2 ctx walk.
///
/// On the `SWITCHABLE && needs_interp_filter` arm the inner S()
/// reads against `TileInterpFilterCdf[ ctx ]` (a §9.4 4-way CDF
/// over `EIGHTTAP / EIGHTTAP_SMOOTH / EIGHTTAP_SHARP / BILINEAR`).
/// The §9.4 default table has BILINEAR's slot at probability ≈ 0
/// (the spec keeps `BILINEAR` reachable only via the frame-level
/// f(2) = 3 path), but the symbol space is still 4-way to match
/// the §6.8.9 ordinal enumeration.
///
/// Returns [`InterpolationFilterReadout`] with the resolved
/// `interp_filter[ 0..2 ]` and per-direction `read_from_bitstream`
/// flags. Caller-bug inputs (out-of-range `sub_size` /
/// `interpolation_filter` / `ref_frame`) surface
/// [`crate::Error::PartitionWalkOutOfRange`]; bitstream errors
/// surface [`crate::Error::UnexpectedEnd`] /
/// [`crate::Error::SymbolExitUnderflow`].
#[allow(clippy::too_many_arguments)]
pub fn read_interpolation_filter(
&self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
skip_mode: u8,
is_compound: bool,
ref_frame: [i32; 2],
y_mode: u8,
motion_mode: u8,
interpolation_filter: u8,
enable_dual_filter: bool,
gm_type: [i32; 8],
) -> Result<InterpolationFilterReadout, crate::Error> {
// §5.11.x caller-bug guards. `interpolation_filter` ranges
// 0..=SWITCHABLE = 0..=4 per §6.8.9; `sub_size` per the §3
// BLOCK_* enumeration; `ref_frame[0]` in `INTRA_FRAME =
// 0..=ALTREF_FRAME = 7` (the §5.11.25 reader caps it);
// `ref_frame[1]` in `NONE = -1 | INTRA_FRAME = 0..=ALTREF_FRAME =
// 7`.
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if interpolation_filter > SWITCHABLE {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(0..=7).contains(&ref_frame[0]) || !(-1..=7).contains(&ref_frame[1]) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.x `else`-arm: `interp_filter[ dir ] = interpolation_filter`.
// Reachable only when the frame-header f(2) decoded `EIGHTTAP /
// EIGHTTAP_SMOOTH / EIGHTTAP_SHARP / BILINEAR` (not
// `SWITCHABLE`). No bits are read at the block level. The §3
// ordinal mapping puts `0..=3` here; `SWITCHABLE = 4` is the
// signal to take the `if`-arm and would never appear as the
// forced value (the caller-bug guard above rejects it as a
// belt-and-braces measure, but the natural `!= SWITCHABLE` test
// covers it).
if interpolation_filter != SWITCHABLE {
return Ok(InterpolationFilterReadout {
interp_filter: [interpolation_filter, interpolation_filter],
read_from_bitstream: [false, false],
});
}
// §5.11.x `if`-arm: per-direction loop. `enable_dual_filter`
// controls whether the loop runs for 1 or 2 dirs; the post-loop
// `if ( !enable_dual_filter ) interp_filter[ 1 ] = interp_filter[
// 0 ]` mirror is also gated by it.
let dir_count = if enable_dual_filter { 2 } else { 1 };
let mut filters: [u8; 2] = [EIGHTTAP, EIGHTTAP];
let mut read_flags: [bool; 2] = [false, false];
for dir in 0..dir_count {
// `needs_interp_filter( )` per av1-spec p.75. Inlined so the
// §5.11.x spec body's gates flow visibly with the
// surrounding S() machinery.
let large = core::cmp::min(block_width(sub_size), block_height(sub_size)) >= 8;
let needs = if skip_mode != 0 || motion_mode == MOTION_MODE_WARPED_CAUSAL {
false
} else if large && y_mode == MODE_GLOBALMV {
let r0 = ref_frame[0];
if (0..8).contains(&r0) {
gm_type[r0 as usize] == GM_TYPE_TRANSLATION
} else {
false
}
} else if large && y_mode == MODE_GLOBAL_GLOBALMV {
let r0 = ref_frame[0];
let r1 = ref_frame[1];
let t0 = (0..8).contains(&r0) && gm_type[r0 as usize] == GM_TYPE_TRANSLATION;
let t1 = (0..8).contains(&r1) && gm_type[r1 as usize] == GM_TYPE_TRANSLATION;
t0 || t1
} else {
true
};
if !needs {
// §5.11.x `interp_filter[ dir ] = EIGHTTAP`. No bits
// read; the pre-init `EIGHTTAP` value already in
// `filters[dir]` matches.
continue;
}
// §5.11.x `interp_filter[ dir ] S()` against
// `TileInterpFilterCdf[ ctx ]`. Build `ctx` via the §8.3.2
// walk inlined here so the neighbour-grid read is visibly
// local to the §5.11.x reader.
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let dir_u32 = dir as u32;
// §8.3.2 above-neighbour: `aboveType = InterpFilters[ MiRow -
// 1 ][ MiCol ][ dir ]` only when `RefFrames[ MiRow - 1 ][
// MiCol ][ 0 ] == RefFrame[ 0 ]` or `[ 1 ] == RefFrame[ 0 ]`;
// else the `3` sentinel ([`INTERP_FILTER_NONE`]).
let above_type = if avail_u && mi_row > 0 {
let row = (mi_row - 1) as i32;
let col = mi_col as i32;
let n0 = self.ref_frame_at(row, col, 0) as i32;
let n1 = self.ref_frame_at(row, col, 1) as i32;
if n0 == ref_frame[0] || n1 == ref_frame[0] {
let cell = ((mi_row - 1) * self.mi_cols + mi_col) as usize;
self.interp_filters[cell * 2 + dir] as usize
} else {
INTERP_FILTER_NONE
}
} else {
INTERP_FILTER_NONE
};
let left_type = if avail_l && mi_col > 0 {
let row = mi_row as i32;
let col = (mi_col - 1) as i32;
let n0 = self.ref_frame_at(row, col, 0) as i32;
let n1 = self.ref_frame_at(row, col, 1) as i32;
if n0 == ref_frame[0] || n1 == ref_frame[0] {
let cell = (mi_row * self.mi_cols + (mi_col - 1)) as usize;
self.interp_filters[cell * 2 + dir] as usize
} else {
INTERP_FILTER_NONE
}
} else {
INTERP_FILTER_NONE
};
let ctx = interp_filter_ctx(above_type, left_type, dir_u32, is_compound)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let row = cdfs
.interp_filter_cdf(ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let sym = decoder.read_symbol(row)? as u8;
debug_assert!(
(sym as usize) < INTERP_FILTERS,
"S() over TileInterpFilterCdf yields 0..INTERP_FILTERS"
);
filters[dir] = sym;
read_flags[dir] = true;
}
// §5.11.x post-loop mirror: `if ( !enable_dual_filter )
// interp_filter[ 1 ] = interp_filter[ 0 ]`. No second read in
// this arm — `read_from_bitstream[1]` stays `false`.
if !enable_dual_filter {
filters[1] = filters[0];
}
Ok(InterpolationFilterReadout {
interp_filter: filters,
read_from_bitstream: read_flags,
})
}
/// `assign_mv( isCompound )` per §5.11.31 (av1-spec p.78). Iterates
/// over the active reference lists (`i = 0..1+isCompound`) and
/// resolves each per-list `Mv[ i ]` to one of:
///
/// * `GlobalMvs[ i ]` when `compMode == GLOBALMV`,
/// * `RefStackMv[ pos ][ i ]` for the NEAREST / NEAR family with
/// `pos = ( compMode == NEARESTMV ) ? 0 : RefMvIdx` (forced to
/// `0` when `compMode == NEWMV && NumMvFound <= 1`),
/// * `PredMv[ i ] + diffMv` for `compMode == NEWMV` (the
/// §5.11.31 `read_mv()` reader is invoked, which composes
/// `mv_joint S()` + per-axis `read_mv_component()` reads).
///
/// `use_intrabc` is the §5.11.7 `use_intrabc` flag. The inter
/// caller (this crate's §5.11.23
/// [`Self::decode_inter_block_mode_info`]) always passes `false`;
/// the §5.11.7 intra-bc arm is the only caller for `true`, and a
/// subsequent intra-bc arc will provide a thin wrapper that
/// supplies the §5.11.31 intrabc fallback (`sbSize` / `MiRowStart`
/// inputs the inter path does not need).
///
/// Returns the `Mv[ 0..2 ]` array. For single-pred calls the slot
/// `Mv[ 1 ]` is left at `[0, 0]` (matching the §5.11.5 pre-fill).
#[allow(clippy::too_many_arguments)]
fn assign_mv(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
is_compound: bool,
use_intrabc: bool,
y_mode: u8,
ref_mv_idx: u32,
num_mv_found: u32,
ref_stack_mv: &[[[i32; 2]; 2]; MAX_REF_MV_STACK_SIZE],
global_mvs: &[[i32; 2]; 2],
allow_high_precision_mv: bool,
force_integer_mv: bool,
) -> Result<[[i32; 2]; 2], crate::Error> {
// §5.11.31 `MvCtx` derivation (av1-spec p.78): MvCtx =
// MV_INTRABC_CONTEXT when use_intrabc else 0. Computed once
// here and passed through to each `read_mv` call.
let mv_ctx = mv_ctx(use_intrabc);
// r174 inter-only guard: the inter dispatcher hard-codes
// `use_intrabc = false`. Reject the inter-arm call landing on
// `use_intrabc = true` (which would need the §5.11.7 intra-bc
// sbSize / MiRowStart inputs not threaded through this method).
// The future intra-bc arc will surface that path via a separate
// wrapper. Surface as `PartitionWalkOutOfRange` (the standard
// caller-bug variant) rather than minting a new gap variant.
if use_intrabc {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let list_count = if is_compound { 2 } else { 1 };
let mut mv: [[i32; 2]; 2] = [[0, 0], [0, 0]];
for i in 0..list_count {
// §5.11.31 compMode selection (av1-spec p.78). `use_intrabc`
// is false in this branch, so always take the §5.11.30
// `get_mode( i )` path.
let comp_mode = get_mode(y_mode, i);
// §5.11.31 PredMv derivation (av1-spec p.78). With
// `use_intrabc == 0`:
// if ( compMode == GLOBALMV )
// PredMv[ i ] = GlobalMvs[ i ]
// else {
// pos = ( compMode == NEARESTMV ) ? 0 : RefMvIdx
// if ( compMode == NEWMV && NumMvFound <= 1 ) pos = 0
// PredMv[ i ] = RefStackMv[ pos ][ i ]
// }
let pred_mv: [i32; 2] = if comp_mode == MODE_GLOBALMV {
global_mvs[i]
} else {
let mut pos = if comp_mode == MODE_NEARESTMV {
0u32
} else {
ref_mv_idx
};
if comp_mode == MODE_NEWMV && num_mv_found <= 1 {
pos = 0;
}
// pos is in 0..MAX_REF_MV_STACK_SIZE = 0..8 by
// construction (RefMvIdx is bounded at 2 by the
// §5.11.23 drl_mode loops; 0/RefMvIdx is therefore in
// 0..=2). Indexing into the 8-slot stack is safe.
debug_assert!(
(pos as usize) < MAX_REF_MV_STACK_SIZE,
"PredMv pos must be in 0..MAX_REF_MV_STACK_SIZE"
);
ref_stack_mv[pos as usize][i]
};
// §5.11.31: `if ( compMode == NEWMV ) read_mv( i ) else
// Mv[ i ] = PredMv[ i ]`.
if comp_mode == MODE_NEWMV {
mv[i] = self.read_mv(
decoder,
cdfs,
mv_ctx,
pred_mv,
allow_high_precision_mv,
force_integer_mv,
)?;
} else {
mv[i] = pred_mv;
}
}
Ok(mv)
}
/// `read_mv( ref )` per §5.11.31 (av1-spec p.78). Composes:
///
/// * `mv_joint` S() against `TileMvJointCdf[ MvCtx ]` — the 4-way
/// joint code (`MV_JOINT_ZERO` / `MV_JOINT_HNZVZ` /
/// `MV_JOINT_HZVNZ` / `MV_JOINT_HNZVNZ`).
/// * `read_mv_component( 0 )` gated on `MV_JOINT_HZVNZ ||
/// MV_JOINT_HNZVNZ` (note: the spec body's `MV_JOINT_HZVNZ`
/// gates `diffMv[0]`, not `diffMv[1]` — see §6.10.27 "MV Joint"
/// table; the labels track horizontal / vertical pixel axes
/// while `comp = 0` is the row (vertical) component, so the
/// gating is `comp = 0 ⇔ vertical-non-zero` per the spec text).
/// * `read_mv_component( 1 )` gated on `MV_JOINT_HNZVZ ||
/// MV_JOINT_HNZVNZ` (likewise `comp = 1 ⇔ horizontal-non-zero`).
/// * `Mv[ ref ][ comp ] = PredMv[ ref ][ comp ] + diffMv[ comp ]`
/// per-component addition. The caller is responsible for the
/// §6.10.25 `is_mv_valid` conformance bound (`Abs(Mv) < (1 <<
/// 14)`); this method does NOT clamp.
///
/// Returns the final `Mv[ ref ]` pair (row, col) in 1/8-luma-sample
/// units.
fn read_mv(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mv_ctx: usize,
pred_mv: [i32; 2],
allow_high_precision_mv: bool,
force_integer_mv: bool,
) -> Result<[i32; 2], crate::Error> {
let mut diff_mv: [i32; 2] = [0, 0];
// §5.11.31 `mv_joint` S() against `TileMvJointCdf[ MvCtx ]`.
let row = cdfs.mv_joint_cdf(mv_ctx);
let mv_joint = decoder.read_symbol(row)? as u8;
debug_assert!(
(mv_joint as usize) < MV_JOINTS,
"S() over TileMvJointCdf yields 0..MV_JOINTS"
);
// §5.11.31 gating: `MV_JOINT_HZVNZ` gates `diffMv[ 0 ]` and
// `MV_JOINT_HNZVZ` gates `diffMv[ 1 ]`. (The §6.10.27 table
// names track pixel-axis labels but the index gates are
// explicit in the §5.11.31 syntax body.)
if mv_joint == MV_JOINT_HZVNZ || mv_joint == MV_JOINT_HNZVNZ {
diff_mv[0] = self.read_mv_component(
decoder,
cdfs,
/* comp = */ 0,
mv_ctx,
allow_high_precision_mv,
force_integer_mv,
)?;
}
if mv_joint == MV_JOINT_HNZVZ || mv_joint == MV_JOINT_HNZVNZ {
diff_mv[1] = self.read_mv_component(
decoder,
cdfs,
/* comp = */ 1,
mv_ctx,
allow_high_precision_mv,
force_integer_mv,
)?;
}
// §5.11.31 final assignment: `Mv[ ref ][ c ] = PredMv[ ref ][
// c ] + diffMv[ c ]`. Use `wrapping_add` to mirror the spec's
// arithmetic (the §6.10.25 conformance bound `Abs(Mv) < (1 <<
// 14)` is checked by the caller).
Ok([
pred_mv[0].wrapping_add(diff_mv[0]),
pred_mv[1].wrapping_add(diff_mv[1]),
])
}
/// `read_mv_component( comp )` per §5.11.32 (av1-spec p.81-82).
/// Reads one MV component (row when `comp == 0`, column when
/// `comp == 1`) as a signed magnitude in 1/8-luma-sample units.
///
/// Spec body composition (av1-spec p.81-82):
///
/// 1. `mv_sign` S() against `TileMvSignCdf[ MvCtx ][ comp ]`.
/// 2. `mv_class` S() against `TileMvClassCdf[ MvCtx ][ comp ]` —
/// one of `MV_CLASS_0..=MV_CLASS_10` (`0..=10`).
/// 3. `MV_CLASS_0` branch (small magnitudes):
/// * `mv_class0_bit` S() — the only `mv_class0_bit` slot is the
/// flat 1-of-2 distribution.
/// * `mv_class0_fr` S() against the per-class0_bit CDF, OR
/// `mv_class0_fr = 3` when `force_integer_mv == 1` (the
/// §5.9.5 frame-header bit).
/// * `mv_class0_hp` S() OR `mv_class0_hp = 1` when
/// `allow_high_precision_mv == 0` (the §5.9.5 frame-header
/// bit). The §5.11.32 spec text uses the "fall-through to 1"
/// convention (not 0) to keep the §6.10.27 `mag` formula
/// `+ 1` arithmetic consistent.
/// * `mag = ( ( mv_class0_bit << 3 ) | ( mv_class0_fr << 1 ) |
/// mv_class0_hp ) + 1`.
/// 4. `MV_CLASS_K` branch for `K >= 1` (larger magnitudes):
/// * `mv_bit` S() loop reading `K` bits (one CDF per bit
/// position via `TileMvBitCdf[ MvCtx ][ comp ][ i ]`), packed
/// into `d = sum(mv_bit_i << i)`.
/// * `mag = CLASS0_SIZE << ( mv_class + 2 )`.
/// * `mv_fr` S() OR `mv_fr = 3` (force_integer_mv arm).
/// * `mv_hp` S() OR `mv_hp = 1` (allow_high_precision_mv arm).
/// * `mag += ( ( d << 3 ) | ( mv_fr << 1 ) | mv_hp ) + 1`.
/// 5. `return mv_sign ? -mag : mag`.
///
/// Returns the signed component magnitude as `i32` (the §6.10.25
/// conformance bound `Abs(Mv) < (1 << 14)` is checked by the
/// §5.11.31 caller). The maximum magnitude this method can produce
/// is bounded by `CLASS0_SIZE << ( MV_CLASSES + 1 ) - 1 = 4095`
/// for the class arm baseline plus the `( d << 3 | mv_fr << 1 |
/// mv_hp ) + 1` increment up to `8 << ( MV_CLASS_10 - 1 ) + ...`,
/// all of which fit in `i16` by construction (the §6.10.25 bound
/// is a tighter `1 << 14 = 16384`).
fn read_mv_component(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
comp: usize,
mv_ctx: usize,
allow_high_precision_mv: bool,
force_integer_mv: bool,
) -> Result<i32, crate::Error> {
// §5.11.32 line 1: `mv_sign` S().
let row = cdfs.mv_sign_cdf(mv_ctx, comp);
let mv_sign = decoder.read_symbol(row)? as u8;
debug_assert!(mv_sign <= 1, "S() over TileMvSignCdf yields 0 or 1");
// §5.11.32 line 2: `mv_class` S().
let row = cdfs.mv_class_cdf(mv_ctx, comp);
let mv_class = decoder.read_symbol(row)? as u8;
debug_assert!(
(mv_class as usize) < MV_CLASSES,
"S() over TileMvClassCdf yields 0..MV_CLASSES = 0..11"
);
let mag: i32 = if mv_class == MV_CLASS_0 {
// §5.11.32 MV_CLASS_0 ladder (av1-spec p.81 lines 3-9).
// `mv_class0_bit` S().
let row = cdfs.mv_class0_bit_cdf(mv_ctx, comp);
let mv_class0_bit = decoder.read_symbol(row)? as u8;
debug_assert!(
(mv_class0_bit as usize) < CLASS0_SIZE,
"S() over TileMvClass0BitCdf yields 0..CLASS0_SIZE = 0..2"
);
// `mv_class0_fr`: gated on `force_integer_mv`.
let mv_class0_fr: u8 = if force_integer_mv {
3
} else {
let row = cdfs.mv_class0_fr_cdf(mv_ctx, comp, mv_class0_bit as usize);
let v = decoder.read_symbol(row)? as u8;
debug_assert!(
(v as usize) < MV_JOINTS,
"S() over TileMvClass0FrCdf yields 0..4"
);
v
};
// `mv_class0_hp`: gated on `allow_high_precision_mv`. The
// §5.11.32 spec text uses fall-through value `1` (not 0) so
// the `+ 1` outer arithmetic remains correct without
// requiring the explicit hp bit.
let mv_class0_hp: u8 = if allow_high_precision_mv {
let row = cdfs.mv_class0_hp_cdf(mv_ctx, comp);
let v = decoder.read_symbol(row)? as u8;
debug_assert!(v <= 1, "S() over TileMvClass0HpCdf yields 0 or 1");
v
} else {
1
};
// §5.11.32 mag formula: `( ( mv_class0_bit << 3 ) | (
// mv_class0_fr << 1 ) | mv_class0_hp ) + 1`.
(((mv_class0_bit as i32) << 3) | ((mv_class0_fr as i32) << 1) | (mv_class0_hp as i32))
+ 1
} else {
// §5.11.32 MV_CLASS_K ladder for K >= 1 (av1-spec p.81-82
// lines 10-17).
//
// `d = 0; for ( i = 0; i < mv_class; i++ ) { mv_bit S(); d
// |= mv_bit << i }` — `mv_class` is in `1..=10` here, and
// `MV_OFFSET_BITS = 10` is the spec's upper bound. The
// §8.3.2 cdf selection is `TileMvBitCdf[ MvCtx ][ comp ][ i
// ]` for each bit position.
let mut d: u32 = 0;
for i in 0..(mv_class as usize) {
debug_assert!(
i < MV_OFFSET_BITS,
"mv_class iterations bounded by MV_OFFSET_BITS = {MV_OFFSET_BITS}"
);
let row = cdfs.mv_bit_cdf(mv_ctx, comp, i);
let mv_bit = decoder.read_symbol(row)?;
debug_assert!(mv_bit <= 1, "S() over TileMvBitCdf yields 0 or 1");
d |= mv_bit << i;
}
// `mag = CLASS0_SIZE << ( mv_class + 2 )` (the
// `MV_CLASS_K` baseline magnitude).
let mut mag: i32 = (CLASS0_SIZE as i32) << (mv_class + 2);
let mv_fr: u8 = if force_integer_mv {
3
} else {
let row = cdfs.mv_fr_cdf(mv_ctx, comp);
let v = decoder.read_symbol(row)? as u8;
debug_assert!((v as usize) < MV_JOINTS, "S() over TileMvFrCdf yields 0..4");
v
};
let mv_hp: u8 = if allow_high_precision_mv {
let row = cdfs.mv_hp_cdf(mv_ctx, comp);
let v = decoder.read_symbol(row)? as u8;
debug_assert!(v <= 1, "S() over TileMvHpCdf yields 0 or 1");
v
} else {
1
};
// `mag += ( ( d << 3 ) | ( mv_fr << 1 ) | mv_hp ) + 1`.
mag += (((d as i32) << 3) | ((mv_fr as i32) << 1) | (mv_hp as i32)) + 1;
mag
};
// §5.11.32 final `return mv_sign ? -mag : mag`.
Ok(if mv_sign != 0 { -mag } else { mag })
}
/// `read_ref_frames()` per §5.11.25 (av1-spec p.76) — the
/// per-block reference-frame syntax tree consumed by
/// [`Self::decode_inter_block_mode_info`]. Returns `RefFrame[ 0..2 ]`
/// as `[i32; 2]` with `INTRA_FRAME = 0..=ALTREF_FRAME = 7` in slot 0
/// and `NONE = -1` or `LAST_FRAME..=ALTREF_FRAME` in slot 1.
///
/// Method body composes the §5.11.25 four-arm outer dispatch:
///
/// 1. `skip_mode == 1` ⇒ `RefFrame[ 0..2 ] = SkipModeFrame[ 0..2 ]`
/// (no bits read).
/// 2. `seg_ref_frame_active == 1` ⇒
/// `RefFrame[ 0 ] = seg_ref_frame_data`, `RefFrame[ 1 ] = NONE`
/// (no bits read).
/// 3. `seg_skip_active || seg_globalmv_active` ⇒
/// `RefFrame[ 0 ] = LAST_FRAME`, `RefFrame[ 1 ] = NONE` (no
/// bits read).
/// 4. Else: the §5.11.25 default arm with `comp_mode` /
/// `comp_ref_type` / per-ref `S()` cascade against the §8.3.2
/// ctxes; see [`Self::decode_inter_block_mode_info`] for the
/// full enumeration of `S()` reads.
#[allow(clippy::too_many_arguments)]
fn read_ref_frames(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
sub_size: usize,
skip_mode: u8,
avail_u: bool,
avail_l: bool,
above_ref_frame: [i32; 2],
left_ref_frame: [i32; 2],
above_intra: bool,
left_intra: bool,
above_single: bool,
left_single: bool,
skip_mode_frame: [i32; 2],
seg_ref_frame_active: bool,
seg_ref_frame_data: i32,
seg_skip_active: bool,
seg_globalmv_active: bool,
reference_select: bool,
) -> Result<[i32; 2], crate::Error> {
// §5.11.25 arm 1: `if ( skip_mode )`. No bits read; values
// taken from the frame-header §5.9.22 derivation.
if skip_mode != 0 {
return Ok([skip_mode_frame[0], skip_mode_frame[1]]);
}
// §5.11.25 arm 2: `else if ( seg_feature_active( SEG_LVL_REF_FRAME ) )`.
// `RefFrame[ 1 ] = NONE = -1`.
if seg_ref_frame_active {
return Ok([seg_ref_frame_data, -1]);
}
// §5.11.25 arm 3: `else if ( seg_feature_active( SEG_LVL_SKIP )
// || seg_feature_active( SEG_LVL_GLOBALMV ) )`.
// `RefFrame[ 0 ] = LAST_FRAME = 1`, `RefFrame[ 1 ] = NONE = -1`.
if seg_skip_active || seg_globalmv_active {
return Ok([1, -1]);
}
// §5.11.25 arm 4 (default): the syntax-tree branch.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
// §5.11.25: `if ( reference_select && Min(bw4, bh4) >= 2 )
// comp_mode S() else comp_mode = SINGLE_REFERENCE`.
let comp_mode = if reference_select && bw4.min(bh4) >= 2 {
let ctx = comp_mode_ctx(
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
);
let row = cdfs.comp_mode_cdf(ctx);
let v = decoder.read_symbol(row)? as u8;
debug_assert!(v <= 1, "comp_mode is binary (SINGLE | COMPOUND)");
v
} else {
0 // SINGLE_REFERENCE
};
let mut ref_frame: [i32; 2] = [0, -1];
if comp_mode == 1 {
// §5.11.25 COMPOUND_REFERENCE arm. First read
// `comp_ref_type` (`S()` over `TileCompRefTypeCdf[ ctx ]`,
// §8.3.2 p.382).
let crt_ctx = comp_ref_type_ctx(
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
);
let row = cdfs.comp_ref_type_cdf(crt_ctx);
let comp_ref_type = decoder.read_symbol(row)? as u8;
debug_assert!(comp_ref_type <= 1, "comp_ref_type is binary (UNI | BI)");
if comp_ref_type == 0 {
// §5.11.25 UNIDIR_COMP_REFERENCE arm. Read
// `uni_comp_ref` against `TileUniCompRefCdf[ ctx ][ 0 ]`
// (ctx via the `single_ref_p1` derivation per §8.3.2 p.383).
let ucr_ctx = ref_count_ctx(
count_refs(1, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(2, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame),
count_refs(5, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(6, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(7, avail_u, above_ref_frame, avail_l, left_ref_frame),
);
let row = cdfs.uni_comp_ref_cdf(ucr_ctx, 0);
let uni_comp_ref = decoder.read_symbol(row)? as u8;
debug_assert!(uni_comp_ref <= 1, "uni_comp_ref is binary");
if uni_comp_ref != 0 {
// BWDREF_FRAME = 5, ALTREF_FRAME = 7.
ref_frame = [5, 7];
} else {
// §5.11.25: read `uni_comp_ref_p1` against
// `TileUniCompRefCdf[ ctx ][ 1 ]`. The §8.3.2
// p.383 derivation uses `last2Count` vs
// `last3GoldCount`.
let last2 = count_refs(2, avail_u, above_ref_frame, avail_l, left_ref_frame);
let last3gold =
count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p1_ctx = ref_count_ctx(last2, last3gold);
let row = cdfs.uni_comp_ref_cdf(p1_ctx, 1);
let uni_comp_ref_p1 = decoder.read_symbol(row)? as u8;
debug_assert!(uni_comp_ref_p1 <= 1, "uni_comp_ref_p1 is binary");
if uni_comp_ref_p1 != 0 {
// §5.11.25: read `uni_comp_ref_p2` against
// `TileUniCompRefCdf[ ctx ][ 2 ]`. Ctx as in
// `comp_ref_p2` derivation (last3 vs gold).
let last3 =
count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame);
let gold = count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p2_ctx = ref_count_ctx(last3, gold);
let row = cdfs.uni_comp_ref_cdf(p2_ctx, 2);
let uni_comp_ref_p2 = decoder.read_symbol(row)? as u8;
debug_assert!(uni_comp_ref_p2 <= 1, "uni_comp_ref_p2 is binary");
if uni_comp_ref_p2 != 0 {
// LAST_FRAME = 1, GOLDEN_FRAME = 4.
ref_frame = [1, 4];
} else {
// LAST_FRAME = 1, LAST3_FRAME = 3.
ref_frame = [1, 3];
}
} else {
// LAST_FRAME = 1, LAST2_FRAME = 2.
ref_frame = [1, 2];
}
}
} else {
// §5.11.25 BIDIR_COMP_REFERENCE arm. Read
// `comp_ref` against `TileCompRefCdf[ ctx ][ 0 ]`.
// §8.3.2 p.366 derivation: `last12Count` vs
// `last3GoldCount`.
let last12 = count_refs(1, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(2, avail_u, above_ref_frame, avail_l, left_ref_frame);
let last3gold = count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame);
let cr_ctx = ref_count_ctx(last12, last3gold);
let row = cdfs.comp_ref_cdf(cr_ctx, 0);
let comp_ref = decoder.read_symbol(row)? as u8;
debug_assert!(comp_ref <= 1, "comp_ref is binary");
if comp_ref == 0 {
// §5.11.25: read `comp_ref_p1` against
// `TileCompRefCdf[ ctx ][ 1 ]`. §8.3.2 p.367 ctx:
// `lastCount` vs `last2Count`.
let last1 = count_refs(1, avail_u, above_ref_frame, avail_l, left_ref_frame);
let last2 = count_refs(2, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p1_ctx = ref_count_ctx(last1, last2);
let row = cdfs.comp_ref_cdf(p1_ctx, 1);
let comp_ref_p1 = decoder.read_symbol(row)? as u8;
debug_assert!(comp_ref_p1 <= 1, "comp_ref_p1 is binary");
// §5.11.25: `RefFrame[ 0 ] = comp_ref_p1 ?
// LAST2_FRAME : LAST_FRAME`.
ref_frame[0] = if comp_ref_p1 != 0 { 2 } else { 1 };
} else {
// §5.11.25: read `comp_ref_p2` against
// `TileCompRefCdf[ ctx ][ 2 ]`. §8.3.2 p.367 ctx:
// `last3Count` vs `goldCount`.
let last3 = count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame);
let gold = count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p2_ctx = ref_count_ctx(last3, gold);
let row = cdfs.comp_ref_cdf(p2_ctx, 2);
let comp_ref_p2 = decoder.read_symbol(row)? as u8;
debug_assert!(comp_ref_p2 <= 1, "comp_ref_p2 is binary");
// `RefFrame[ 0 ] = comp_ref_p2 ? GOLDEN_FRAME :
// LAST3_FRAME`.
ref_frame[0] = if comp_ref_p2 != 0 { 4 } else { 3 };
}
// §5.11.25: read `comp_bwdref` against
// `TileCompBwdRefCdf[ ctx ][ 0 ]`. §8.3.2 p.367 ctx:
// `brfarf2Count` vs `arfCount`.
let brfarf2 = count_refs(5, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(6, avail_u, above_ref_frame, avail_l, left_ref_frame);
let arf = count_refs(7, avail_u, above_ref_frame, avail_l, left_ref_frame);
let bw_ctx = ref_count_ctx(brfarf2, arf);
let row = cdfs.comp_bwd_ref_cdf(bw_ctx, 0);
let comp_bwdref = decoder.read_symbol(row)? as u8;
debug_assert!(comp_bwdref <= 1, "comp_bwdref is binary");
if comp_bwdref == 0 {
// §5.11.25: read `comp_bwdref_p1` against
// `TileCompBwdRefCdf[ ctx ][ 1 ]`. §8.3.2 p.367
// ctx: `brfCount` vs `arf2Count`.
let brf = count_refs(5, avail_u, above_ref_frame, avail_l, left_ref_frame);
let arf2 = count_refs(6, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p1_ctx = ref_count_ctx(brf, arf2);
let row = cdfs.comp_bwd_ref_cdf(p1_ctx, 1);
let comp_bwdref_p1 = decoder.read_symbol(row)? as u8;
debug_assert!(comp_bwdref_p1 <= 1, "comp_bwdref_p1 is binary");
// `RefFrame[ 1 ] = comp_bwdref_p1 ? ALTREF2_FRAME :
// BWDREF_FRAME`. ALTREF2_FRAME = 6, BWDREF_FRAME = 5.
ref_frame[1] = if comp_bwdref_p1 != 0 { 6 } else { 5 };
} else {
// `RefFrame[ 1 ] = ALTREF_FRAME = 7`.
ref_frame[1] = 7;
}
}
} else {
// §5.11.25 SINGLE_REFERENCE arm. Read `single_ref_p1`
// against `TileSingleRefCdf[ ctx ][ 0 ]`. §8.3.2 p.368
// ctx: `fwdCount` vs `bwdCount`.
let fwd = count_refs(1, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(2, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame);
let bwd = count_refs(5, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(6, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(7, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p1_ctx = ref_count_ctx(fwd, bwd);
let row = cdfs.single_ref_cdf(p1_ctx, 0);
let single_ref_p1 = decoder.read_symbol(row)? as u8;
debug_assert!(single_ref_p1 <= 1, "single_ref_p1 is binary");
if single_ref_p1 != 0 {
// §5.11.25: read `single_ref_p2` against
// `TileSingleRefCdf[ ctx ][ 1 ]`. Ctx as in comp_bwdref.
let brfarf2 = count_refs(5, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(6, avail_u, above_ref_frame, avail_l, left_ref_frame);
let arf = count_refs(7, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p2_ctx = ref_count_ctx(brfarf2, arf);
let row = cdfs.single_ref_cdf(p2_ctx, 1);
let single_ref_p2 = decoder.read_symbol(row)? as u8;
debug_assert!(single_ref_p2 <= 1, "single_ref_p2 is binary");
if single_ref_p2 == 0 {
// §5.11.25: read `single_ref_p6` against
// `TileSingleRefCdf[ ctx ][ 5 ]`. Ctx as in
// comp_bwdref_p1.
let brf = count_refs(5, avail_u, above_ref_frame, avail_l, left_ref_frame);
let arf2 = count_refs(6, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p6_ctx = ref_count_ctx(brf, arf2);
let row = cdfs.single_ref_cdf(p6_ctx, 5);
let single_ref_p6 = decoder.read_symbol(row)? as u8;
debug_assert!(single_ref_p6 <= 1, "single_ref_p6 is binary");
// `RefFrame[ 0 ] = single_ref_p6 ? ALTREF2_FRAME :
// BWDREF_FRAME`.
ref_frame[0] = if single_ref_p6 != 0 { 6 } else { 5 };
} else {
// `RefFrame[ 0 ] = ALTREF_FRAME = 7`.
ref_frame[0] = 7;
}
} else {
// §5.11.25: read `single_ref_p3` against
// `TileSingleRefCdf[ ctx ][ 2 ]`. Ctx as in comp_ref.
let last12 = count_refs(1, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(2, avail_u, above_ref_frame, avail_l, left_ref_frame);
let last3gold = count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame)
+ count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p3_ctx = ref_count_ctx(last12, last3gold);
let row = cdfs.single_ref_cdf(p3_ctx, 2);
let single_ref_p3 = decoder.read_symbol(row)? as u8;
debug_assert!(single_ref_p3 <= 1, "single_ref_p3 is binary");
if single_ref_p3 != 0 {
// §5.11.25: read `single_ref_p5` against
// `TileSingleRefCdf[ ctx ][ 4 ]`. Ctx as in
// comp_ref_p2.
let last3 = count_refs(3, avail_u, above_ref_frame, avail_l, left_ref_frame);
let gold = count_refs(4, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p5_ctx = ref_count_ctx(last3, gold);
let row = cdfs.single_ref_cdf(p5_ctx, 4);
let single_ref_p5 = decoder.read_symbol(row)? as u8;
debug_assert!(single_ref_p5 <= 1, "single_ref_p5 is binary");
// `RefFrame[ 0 ] = single_ref_p5 ? GOLDEN_FRAME :
// LAST3_FRAME`.
ref_frame[0] = if single_ref_p5 != 0 { 4 } else { 3 };
} else {
// §5.11.25: read `single_ref_p4` against
// `TileSingleRefCdf[ ctx ][ 3 ]`. Ctx as in
// comp_ref_p1.
let last1 = count_refs(1, avail_u, above_ref_frame, avail_l, left_ref_frame);
let last2 = count_refs(2, avail_u, above_ref_frame, avail_l, left_ref_frame);
let p4_ctx = ref_count_ctx(last1, last2);
let row = cdfs.single_ref_cdf(p4_ctx, 3);
let single_ref_p4 = decoder.read_symbol(row)? as u8;
debug_assert!(single_ref_p4 <= 1, "single_ref_p4 is binary");
// `RefFrame[ 0 ] = single_ref_p4 ? LAST2_FRAME :
// LAST_FRAME`.
ref_frame[0] = if single_ref_p4 != 0 { 2 } else { 1 };
}
}
// `RefFrame[ 1 ] = NONE = -1`.
ref_frame[1] = -1;
}
Ok(ref_frame)
}
/// Helper to read `InterTxSizes[ r ][ c ]` for the §8.3.2
/// `tx_depth` ctx walk. Returns `TX_4X4 = 0` for out-of-grid
/// coordinates (the §8.3.2 derivation falls back through the
/// `AvailU` / `AvailL` gate before consulting the grid, and the
/// `aboveW = 0` / `leftH = 0` fallback for an unavailable
/// neighbour matches what an unfilled cell would contribute via
/// `Tx_Width[ TX_4X4 ] = 4`).
#[inline]
fn inter_tx_size_at(&self, r: i32, c: i32) -> u8 {
if r < 0 || c < 0 {
return TX_4X4 as u8;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return TX_4X4 as u8;
}
self.inter_tx_sizes[(r * self.mi_cols + c) as usize]
}
/// Helper to read the §5.11.5 / §5.11.16 `TxSizes[ r ][ c ]` per-mi
/// luma transform-size grid (the intra-path stamp). Returns
/// `TX_4X4 = 0` for out-of-grid coordinates (the constructor
/// pre-fill). Used by [`Self::loopfilter_tx_size_at`] to recover
/// `LoopfilterTxSizes` for intra cells.
#[inline]
fn tx_size_at(&self, r: i32, c: i32) -> u8 {
if r < 0 || c < 0 {
return TX_4X4 as u8;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return TX_4X4 as u8;
}
self.tx_sizes[(r * self.mi_cols + c) as usize]
}
/// `read_block_tx_size()` per §5.11.16 (av1-spec p.70) — the
/// per-block transform-size syntax-tree read.
///
/// The spec body reads:
///
/// ```text
/// read_block_tx_size( ) {
/// bw4 = Num_4x4_Blocks_Wide[ MiSize ]
/// bh4 = Num_4x4_Blocks_High[ MiSize ]
/// if ( TxMode == TX_MODE_SELECT &&
/// MiSize > BLOCK_4X4 && is_inter &&
/// !skip && !Lossless ) {
/// maxTxSz = Max_Tx_Size_Rect[ MiSize ]
/// txW4 = Tx_Width[ maxTxSz ] / MI_SIZE
/// txH4 = Tx_Height[ maxTxSz ] / MI_SIZE
/// for ( row = MiRow; row < MiRow + bh4; row += txH4 )
/// for ( col = MiCol; col < MiCol + bw4; col += txW4 )
/// read_var_tx_size( row, col, maxTxSz, 0 )
/// } else {
/// read_tx_size( !skip || !is_inter )
/// for ( row = MiRow; row < MiRow + bh4; row++ )
/// for ( col = MiCol; col < MiCol + bw4; col++ )
/// InterTxSizes[ row ][ col ] = TxSize
/// }
/// }
/// ```
///
/// And the §5.11.15 `read_tx_size( allowSelect )` body the `else`
/// arm invokes (av1-spec p.69):
///
/// ```text
/// read_tx_size( allowSelect ) {
/// if ( Lossless ) { TxSize = TX_4X4; return }
/// maxRectTxSize = Max_Tx_Size_Rect[ MiSize ]
/// maxTxDepth = Max_Tx_Depth[ MiSize ]
/// TxSize = maxRectTxSize
/// if ( MiSize > BLOCK_4X4 && allowSelect && TxMode == TX_MODE_SELECT ) {
/// tx_depth S()
/// for ( i = 0; i < tx_depth; i++ )
/// TxSize = Split_Tx_Size[ TxSize ]
/// }
/// }
/// ```
///
/// And the §8.3.2 `tx_depth` ctx formula (av1-spec p.363):
///
/// ```text
/// maxTxWidth = Tx_Width[ maxRectTxSize ]
/// maxTxHeight = Tx_Height[ maxRectTxSize ]
/// if ( AvailU && IsInters[ MiRow - 1 ][ MiCol ] )
/// aboveW = Block_Width[ MiSizes[ MiRow - 1 ][ MiCol ] ]
/// else if ( AvailU )
/// aboveW = get_above_tx_width( MiRow, MiCol )
/// else
/// aboveW = 0
/// ... mirror for leftH ...
/// ctx = ( aboveW >= maxTxWidth ) + ( leftH >= maxTxHeight )
/// ```
///
/// Where (av1-spec p.364):
///
/// ```text
/// get_above_tx_width( row, col ) {
/// if ( row == MiRow ) {
/// if ( !AvailU ) return 64
/// else if ( Skips[ row - 1 ][ col ] && IsInters[ row - 1 ][ col ] )
/// return Block_Width[ MiSizes[ row - 1 ][ col ] ]
/// }
/// return Tx_Width[ InterTxSizes[ row - 1 ][ col ] ]
/// }
/// ```
///
/// Caller contract:
///
/// * `mi_row` / `mi_col` — block top-left (`MiRow` / `MiCol` per
/// §5.11.5).
/// * `sub_size` — the §5.11.5 `MiSize` ordinal.
/// * `lossless` — `LosslessArray[ segment_id ]` for this block per
/// §5.11.8. When `true` the spec forces `TxSize = TX_4X4` with
/// no symbol read.
/// * `is_inter` — `0` for intra blocks (the keyframe / intra-only
/// arm always passes `0`); `1` for inter blocks.
/// * `skip` — the §5.11.11 `skip` value (`0` or `1`).
/// * `tx_mode_select` — `true` ⇔ §5.9.21 / §6.8.21 `TxMode ==
/// TX_MODE_SELECT`. Sourced from the frame header by the caller.
///
/// Returns the §6.10.16 `TxSize` ordinal selected for the block
/// (one of `TX_4X4..TX_64X16`, in `0..TX_SIZES_ALL`). On the
/// `else` arm the value is also stamped into the
/// `bh4 * bw4` footprint of [`Self::inter_tx_sizes`] and
/// [`Self::tx_sizes`].
///
/// On the `TX_MODE_SELECT && is_inter && !skip && !Lossless` arm
/// the method enters the §5.11.17 [`Self::read_var_tx_size`]
/// recursion across the `(txH4, txW4)` sub-rectangles of the block
/// footprint. The §5.11.17 terminal-else arm stamps
/// `InterTxSizes[..] = txSz` for every leaf of the variable
/// transform tree; this method also writes the §5.11.5 outer
/// `TxSizes[ r + y ][ c + x ] = TxSize` grid-fill across the full
/// block footprint, using the last terminal-else's `txSz` as the
/// `TxSize` per §5.11.17.
#[allow(clippy::too_many_arguments)]
pub fn read_block_tx_size(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
lossless: bool,
is_inter: bool,
skip: bool,
tx_mode_select: bool,
) -> Result<u8, crate::Error> {
// Caller-bug guards (the §5.11.5 driver already gates these,
// but this method is also part of the public API).
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.16 lines 1-2: `bw4 = Num_4x4_Blocks_Wide[ MiSize ]`,
// `bh4 = Num_4x4_Blocks_High[ MiSize ]`. Bounded by the
// [`NUM_4X4_BLOCKS_WIDE`] / [`NUM_4X4_BLOCKS_HIGH`] spec
// tables.
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
// §5.11.16 outer dispatch: the `TX_MODE_SELECT && MiSize >
// BLOCK_4X4 && is_inter && !skip && !Lossless` arm enters the
// §5.11.17 `read_var_tx_size` recursion. The spec body loops
// over `(txH4, txW4)` sub-rectangles of the block footprint and
// calls `read_var_tx_size( row, col, maxTxSz, 0 )` for each.
if tx_mode_select && sub_size > BLOCK_4X4 && is_inter && !skip && !lossless {
// §5.11.16 lines 5-7: `maxTxSz = Max_Tx_Size_Rect[ MiSize ]`,
// `txW4 = Tx_Width[ maxTxSz ] / MI_SIZE`,
// `txH4 = Tx_Height[ maxTxSz ] / MI_SIZE`.
let max_tx_sz = MAX_TX_SIZE_RECT[sub_size];
let tx_w4 = (TX_WIDTH[max_tx_sz] / MI_SIZE) as u32;
let tx_h4 = (TX_HEIGHT[max_tx_sz] / MI_SIZE) as u32;
// §5.11.16 inner loops:
// for ( row = MiRow; row < MiRow + bh4; row += txH4 )
// for ( col = MiCol; col < MiCol + bw4; col += txW4 )
// read_var_tx_size( row, col, maxTxSz, 0 )
//
// tx_w4 / tx_h4 are positive for every reachable
// `(MiSize, maxTxSz)` pair (the smallest §6.10.16 transform
// size is `TX_4X4` ⇒ `Tx_Width / MI_SIZE = 1`).
debug_assert!(
tx_w4 > 0 && tx_h4 > 0,
"tx_w4 / tx_h4 must be positive for the §5.11.16 loop"
);
// The §5.11.17 recursion's terminal `else` arm stamps
// `TxSize = txSz` per sub-block; we keep the last write as
// the §5.11.5 outer-loop's `TxSize` value below for the
// `TxSizes[]` grid-fill identity.
let mut last_tx_size = max_tx_sz as u8;
let mut row = mi_row;
while row < mi_row + bh4 {
let mut col = mi_col;
while col < mi_col + bw4 {
last_tx_size = self.read_var_tx_size(
decoder, cdfs, mi_row, mi_col, sub_size, row, col, max_tx_sz, 0, is_inter,
)?;
col += tx_w4;
}
row += tx_h4;
}
// The §5.11.17 recursion stamps `InterTxSizes[]` per
// sub-block; the outer §5.11.5 `TxSizes[ r + y ][ c + x ] =
// TxSize` write still fires across the full block
// footprint. The §5.11.17 spec body sets `TxSize = txSz`
// each time the terminal `else` arm fires, so the
// last-write semantics match (r285 — via
// [`Self::stamp_tx_sizes_footprint`], shared with the
// §5.11.16 write driver's mirror).
self.stamp_tx_sizes_footprint(mi_row, mi_col, sub_size, last_tx_size);
return Ok(last_tx_size);
}
// §5.11.16 `else` arm: `read_tx_size( !skip || !is_inter )`.
// For the intra arm (`is_inter == 0`) `allowSelect` is always
// `true`. For the inter arm with `skip == 1` `allowSelect` is
// also `true`.
let allow_select = !skip || !is_inter;
// §5.11.15 body — `read_tx_size( allowSelect )`.
let tx_size = if lossless {
// §5.11.15 first line: `if ( Lossless ) { TxSize = TX_4X4;
// return }`.
TX_4X4 as u8
} else {
// §5.11.15 lines 2-5: `maxRectTxSize = Max_Tx_Size_Rect[
// MiSize ]`, `maxTxDepth = Max_Tx_Depth[ MiSize ]`,
// `TxSize = maxRectTxSize`.
let max_rect_tx_size = MAX_TX_SIZE_RECT[sub_size];
let max_tx_depth = MAX_TX_DEPTH_TABLE[sub_size];
let mut tx_size = max_rect_tx_size;
// §5.11.15 lines 6-9: the `tx_depth` symbol read +
// recursive `Split_Tx_Size` walk. Gated by
// `MiSize > BLOCK_4X4 && allowSelect && TxMode ==
// TX_MODE_SELECT`. The §8.3.2 `tx_depth_cdf` selector
// already returns `None` for `max_tx_depth == 0`, which
// matches the spec's "no tx_depth read in that case"
// (the §5.11.15 `MiSize > BLOCK_4X4` gate also excludes
// every `max_tx_depth == 0` row of [`MAX_TX_DEPTH_TABLE`]
// — only `BLOCK_4X4` has `max_tx_depth == 0`).
if sub_size > BLOCK_4X4 && allow_select && tx_mode_select {
// §8.3.2 `tx_depth` ctx derivation (av1-spec p.363) —
// factored into [`Self::tx_depth_block_ctx`] (r285) so
// the §5.11.16 write driver derives the identical
// value from its mirror walker.
let ctx = self.tx_depth_block_ctx(mi_row, mi_col, sub_size);
debug_assert!(
ctx < TX_SIZE_CONTEXTS,
"§8.3.2 tx_depth ctx is bounded by 0..TX_SIZE_CONTEXTS"
);
// §8.3.2 CDF selection: `Tile{Tx8x8,Tx16x16,Tx32x32,
// Tx64x64}Cdf[ ctx ]` per `max_tx_depth`. We already
// gated `MiSize > BLOCK_4X4` (which excludes the only
// `max_tx_depth == 0` row), so the selector returns
// `Some(_)`.
let cdf = cdfs
.tx_depth_cdf(max_tx_depth, ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let tx_depth = decoder.read_symbol(cdf)?;
// §5.11.15 line 8: `for ( i = 0; i < tx_depth; i++ )
// TxSize = Split_Tx_Size[ TxSize ]`.
// The §5.11.15 spec note bounds `tx_depth` to
// `0..=MAX_TX_DEPTH = 0..=2` (the `Default_Tx_*Cdf`
// row length caps the S() output). The `Split_Tx_Size`
// lookup is total over `0..TX_SIZES_ALL`.
debug_assert!(
tx_depth <= MAX_TX_DEPTH as u32,
"tx_depth in 0..=MAX_TX_DEPTH per §5.11.15 note"
);
for _ in 0..tx_depth {
debug_assert!(tx_size < TX_SIZES_ALL, "TxSize is bounded by TX_SIZES_ALL");
tx_size = SPLIT_TX_SIZE[tx_size];
}
}
tx_size as u8
};
// §5.11.16 `else` arm grid-fill:
// for ( row = MiRow; row < MiRow + bh4; row++ )
// for ( col = MiCol; col < MiCol + bw4; col++ )
// InterTxSizes[ row ][ col ] = TxSize
//
// Plus the §5.11.5 outer `TxSizes[ r + y ][ c + x ] = TxSize`
// grid-fill (which `decode_block` applies after
// `read_block_tx_size` returns). We perform both stamps here
// so the walker's grid invariants stay in sync (r285 — via
// [`Self::stamp_block_tx_size_grids`], shared with the
// §5.11.16 write driver's mirror).
self.stamp_block_tx_size_grids(mi_row, mi_col, sub_size, tx_size);
Ok(tx_size)
}
/// §8.3.2 `tx_depth` ctx derivation (av1-spec p.363) for the
/// §5.11.15 `tx_depth` S() read of the block anchored at
/// `(mi_row, mi_col)` with `MiSize = sub_size`:
///
/// ```text
/// maxTxWidth = Tx_Width[ maxRectTxSize ]
/// maxTxHeight = Tx_Height[ maxRectTxSize ]
/// if ( AvailU && IsInters[ MiRow - 1 ][ MiCol ] )
/// aboveW = Block_Width[ MiSizes[ MiRow - 1 ][ MiCol ] ]
/// else if ( AvailU )
/// aboveW = get_above_tx_width( MiRow, MiCol )
/// else
/// aboveW = 0
/// ... mirror for leftH via get_left_tx_height ...
/// ctx = ( aboveW >= maxTxWidth ) + ( leftH >= maxTxHeight )
/// ```
///
/// Factored out of [`Self::read_block_tx_size`] (r285) so the
/// encoder's §5.11.16 write driver derives the identical ctx from
/// its mirror walker. Side-effect free; the caller is responsible
/// for the `sub_size < BLOCK_SIZES` guard (the §5.11.16 reader and
/// the write driver both validate it before calling).
#[must_use]
pub fn tx_depth_block_ctx(&self, mi_row: u32, mi_col: u32, sub_size: usize) -> usize {
let max_rect_tx_size = MAX_TX_SIZE_RECT[sub_size];
let max_tx_width = TX_WIDTH[max_rect_tx_size] as u32;
let max_tx_height = TX_HEIGHT[max_rect_tx_size] as u32;
// The §8.3.2 `aboveW` ladder. For our walker `MiRow == row`
// always (the §8.3.2 helpers are written generically over
// `row` but only invoked at the block's top-left).
let avail_u = self.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = self.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let above_w: u32 = if avail_u {
let above_is_inter = self.is_inter_at(mi_row as i32 - 1, mi_col as i32);
if above_is_inter != 0 {
// `IsInters[ above ] == 1` ⇒ `aboveW =
// Block_Width[ MiSizes[ above ] ]`.
let nb = self.mi_size_at(mi_row as i32 - 1, mi_col as i32);
if nb < BLOCK_SIZES {
block_width(nb) as u32
} else {
// Unfilled cell (still BLOCK_INVALID) — treat the
// same as the `get_above_tx_width` fall-through
// `Tx_Width[ InterTxSizes ]` lookup with the
// initial-zero TX_4X4 ⇒ 4.
TX_WIDTH[TX_4X4] as u32
}
} else {
// `else if ( AvailU )` arm:
// aboveW = get_above_tx_width( MiRow, MiCol )
// For `row == MiRow && AvailU` and the
// `Skips[above] && IsInters[above]` condition false
// (we already established `IsInters == 0`), the
// fall-through returns
// `Tx_Width[ InterTxSizes[ row - 1 ][ col ] ]`.
let nb_tx = self.inter_tx_size_at(mi_row as i32 - 1, mi_col as i32);
TX_WIDTH[nb_tx as usize] as u32
}
} else {
0
};
let left_h: u32 = if avail_l {
let left_is_inter = self.is_inter_at(mi_row as i32, mi_col as i32 - 1);
if left_is_inter != 0 {
let nb = self.mi_size_at(mi_row as i32, mi_col as i32 - 1);
if nb < BLOCK_SIZES {
block_height(nb) as u32
} else {
TX_HEIGHT[TX_4X4] as u32
}
} else {
let nb_tx = self.inter_tx_size_at(mi_row as i32, mi_col as i32 - 1);
TX_HEIGHT[nb_tx as usize] as u32
}
} else {
0
};
tx_depth_ctx(above_w, left_h, max_tx_width, max_tx_height)
}
/// §5.11.16 `else`-arm grid-fill + the §5.11.5 outer `TxSizes[]`
/// fill — stamps `tx_size` into BOTH [`Self::inter_tx_sizes`] and
/// [`Self::tx_sizes`] across the `bh4 × bw4` footprint of the
/// block anchored at `(mi_row, mi_col)`, clipped at the frame's
/// mi extent. Shared by [`Self::read_block_tx_size`] and the
/// encoder's §5.11.16 write-driver mirror (r285).
pub fn stamp_block_tx_size_grids(
&mut self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
tx_size: u8,
) {
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let idx = (rr * self.mi_cols + cc) as usize;
self.inter_tx_sizes[idx] = tx_size;
self.tx_sizes[idx] = tx_size;
}
}
}
/// §5.11.16 var-tx-arm outer `TxSizes[]` fill — after the
/// §5.11.17 recursion stamps `InterTxSizes[]` per leaf, the
/// §5.11.5 outer `TxSizes[ r + y ][ c + x ] = TxSize` write still
/// fires across the full block footprint using the LAST
/// terminal-else's `txSz` (the §5.11.17 `TxSize = txSz`
/// last-write semantics). Shared by [`Self::read_block_tx_size`]
/// and the encoder's §5.11.16 write-driver mirror (r285).
pub fn stamp_tx_sizes_footprint(
&mut self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
tx_size: u8,
) {
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
self.tx_sizes[(rr * self.mi_cols + cc) as usize] = tx_size;
}
}
}
/// `read_var_tx_size( row, col, txSz, depth )` per §5.11.17
/// (av1-spec p.70) — reads the variable-transform-size sub-tree
/// for an inter block whose §5.11.16 outer dispatch entered the
/// `TX_MODE_SELECT && is_inter && !skip && !Lossless` arm.
///
/// Spec body (av1-spec p.70):
///
/// ```text
/// read_var_tx_size( row, col, txSz, depth ) {
/// if ( row >= MiRows || col >= MiCols )
/// return
/// if ( txSz == TX_4X4 || depth == MAX_VARTX_DEPTH ) {
/// txfm_split = 0
/// } else {
/// txfm_split S()
/// }
/// w4 = Tx_Width[ txSz ] / MI_SIZE
/// h4 = Tx_Height[ txSz ] / MI_SIZE
/// if ( txfm_split ) {
/// subTxSz = Split_Tx_Size[ txSz ]
/// stepW = Tx_Width[ subTxSz ] / MI_SIZE
/// stepH = Tx_Height[ subTxSz ] / MI_SIZE
/// for ( i = 0; i < h4; i += stepH )
/// for ( j = 0; j < w4; j += stepW )
/// read_var_tx_size( row + i, col + j, subTxSz, depth+1 )
/// } else {
/// for ( i = 0; i < h4; i++ )
/// for ( j = 0; j < w4; j++ )
/// InterTxSizes[ row + i ][ col + j ] = txSz
/// TxSize = txSz
/// }
/// }
/// ```
///
/// The §8.3.2 `txfm_split` ctx selector (av1-spec p.363-364) reads:
///
/// ```text
/// above = get_above_tx_width( row, col ) < Tx_Width[ txSz ]
/// left = get_left_tx_height( row, col ) < Tx_Height[ txSz ]
/// size = Min( 64, Max( Block_Width[ MiSize ], Block_Height[ MiSize ] ) )
/// maxTxSz = find_tx_size( size, size )
/// txSzSqrUp = Tx_Size_Sqr_Up[ txSz ]
/// ctx = (txSzSqrUp != maxTxSz) * 3
/// + (TX_SIZES - 1 - maxTxSz) * 6
/// + above + left
/// ```
///
/// where `get_above_tx_width` / `get_left_tx_height` are the
/// §8.3.2 helpers driving the `Skips[..] && IsInters[..]` early
/// return and the `Tx_Width[ InterTxSizes[..] ]` fall-through. Both
/// are inlined here against the walker grids (`self.skips`,
/// `self.is_inters`, `self.inter_tx_sizes`, `self.mi_sizes`) gated
/// on `TileGeometry::is_inside`.
///
/// The `mi_row_b` / `mi_col_b` arguments are the block's top-left
/// (§5.11.5 `MiRow` / `MiCol`); the §8.3.2 `get_above_tx_width` /
/// `get_left_tx_height` helpers distinguish `row == MiRow` (the
/// block's first sub-tile-row) from later iterations of the
/// `read_var_tx_size` loop. `sub_size` is `MiSize` (used for
/// `Block_Width[ MiSize ]` / `Block_Height[ MiSize ]` in the ctx
/// formula). `is_inter` is the §5.11.20 `is_inter` value the
/// caller has in scope (always `1` on the reachable §5.11.16 arm,
/// but kept as a parameter for direct-call symmetry with
/// `read_block_tx_size`).
///
/// Returns the per-sub-block `TxSize` the §5.11.17 spec body
/// assigns to the §5.11.5 `TxSize` variable on the recursion's
/// terminal-else arm — i.e. the `txSz` of the deepest split that
/// fires on the last `(i, j)` iteration. Returns
/// [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (out-of-range `sub_size`, anchor outside the frame,
/// `tx_sz >= TX_SIZES_ALL`, `depth > MAX_VARTX_DEPTH`).
/// [`crate::Error::UnexpectedEnd`] / [`crate::Error::SymbolExitUnderflow`]
/// surface bitstream errors from the §8.2.6 `S()` reads.
#[allow(clippy::too_many_arguments, clippy::only_used_in_recursion)]
pub fn read_var_tx_size(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row_b: u32,
mi_col_b: u32,
sub_size: usize,
row: u32,
col: u32,
tx_sz: usize,
depth: u32,
is_inter: bool,
) -> Result<u8, crate::Error> {
// Caller-bug guards.
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row_b >= self.mi_rows || mi_col_b >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if tx_sz >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if depth > MAX_VARTX_DEPTH {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.17 lines 1-2: `if ( row >= MiRows || col >= MiCols )
// return`. Frame-edge clip — the caller's outer loop walks the
// block footprint, which can extend past `MiRows` / `MiCols`
// for sub-blocks straddling the bottom or right edge of the
// frame.
if row >= self.mi_rows || col >= self.mi_cols {
return Ok(tx_sz as u8);
}
// §5.11.17 lines 3-7: `txfm_split` read. The two terminal
// conditions (`txSz == TX_4X4` and `depth == MAX_VARTX_DEPTH`)
// force the spec's `txfm_split = 0` (no S() read) — the
// recursion bottoms out either way.
let txfm_split: u8 = if tx_sz == TX_4X4 || depth == MAX_VARTX_DEPTH {
0
} else {
// §8.3.2 ctx derivation — factored into
// [`Self::txfm_split_node_ctx`] (r285) so the encoder's
// §5.11.17 write driver derives the identical value from
// its mirror walker.
let ctx = self.txfm_split_node_ctx(mi_row_b, mi_col_b, sub_size, row, col, tx_sz)?;
let cdf = cdfs.txfm_split_cdf(ctx);
// §8.2.6 S(): a 2-symbol read against
// `Default_Txfm_Split_Cdf` (length 3 including the counter
// slot) returns 0 or 1.
let sym = decoder.read_symbol(cdf)? as u8;
debug_assert!(sym <= 1, "S() over Default_Txfm_Split_Cdf yields 0 or 1");
sym
};
// §5.11.17 lines 8-9: `w4 = Tx_Width[ txSz ] / MI_SIZE`,
// `h4 = Tx_Height[ txSz ] / MI_SIZE`. Both ≥ 1 for every
// §6.10.16 ordinal (smallest is TX_4X4 = 4×4).
let w4 = (TX_WIDTH[tx_sz] / MI_SIZE) as u32;
let h4 = (TX_HEIGHT[tx_sz] / MI_SIZE) as u32;
if txfm_split != 0 {
// §5.11.17 lines 11-16: recursion. `subTxSz = Split_Tx_Size[
// txSz ]`; the recursive call increments depth.
let sub_tx_sz = SPLIT_TX_SIZE[tx_sz];
let step_w = (TX_WIDTH[sub_tx_sz] / MI_SIZE) as u32;
let step_h = (TX_HEIGHT[sub_tx_sz] / MI_SIZE) as u32;
debug_assert!(
step_w > 0 && step_h > 0,
"Split_Tx_Size step must advance the §5.11.17 loop"
);
// The §5.11.17 spec writes `TxSize = txSz` only on the
// terminal-else arm; the recursion propagates the last
// sub-call's terminal `txSz` upward.
let mut last_tx_sz = sub_tx_sz as u8;
let mut i = 0u32;
while i < h4 {
let mut j = 0u32;
while j < w4 {
last_tx_sz = self.read_var_tx_size(
decoder,
cdfs,
mi_row_b,
mi_col_b,
sub_size,
row + i,
col + j,
sub_tx_sz,
depth + 1,
is_inter,
)?;
j += step_w;
}
i += step_h;
}
Ok(last_tx_sz)
} else {
// §5.11.17 lines 17-21: terminal else arm. Stamp
// `InterTxSizes[ row + i ][ col + j ] = txSz` over the
// `(h4, w4)` footprint of this sub-tree's leaf, clipped at
// the frame's `MiRows` / `MiCols` extent (r285 — via
// [`Self::stamp_var_tx_leaf`], shared with the §5.11.17
// write driver's mirror).
self.stamp_var_tx_leaf(row, col, tx_sz);
// §5.11.17 line 21: `TxSize = txSz`. The §5.11.5 outer
// loop's TxSize identity is the last terminal-else's
// `txSz` — surfaced to the §5.11.16 caller via the
// return value.
Ok(tx_sz as u8)
}
}
/// §8.3.2 `txfm_split` ctx derivation (av1-spec p.363-364) for
/// one §5.11.17 `read_var_tx_size` node at `(row, col)` with the
/// node's `txSz`, inside the block anchored at
/// `(mi_row_b, mi_col_b)` with `MiSize = sub_size`:
///
/// ```text
/// above = get_above_tx_width( row, col ) < Tx_Width[ txSz ]
/// left = get_left_tx_height( row, col ) < Tx_Height[ txSz ]
/// size = Min( 64, Max( Block_Width[ MiSize ], Block_Height[ MiSize ] ) )
/// maxTxSz = find_tx_size( size, size )
/// txSzSqrUp = Tx_Size_Sqr_Up[ txSz ]
/// ctx = (txSzSqrUp != maxTxSz) * 3
/// + (TX_SIZES - 1 - maxTxSz) * 6
/// + above + left
/// ```
///
/// Factored out of [`Self::read_var_tx_size`] (r285) so the
/// encoder's §5.11.17 write driver derives the identical ctx from
/// its mirror walker. Side-effect free. The ctx is a function of
/// the RUNNING `InterTxSizes[]` state — earlier leaves of the same
/// block's recursion feed later nodes' ctx on both sides, so the
/// caller must interleave this derivation with
/// [`Self::stamp_var_tx_leaf`] in §5.11.17 visit order.
pub fn txfm_split_node_ctx(
&self,
mi_row_b: u32,
mi_col_b: u32,
sub_size: usize,
row: u32,
col: u32,
tx_sz: usize,
) -> Result<usize, crate::Error> {
if sub_size >= BLOCK_SIZES || tx_sz >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §8.3.2 helpers `get_above_tx_width` / `get_left_tx_height`
// against the walker's grids.
let above_w = self.get_above_tx_width(mi_row_b, row, col);
let left_h = self.get_left_tx_height(mi_col_b, row, col);
let above = above_w < TX_WIDTH[tx_sz] as u32;
let left = left_h < TX_HEIGHT[tx_sz] as u32;
// §8.3.2 `size = Min( 64, Max( Block_Width[ MiSize ],
// Block_Height[ MiSize ] ) )`.
let bw = block_width(sub_size);
let bh = block_height(sub_size);
let size = core::cmp::min(64, core::cmp::max(bw, bh));
// §8.3.2 `maxTxSz = find_tx_size( size, size )`. `size` is
// always a power-of-two in `4..=64` for any `MiSize`, so the
// lookup always hits a square `TX_NxN` ordinal.
let max_tx_sz = find_tx_size(size, size).ok_or(crate::Error::PartitionWalkOutOfRange)?;
// §8.3.2 `txSzSqrUp = Tx_Size_Sqr_Up[ txSz ]`. The §8.3.2 ctx
// formula consumes `(txSzSqrUp != maxTxSz)` as a 0/1
// multiplier, and `(TX_SIZES - 1 - maxTxSz)` as a 6×
// size-class term; both feed [`txfm_split_ctx`] in u32.
let tx_sz_sqr_up = TX_SIZE_SQR_UP[tx_sz] as u32;
txfm_split_ctx(above, left, tx_sz_sqr_up, max_tx_sz as u32)
.ok_or(crate::Error::PartitionWalkOutOfRange)
}
/// §5.11.17 terminal-else `InterTxSizes[]` stamp — fills the
/// `(h4, w4)` footprint of one variable-transform leaf at
/// `(row, col)` with `tx_sz`, clipped at the frame's mi extent.
/// Shared by [`Self::read_var_tx_size`] and the encoder's
/// §5.11.17 write-driver mirror (r285).
pub fn stamp_var_tx_leaf(&mut self, row: u32, col: u32, tx_sz: usize) {
debug_assert!(tx_sz < TX_SIZES_ALL, "tx_sz bounded by TX_SIZES_ALL");
let w4 = (TX_WIDTH[tx_sz] / MI_SIZE) as u32;
let h4 = (TX_HEIGHT[tx_sz] / MI_SIZE) as u32;
for i in 0..h4 {
let rr = row + i;
if rr >= self.mi_rows {
break;
}
for j in 0..w4 {
let cc = col + j;
if cc >= self.mi_cols {
break;
}
self.inter_tx_sizes[(rr * self.mi_cols + cc) as usize] = tx_sz as u8;
}
}
}
/// §8.3.2 `get_above_tx_width( row, col )` helper (av1-spec p.364)
/// — drives the `above` arm of the `txfm_split` ctx selector.
///
/// Spec body:
///
/// ```text
/// get_above_tx_width( row, col ) {
/// if ( row == MiRow ) {
/// if ( !AvailU ) return 64
/// else if ( Skips[ row - 1 ][ col ] && IsInters[ row - 1 ][ col ] )
/// return Block_Width[ MiSizes[ row - 1 ][ col ] ]
/// }
/// return Tx_Width[ InterTxSizes[ row - 1 ][ col ] ]
/// }
/// ```
///
/// `mi_row_b` is the block's top-left §5.11.5 `MiRow`; the
/// `row == MiRow` predicate fires only on the block's top sub-tile
/// row.
#[inline]
fn get_above_tx_width(&self, mi_row_b: u32, row: u32, col: u32) -> u32 {
if row == mi_row_b {
let avail_u = self.geometry.is_inside(row as i32 - 1, col as i32);
if !avail_u {
return 64;
}
let above_skip = self.skip_at(row as i32 - 1, col as i32);
let above_is_inter = self.is_inter_at(row as i32 - 1, col as i32);
if above_skip != 0 && above_is_inter != 0 {
let nb = self.mi_size_at(row as i32 - 1, col as i32);
// Unfilled cell (`BLOCK_INVALID`) ⇒ treat as `TX_4X4`
// width (the natural identity matching the
// initial-zero `InterTxSizes[]` state).
if nb < BLOCK_SIZES {
return block_width(nb) as u32;
}
return TX_WIDTH[TX_4X4] as u32;
}
}
let nb_tx = self.inter_tx_size_at(row as i32 - 1, col as i32);
TX_WIDTH[nb_tx as usize] as u32
}
/// §8.3.2 `get_left_tx_height( row, col )` helper (av1-spec p.364)
/// — mirrors [`Self::get_above_tx_width`] for the `left` arm.
#[inline]
fn get_left_tx_height(&self, mi_col_b: u32, row: u32, col: u32) -> u32 {
if col == mi_col_b {
let avail_l = self.geometry.is_inside(row as i32, col as i32 - 1);
if !avail_l {
return 64;
}
let left_skip = self.skip_at(row as i32, col as i32 - 1);
let left_is_inter = self.is_inter_at(row as i32, col as i32 - 1);
if left_skip != 0 && left_is_inter != 0 {
let nb = self.mi_size_at(row as i32, col as i32 - 1);
if nb < BLOCK_SIZES {
return block_height(nb) as u32;
}
return TX_HEIGHT[TX_4X4] as u32;
}
}
let nb_tx = self.inter_tx_size_at(row as i32, col as i32 - 1);
TX_HEIGHT[nb_tx as usize] as u32
}
/// `coeffs( plane, startX, startY, txSz )` per §5.11.39 (av1-spec
/// p.89-91) — the per-TU coefficient reader the §5.11.34 `residual()`
/// driver invokes via §5.11.35 `transform_block`'s
/// `eob = coeffs( plane, startX, startY, txSz )` line.
///
/// The spec body (av1-spec p.89-91) reads:
///
/// ```text
/// coeffs( plane, startX, startY, txSz ) {
/// x4 = startX >> 2
/// y4 = startY >> 2
/// w4 = Tx_Width[ txSz ] >> 2 ; h4 = Tx_Height[ txSz ] >> 2
/// txSzCtx = ( Tx_Size_Sqr[txSz] + Tx_Size_Sqr_Up[txSz] + 1 ) >> 1
/// ptype = plane > 0
/// segEob = ( txSz == TX_16X64 || txSz == TX_64X16 ) ? 512 :
/// Min( 1024, Tx_Width[ txSz ] * Tx_Height[ txSz ] )
/// for ( c = 0; c < segEob; c++ ) Quant[c] = 0
/// eob = 0 ; culLevel = 0 ; dcCategory = 0
/// all_zero S()
/// if ( all_zero ) {
/// // §5.11.39 line-5 short-circuit; the §5.11.40
/// // transform_type call site sets TxTypes[..] = DCT_DCT
/// // on the luma plane (deferred to caller).
/// } else {
/// // §5.11.40 `transform_type( x4, y4, txSz )` for luma
/// // (deferred to caller — txType / txClass passed in)
/// // eob_pt_* S() per eobMultisize → eobPt
/// // eob = ( eobPt < 2 ) ? eobPt : ( ( 1 << ( eobPt - 2 ) ) + 1 )
/// // eob_extra S() (only when eobShift >= 0)
/// // eob_extra_bit L(1) loop (eobPt - 3 raw bits)
/// // reverse-scan: coeff_base_eob S() at c==eob-1,
/// // coeff_base S() for c < eob-1
/// // followed by coeff_br S() chain for
/// // level > NUM_BASE_LEVELS
/// // forward-scan: dc_sign S() at c==0, sign_bit L(1)
/// // otherwise; golomb chain for
/// // Quant[pos] > NUM_BASE_LEVELS +
/// // COEFF_BASE_RANGE; dcCategory derive
/// // from pos == 0 sign; culLevel +=
/// // Quant[pos]; sign-apply
/// // culLevel = Min( 63, culLevel )
/// }
/// // AboveLevelContext / LeftLevelContext / AboveDcContext /
/// // LeftDcContext stamps (deferred to caller — the per-plane
/// // context arrays are out-of-scope until a follow-on arc).
/// return eob
/// }
/// ```
///
/// ## Caller-supplied state
///
/// The §8.3.2 selectors for `all_zero` / `eob_pt_*` / `eob_extra` /
/// `coeff_base{_eob}` / `coeff_br` / `dc_sign` require neighbour-side
/// context state (`AboveLevelContext` / `LeftLevelContext` /
/// `AboveDcContext` / `LeftDcContext`) the walker does not yet track
/// across TUs. This reader therefore takes the §8.3.2 ctx values as
/// pre-computed arguments rather than deriving them on the fly. The
/// caller (today: a §5.11.34 driver; tomorrow: the
/// [`Self::decode_block_syntax`] post-§5.11.30 site) is responsible
/// for:
///
/// * `tx_size` — the per-TU size, a [`TX_SIZES_ALL`] index
/// (`0..TX_SIZES_ALL`).
/// * `tx_class` — the §8.3.2 `get_tx_class( txType )` result, one of
/// [`TX_CLASS_2D`] / [`TX_CLASS_HORIZ`] / [`TX_CLASS_VERT`]. The
/// §5.11.40 `compute_tx_type()` derivation belongs to the caller
/// (the §5.11.40 luma `TxTypes[ y4 + j ][ x4 + i ] = DCT_DCT`
/// write on the `all_zero` arm also belongs to the caller — it
/// needs the walker's `TxTypes` grid, which is out-of-scope for
/// this reader).
/// * `plane` — `0` (Y) or `1`/`2` (U/V). `ptype = plane > 0` is
/// derived internally.
/// * `is_inter` — `0` or `1`. Drives the `eob_pt_*` `[ is_inter ]`
/// axis (only for the `eob_pt_16..256` tables; `eob_pt_512` /
/// `eob_pt_1024` have no `isInter` axis per §8.3.2).
/// * `txb_skip_ctx` — the §8.3.2 `all_zero` context in
/// `0..TXB_SKIP_CONTEXTS`. The §8.3.2 derivation (p.370-371)
/// reads `AboveLevelContext` / `LeftLevelContext` /
/// `AboveDcContext` / `LeftDcContext` to build it.
/// * `dc_sign_ctx` — the §8.3.2 `dc_sign` context in
/// `0..DC_SIGN_CONTEXTS` (p.377). Same neighbour-context source.
/// * `scan` — `scan[c]` for `c in 0..segEob` (the §5.11.39 scan
/// table — caller derived from `txSz` + `txType` per §7.5
/// `get_scan`). Length must be `>= segEob`.
/// * `quant` — the output `Quant[ pos ]` buffer, indexed `0..(tx_w *
/// tx_h)` where `tx_w * tx_h = Tx_Width[ txSz ] *
/// Tx_Height[ txSz ]`. The reader zero-fills `Quant[ 0..segEob ]`
/// at entry per the §5.11.39 line-3 pre-loop, then writes the
/// decoded signed levels at the scan positions. The caller's
/// buffer must therefore be `>= max(segEob, max_scan_pos + 1)`
/// cells — passing a slice of length `tx_w * tx_h` always
/// satisfies both bounds.
///
/// ## §5.11.34 deferred bodies
///
/// This reader implements §5.11.39 only. The wider §5.11.34
/// `residual()` body (the `widthChunks` / `heightChunks` /
/// `transform_tree` / `transform_block` dispatch) and §5.11.35's
/// per-TU surrounds (intra `predict_intra` / `predict_palette` /
/// `predict_chroma_from_luma`, `reconstruct`,
/// `LoopfilterTxSizes[]` / `BlockDecoded[]` stamps) are subsequent-
/// arc targets. Likewise §5.11.40 `transform_type` (the luma `S()`
/// over `TileTxTypeSet{1,2}Cdf`) and §5.11.41 `compute_tx_type`
/// belong to the §5.11.34 caller. The §7.13 `Dequant[][]` step
/// (true quantizer-matrix dequantization) is a later arc still.
///
/// ## Returns
///
/// A [`CoefficientsReadout`] with `all_zero` / `eob` / `cul_level` /
/// `dc_category`. The `Quant[ pos ]` levels (signed, in
/// `i32::MIN..=i32::MAX` per the spec's `& 0xFFFFF` clip and sign
/// apply) are written into the caller's buffer.
///
/// Returns [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// indices (`tx_size >= TX_SIZES_ALL`, `tx_class > TX_CLASS_VERT`,
/// `plane > 2`, `is_inter > 1`, `txb_skip_ctx >= TXB_SKIP_CONTEXTS`,
/// `dc_sign_ctx >= DC_SIGN_CONTEXTS`, `scan.len() < seg_eob`,
/// `quant.len() < tx_w * tx_h`).
#[allow(clippy::too_many_arguments)]
pub fn coefficients(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
plane: u8,
is_inter: u8,
tx_size: usize,
tx_class: usize,
txb_skip_ctx: usize,
dc_sign_ctx: usize,
scan: &[u16],
quant: &mut [i32],
) -> Result<CoefficientsReadout, crate::Error> {
// ---------------- caller-bug guards ----------------
if tx_size >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if tx_class > TX_CLASS_VERT {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if plane > 2 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if is_inter > 1 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if txb_skip_ctx >= TXB_SKIP_CONTEXTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if dc_sign_ctx >= DC_SIGN_CONTEXTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.39 lines 1-7: derived sizes.
let tx_w = TX_WIDTH[tx_size];
let tx_h = TX_HEIGHT[tx_size];
// §5.11.39 line 4: `txSzCtx = ( Tx_Size_Sqr[txSz] +
// Tx_Size_Sqr_Up[txSz] + 1 ) >> 1`. The §3 `Tx_Size_Sqr[ txSz ]`
// returns the square `TX_NxN` whose side length matches
// `Min( Tx_Width, Tx_Height )` for the rectangular `txSz`; we
// derive it from `Min( TX_WIDTH[txSz], TX_HEIGHT[txSz] )` via
// the `log2(side) - 2` rule (the square `TX_NxN` ordinal is
// `log2(side) - 2` because `TX_4X4 = 0`, `TX_8X8 = 1`, etc.).
// `Tx_Size_Sqr_Up[ txSz ]` is the existing [`TX_SIZE_SQR_UP`]
// table.
let tx_sz_sqr = {
let side = core::cmp::min(tx_w, tx_h);
// side ∈ {4, 8, 16, 32, 64}; log2 - 2 ∈ {0, 1, 2, 3, 4}.
(side.trailing_zeros() as usize) - 2
};
let tx_sz_sqr_up = TX_SIZE_SQR_UP[tx_size];
let tx_sz_ctx = (tx_sz_sqr + tx_sz_sqr_up + 1) >> 1;
// §5.11.39 line 6: `segEob = ( txSz == TX_16X64 || txSz ==
// TX_64X16 ) ? 512 : Min( 1024, Tx_Width[ txSz ] *
// Tx_Height[ txSz ] )`.
let seg_eob = if tx_size == TX_16X64 || tx_size == TX_64X16 {
512usize
} else {
core::cmp::min(1024, tx_w * tx_h)
};
// ---------------- caller-bug guards (continued) ----------------
if scan.len() < seg_eob {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// The Quant[] buffer is indexed by `pos = scan[c]`, which is
// bounded by `tx_w * tx_h - 1` per the §7.5 scan-table
// construction. The caller's buffer must accommodate every
// possible `pos`.
if quant.len() < tx_w * tx_h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.39 lines 8-9: `for ( c = 0; c < segEob; c++ ) Quant[c] =
// 0`. The spec only zeros `0..segEob` (not the wider
// `tx_w * tx_h` extent); we honour that, since the §5.11.39
// forward scan only ever touches positions in `scan[ 0..eob ]`,
// and `eob <= segEob` for all reachable code paths.
for cell in quant.iter_mut().take(seg_eob) {
*cell = 0;
}
// The §5.11.39 line-10/11 `Dequant[ 0..64 ][ 0..64 ] = 0` reset
// is the post-§5.11.39 dequantization step's input (filled by
// §7.13). It is out-of-scope for this reader — the caller (and
// the §7.13 reader) owns the `Dequant[][]` array.
// §5.11.39 line 13: `all_zero S()`.
let all_zero_cdf = cdfs
.txb_skip_cdf(tx_sz_ctx, txb_skip_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let all_zero = decoder.read_symbol(all_zero_cdf)?;
debug_assert!(all_zero <= 1, "S() over Default_Txb_Skip_Cdf yields 0 or 1");
// §5.11.39 line 14: `if ( all_zero ) { c = 0; ... }` — the
// short-circuit. The §5.11.40 `transform_type` line "if ( plane
// == 0 ) { for i,j: TxTypes[ y4 + j ][ x4 + i ] = DCT_DCT }"
// sits inside the if-arm; we surface that as a deferred
// caller-side stamp because TxTypes[] is not yet a walker grid.
if all_zero != 0 {
return Ok(CoefficientsReadout {
all_zero: true,
eob: 0,
cul_level: 0,
dc_category: 0,
});
}
self.coefficients_gate_open(
decoder,
cdfs,
plane,
is_inter,
tx_size,
tx_class,
dc_sign_ctx,
scan,
quant,
)
}
/// §5.11.39 line 13: the standalone `all_zero` S() read against
/// `TileTxbSkipCdf[ txSzCtx ][ ctx ]` — for callers that must
/// interleave the §5.11.47 `transform_type()` read between
/// `all_zero` and the EOB read per the §5.11.39 spec order (see
/// [`Self::coefficients_gate_open`]). Returns the decoded
/// `all_zero` flag.
pub fn read_all_zero(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
tx_size: usize,
txb_skip_ctx: usize,
) -> Result<bool, crate::Error> {
if tx_size >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if txb_skip_ctx >= TXB_SKIP_CONTEXTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.39 line 4: `txSzCtx = ( Tx_Size_Sqr[txSz] +
// Tx_Size_Sqr_Up[txSz] + 1 ) >> 1`.
let tx_sz_sqr = {
let side = core::cmp::min(TX_WIDTH[tx_size], TX_HEIGHT[tx_size]);
(side.trailing_zeros() as usize) - 2
};
let tx_sz_ctx = (tx_sz_sqr + TX_SIZE_SQR_UP[tx_size] + 1) >> 1;
let all_zero_cdf = cdfs
.txb_skip_cdf(tx_sz_ctx, txb_skip_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let all_zero = decoder.read_symbol(all_zero_cdf)?;
Ok(all_zero != 0)
}
/// §5.11.39 `coeffs()` — the gate-open body (everything **after**
/// the `all_zero` S() read, av1-spec p.89-93): the `eob_pt_*` /
/// `eob_extra` / `eob_extra_bit` EOB read, the reverse-scan
/// `coeff_base{_eob}` + `coeff_br` level loop, and the forward-scan
/// `dc_sign` / `sign_bit` / golomb pass.
///
/// Split out of [`Self::coefficients`] because the §5.11.39 spec
/// order interleaves the caller-owned §5.11.47 `transform_type()`
/// read between `all_zero` and the EOB read on the luma plane:
///
/// ```text
/// all_zero S()
/// if ( all_zero ) { ... TxTypes[..] = DCT_DCT ... }
/// else {
/// if ( plane == 0 )
/// transform_type( x4, y4, txSz ) // caller's read
/// PlaneTxType = compute_tx_type( ... ) // caller derives
/// scan = get_scan( txSz ) // tx_class/scan
/// eob_pt_* S() ... // THIS function
/// }
/// ```
///
/// A caller that must honour that order (the §5.11.35
/// `transform_block` dispatch) reads `all_zero` via
/// [`Self::read_all_zero`], performs its §5.11.47/§5.11.40
/// derivations, then invokes this body directly. Callers with a
/// pre-committed `tx_class`/`scan` (chroma, or the constrained
/// encoder-mirror drivers where §5.11.47 codes no symbol) can keep
/// using [`Self::coefficients`], whose behaviour is unchanged.
#[allow(clippy::too_many_arguments)]
pub fn coefficients_gate_open(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
plane: u8,
is_inter: u8,
tx_size: usize,
tx_class: usize,
dc_sign_ctx: usize,
scan: &[u16],
quant: &mut [i32],
) -> Result<CoefficientsReadout, crate::Error> {
// ---------------- caller-bug guards ----------------
if tx_size >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if tx_class > TX_CLASS_VERT {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if plane > 2 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if is_inter > 1 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if dc_sign_ctx >= DC_SIGN_CONTEXTS {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.39 lines 1-7: derived sizes (see [`Self::coefficients`]).
let tx_w = TX_WIDTH[tx_size];
let tx_h = TX_HEIGHT[tx_size];
let tx_sz_sqr = {
let side = core::cmp::min(tx_w, tx_h);
(side.trailing_zeros() as usize) - 2
};
let tx_sz_sqr_up = TX_SIZE_SQR_UP[tx_size];
let tx_sz_ctx = (tx_sz_sqr + tx_sz_sqr_up + 1) >> 1;
let ptype = (plane > 0) as usize;
let seg_eob = if tx_size == TX_16X64 || tx_size == TX_64X16 {
512usize
} else {
core::cmp::min(1024, tx_w * tx_h)
};
if scan.len() < seg_eob {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if quant.len() < tx_w * tx_h {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.39 lines 8-9 zero-fill (idempotent when the caller came
// through [`Self::coefficients`], which already zeroed).
for cell in quant.iter_mut().take(seg_eob) {
*cell = 0;
}
// ---------------- §5.11.39 EOB read ----------------
// §5.11.39 line 19: `eobMultisize = Min( Tx_Width_Log2[ txSz ],
// 5) + Min( Tx_Height_Log2[ txSz ], 5) - 4`. The constant `5` is
// the §3 `TX_64X64`-class log2 cap. `Tx_Height_Log2[ txSz ]` is
// not yet tabulated as a public constant; we derive it inline
// from `TX_HEIGHT[ txSz ] ∈ {4, 8, 16, 32, 64}`'s
// `trailing_zeros`.
let tx_height_log2_local = (tx_h.trailing_zeros() as usize) & 0xFF;
let eob_multisize: i32 = (core::cmp::min(TX_WIDTH_LOG2[tx_size], 5)
+ core::cmp::min(tx_height_log2_local, 5)) as i32
- 4;
debug_assert!(
(0..=6).contains(&eob_multisize),
"eobMultisize ∈ 0..=6 over every TX_SIZES_ALL ordinal"
);
// §5.11.39 lines 20-37: `eob_pt_*` S() per `eobMultisize`. The
// §8.3.2 ctx for the `eob_pt_16..eob_pt_256` family is `( get_tx_class
// == TX_CLASS_2D ) ? 0 : 1`; `eob_pt_512` / `eob_pt_1024` have
// no `[ is_inter ]` / `[ ctx ]` axis.
let eob_pt_ctx = if tx_class == TX_CLASS_2D { 0 } else { 1 };
let eob_pt: u32 = match eob_multisize {
0 => {
let cdf = cdfs
.eob_pt_16_cdf(ptype, eob_pt_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? + 1
}
1 => {
let cdf = cdfs
.eob_pt_32_cdf(ptype, eob_pt_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? + 1
}
2 => {
let cdf = cdfs
.eob_pt_64_cdf(ptype, eob_pt_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? + 1
}
3 => {
let cdf = cdfs
.eob_pt_128_cdf(ptype, eob_pt_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? + 1
}
4 => {
let cdf = cdfs
.eob_pt_256_cdf(ptype, eob_pt_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? + 1
}
5 => {
let cdf = cdfs
.eob_pt_512_cdf(ptype)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? + 1
}
_ => {
let cdf = cdfs
.eob_pt_1024_cdf(ptype)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? + 1
}
};
// §5.11.39 line 39: `eob = ( eobPt < 2 ) ? eobPt : ( ( 1 << (
// eobPt - 2 ) ) + 1 )`.
let mut eob: u32 = if eob_pt < 2 {
eob_pt
} else {
(1u32 << (eob_pt - 2)) + 1
};
// §5.11.39 line 40: `eobShift = Max( -1, eobPt - 3 )`. Signed
// because `eobPt` may be 1 or 2 (giving -2 / -1 before the Max).
let mut eob_shift: i32 = core::cmp::max(-1, eob_pt as i32 - 3);
// §5.11.39 lines 41-55: `eob_extra` S() then `eob_extra_bit`
// L(1) loop. Both contribute powers of two to `eob`.
if eob_shift >= 0 {
// §8.3.2 `eob_extra`: ctx = `eobPt - 3`.
let extra_ctx = (eob_pt - 3) as usize;
let cdf = cdfs
.eob_extra_cdf(tx_sz_ctx, ptype, extra_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let eob_extra = decoder.read_symbol(cdf)?;
debug_assert!(
eob_extra <= 1,
"S() over Default_Eob_Extra_Cdf yields 0 or 1"
);
if eob_extra != 0 {
eob += 1u32 << eob_shift;
}
// §5.11.39 lines 47-53: per-bit raw L(1) loop. `i` runs from
// 1 to Max( 0, eobPt - 2 ); inside, `eobShift = Max( 0,
// eobPt - 2 ) - 1 - i`.
let upper = core::cmp::max(0, eob_pt as i32 - 2);
let mut i: i32 = 1;
while i < upper {
eob_shift = upper - 1 - i;
let eob_extra_bit = decoder.read_literal(1)?;
if eob_extra_bit != 0 {
eob += 1u32 << eob_shift;
}
i += 1;
}
}
// ---------------- §5.11.39 reverse-scan: base levels ----------
// §5.11.39 lines 56-71. `c` runs eob-1 → 0. The first iteration
// reads `coeff_base_eob` against the EOB-specific ctx; subsequent
// iterations read `coeff_base` against the position-aware ctx.
// Both can extend via `coeff_br` S() chain.
//
// The §8.3.2 ctx derivations consume the running `Quant[]`
// array — every iteration of this loop writes one cell of
// `Quant[]`, and every subsequent iteration's ctx scan reads
// the already-written cells. The `Quant[]` array is the
// §5.11.39 line-9 zero-initialised buffer; the buffer must be
// wide enough to be indexed at every `pos = scan[c]`.
if eob > 0 {
let mut c: i32 = eob as i32 - 1;
while c >= 0 {
let pos = scan[c as usize] as usize;
// §8.3.2 ctx derivations — see [`get_coeff_base_ctx`] /
// [`get_coeff_base_eob_ctx`].
let is_eob = c == (eob as i32 - 1);
let mut level: u32;
if is_eob {
// §8.3.2 `coeff_base_eob`: ctx via
// [`get_coeff_base_eob_ctx`] (which folds in
// SIG_COEF_CONTEXTS_EOB - SIG_COEF_CONTEXTS).
let ctx = get_coeff_base_eob_ctx(quant, tx_size, tx_class, pos, c as usize);
let cdf = cdfs
.coeff_base_eob_cdf(tx_sz_ctx, ptype, ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// §5.11.39 line 60: `level = coeff_base_eob + 1`
// (the §9.4 alphabet is 3 values: 0, 1, 2 → levels
// 1, 2, 3).
let sym = decoder.read_symbol(cdf)?;
debug_assert!(sym <= 2, "S() over Default_Coeff_Base_Eob_Cdf yields 0..=2");
level = sym + 1;
} else {
// §8.3.2 `coeff_base`: ctx via
// [`get_coeff_base_ctx`] with `is_eob = false`.
let ctx = get_coeff_base_ctx(quant, tx_size, tx_class, pos, c as usize, false);
let cdf = cdfs
.coeff_base_cdf(tx_sz_ctx, ptype, ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// §5.11.39 line 63: `level = coeff_base` (the §9.4
// alphabet is 4 values: 0, 1, 2, 3 → levels 0, 1, 2, 3).
let sym = decoder.read_symbol(cdf)?;
debug_assert!(sym <= 3, "S() over Default_Coeff_Base_Cdf yields 0..=3");
level = sym;
}
// §5.11.39 lines 65-70: `coeff_br` chain when `level >
// NUM_BASE_LEVELS`. Iterations capped at COEFF_BASE_RANGE
// / ( BR_CDF_SIZE - 1 ) = 12 / 3 = 4. Each `coeff_br`
// S() returns 0..=BR_CDF_SIZE-1 = 0..=3; the chain
// terminates the first time the value is strictly less
// than BR_CDF_SIZE - 1 = 3.
if level as usize > NUM_BASE_LEVELS {
let br_iters = COEFF_BASE_RANGE / (BR_CDF_SIZE - 1);
for _ in 0..br_iters {
let ctx = get_br_ctx(quant, tx_size, tx_class, pos);
let cdf = cdfs
.coeff_br_cdf(tx_sz_ctx, ptype, ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let coeff_br = decoder.read_symbol(cdf)?;
debug_assert!(
(coeff_br as usize) < BR_CDF_SIZE,
"S() over Default_Coeff_Br_Cdf yields 0..BR_CDF_SIZE"
);
level += coeff_br;
if (coeff_br as usize) < BR_CDF_SIZE - 1 {
break;
}
}
}
// §5.11.39 line 71: `Quant[ pos ] = level` (magnitude
// only; the sign-apply runs in the forward-scan loop).
quant[pos] = level as i32;
c -= 1;
}
}
// ---------------- §5.11.39 forward-scan: signs + golomb -------
// §5.11.39 lines 73-100. Walks c from 0 to eob; for each
// non-zero `Quant[pos]` reads either `dc_sign` (S() at c == 0)
// or `sign_bit` (L(1) otherwise), then optionally extends the
// magnitude via the §5.11.39 lines 84-93 golomb chain when
// `Quant[pos] > NUM_BASE_LEVELS + COEFF_BASE_RANGE`.
let mut cul_level: u32 = 0;
let mut dc_category: u8 = 0;
for (c, &pos_u16) in scan.iter().enumerate().take(eob as usize) {
let pos = pos_u16 as usize;
let qpos = quant[pos];
let sign: u32 = if qpos != 0 {
if c == 0 {
// §8.3.2 `dc_sign`: ctx from caller.
let cdf = cdfs
.dc_sign_cdf(ptype, dc_sign_ctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let dc_sign = decoder.read_symbol(cdf)?;
debug_assert!(dc_sign <= 1, "S() over Default_Dc_Sign_Cdf yields 0 or 1");
dc_sign
} else {
decoder.read_literal(1)?
}
} else {
0
};
// §5.11.39 lines 84-93: golomb chain when magnitude exceeds
// NUM_BASE_LEVELS + COEFF_BASE_RANGE = 2 + 12 = 14.
let threshold = (NUM_BASE_LEVELS + COEFF_BASE_RANGE) as i32;
if qpos > threshold {
// golomb_length_bit L(1) loop: `length` = position of
// the first 1-bit (1-indexed). The first iteration is
// always entered (do-while), so length >= 1.
//
// Robustness cap: a corrupt / truncated payload leaves
// the §8.2.2 arithmetic decoder emitting zero bits
// forever, so an uncapped do-while never terminates
// (and `length` / the `x << 1` chain below overflow).
// 30 bits keeps every conformant magnitude reachable
// (`x < 1 << 30` still exceeds anything §7.12.2's
// dequant clamp can pass through) while bounding the
// walk.
let mut length: u32 = 0;
loop {
length += 1;
if length > 30 {
return Err(crate::Error::GolombLengthOverflow);
}
let bit = decoder.read_literal(1)?;
if bit != 0 {
break;
}
}
// golomb_data_bit L(1) loop: `x = 1`, then `x = (x <<
// 1) | bit` for `i = length-2..=0`.
let mut x: u32 = 1;
let mut i: i32 = length as i32 - 2;
while i >= 0 {
let bit = decoder.read_literal(1)?;
x = (x << 1) | bit;
i -= 1;
}
// §5.11.39 line 93: `Quant[ pos ] = x + COEFF_BASE_RANGE
// + NUM_BASE_LEVELS`.
quant[pos] = (x as i32) + (COEFF_BASE_RANGE as i32) + (NUM_BASE_LEVELS as i32);
}
// §5.11.39 lines 94-96: `dcCategory` derive on pos == 0.
// The spec uses the post-golomb `Quant[pos]` for the `> 0`
// gate; that's also the spec's `Quant[pos] != 0` value
// since the upstream forward-loop entered the sign branch.
if pos == 0 && quant[pos] > 0 {
dc_category = if sign != 0 { 1 } else { 2 };
}
// §5.11.39 line 97: `Quant[ pos ] = Quant[ pos ] & 0xFFFFF`.
// The mask is the §5.11.39 20-bit-coefficient clip; values
// never exceed it on a conformant stream (the max is
// NUM_BASE_LEVELS + COEFF_BASE_RANGE + max_golomb_x ≪ 2^20),
// but the mask is applied verbatim for spec parity.
quant[pos] &= 0xFFFFF;
// §5.11.39 line 98: `culLevel += Quant[ pos ]`.
cul_level = cul_level.saturating_add(quant[pos] as u32);
// §5.11.39 lines 99-100: sign apply.
if sign != 0 {
quant[pos] = -quant[pos];
}
}
// §5.11.39 line 102: `culLevel = Min( 63, culLevel )`.
let cul_level: u8 = core::cmp::min(63, cul_level) as u8;
Ok(CoefficientsReadout {
all_zero: false,
eob,
cul_level,
dc_category,
})
}
/// §5.11.5 `palette_tokens( )` (§5.11.49) — the per-plane
/// `color_index_map_{y,uv}` literal + anti-diagonal colour-index
/// walk, shared by the §5.11.7 intra-frame arm and the §5.11.22
/// intra-in-inter arm (the §5.11.5 body is arm-agnostic here).
/// No-op (returns `(None, None)`) when both palette sizes are 0.
#[allow(clippy::too_many_arguments)]
fn read_palette_token_maps(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
palette_size_y: u8,
palette_size_uv: u8,
subsampling_x: u8,
subsampling_y: u8,
) -> Result<(Option<DecodedPaletteMap>, Option<DecodedPaletteMap>), crate::Error> {
// §5.11.5 line `palette_tokens( )` — §5.11.49. The outer guard
// is `if ( PaletteSizeY ) ... if ( PaletteSizeUV ) ...` (zero
// on every non-palette block, so the call collapses to a no-op
// there). On a palette block each plane reads its
// `color_index_map_{y,uv}` `NS(PaletteSize)` literal followed
// by the §5.11.49 anti-diagonal `palette_color_idx_{y,uv}` S()
// walk via [`palette_tokens_plane`]. Shared by the §5.11.7
// intra-frame arm and the §5.11.22 intra-in-inter arm (the
// §5.11.5 body is arm-agnostic here).
let mut color_map_y: Option<DecodedPaletteMap> = None;
let mut color_map_uv: Option<DecodedPaletteMap> = None;
if palette_size_y > 0 {
let args = palette_tokens_args(
sub_size,
mi_row as usize,
mi_col as usize,
self.mi_rows as usize,
self.mi_cols as usize,
PalettePlane::Y,
0,
0,
)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// §5.11.49: `color_index_map_y NS(PaletteSizeY)`.
let color_index_map = decoder.read_ns(palette_size_y as u32)? as u8;
let mut color_map = vec![0u8; args.block_h * args.block_w];
palette_tokens_plane(
decoder,
cdfs,
PalettePlane::Y,
palette_size_y as usize,
args.block_w,
args.block_h,
args.onscreen_w,
args.onscreen_h,
color_index_map,
&mut color_map,
args.block_w,
)?;
color_map_y = Some(DecodedPaletteMap {
data: color_map,
stride: args.block_w,
block_w: args.block_w,
block_h: args.block_h,
onscreen_w: args.onscreen_w,
onscreen_h: args.onscreen_h,
});
}
if palette_size_uv > 0 {
let args = palette_tokens_args(
sub_size,
mi_row as usize,
mi_col as usize,
self.mi_rows as usize,
self.mi_cols as usize,
PalettePlane::Uv,
subsampling_x as usize,
subsampling_y as usize,
)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// §5.11.49: `color_index_map_uv NS(PaletteSizeUV)`.
let color_index_map = decoder.read_ns(palette_size_uv as u32)? as u8;
let mut color_map = vec![0u8; args.block_h * args.block_w];
palette_tokens_plane(
decoder,
cdfs,
PalettePlane::Uv,
palette_size_uv as usize,
args.block_w,
args.block_h,
args.onscreen_w,
args.onscreen_h,
color_index_map,
&mut color_map,
args.block_w,
)?;
color_map_uv = Some(DecodedPaletteMap {
data: color_map,
stride: args.block_w,
block_w: args.block_w,
block_h: args.block_h,
onscreen_w: args.onscreen_w,
onscreen_h: args.onscreen_h,
});
}
Ok((color_map_y, color_map_uv))
}
/// `decode_block( r, c, subSize )` per §5.11.5 (av1-spec p.63-64)
/// — the per-block syntax-walker entry. This is the
/// long-skeleton-with-stubs sibling of the leaf-only
/// [`Self::decode_block`] used internally by
/// [`Self::decode_partition`].
///
/// The spec body (av1-spec p.63-64) reads:
///
/// ```text
/// decode_block( r, c, subSize ) {
/// MiRow = r
/// MiCol = c
/// MiSize = subSize
/// bw4 = Num_4x4_Blocks_Wide[ subSize ]
/// bh4 = Num_4x4_Blocks_High[ subSize ]
/// if ( bh4 == 1 && subsampling_y && (MiRow & 1) == 0 )
/// HasChroma = 0
/// else if ( bw4 == 1 && subsampling_x && (MiCol & 1) == 0 )
/// HasChroma = 0
/// else
/// HasChroma = NumPlanes > 1
/// AvailU = is_inside( r - 1, c )
/// AvailL = is_inside( r, c - 1 )
/// AvailUChroma = AvailU
/// AvailLChroma = AvailL
/// if ( HasChroma ) {
/// if ( subsampling_y && bh4 == 1 )
/// AvailUChroma = is_inside( r - 2, c )
/// if ( subsampling_x && bw4 == 1 )
/// AvailLChroma = is_inside( r, c - 2 )
/// } else {
/// AvailUChroma = 0
/// AvailLChroma = 0
/// }
/// mode_info( )
/// palette_tokens( )
/// read_block_tx_size( )
/// if ( skip )
/// reset_block_context( bw4, bh4 )
/// isCompound = RefFrame[ 1 ] > INTRA_FRAME
/// … per-block grid fills for YModes / UVModes / RefFrames /
/// (inter) CompGroupIdxs / CompoundIdxs / InterpFilters / Mvs …
/// compute_prediction( )
/// residual( )
/// … per-block grid fills for IsInters / SkipModes / Skips /
/// TxSizes / MiSizes / SegmentIds / PaletteSizes /
/// PaletteColors / DeltaLFs …
/// }
/// ```
///
/// Implemented in this round:
///
/// * §5.11.5 **prologue** — `MiRow` / `MiCol` / `MiSize` are
/// caller-passed; `bw4` / `bh4` derived from
/// [`NUM_4X4_BLOCKS_WIDE`] / [`NUM_4X4_BLOCKS_HIGH`]; `HasChroma`
/// derived from the spec's three-arm dispatch on
/// `subsampling_{x,y}` / `bw4` / `bh4` / `NumPlanes`; `AvailU` /
/// `AvailL` from [`TileGeometry::is_inside`]; `AvailUChroma` /
/// `AvailLChroma` from the chroma fix-up arm (`subsampling_y &&
/// bh4 == 1` ⇒ `is_inside( r - 2, c )`, etc.).
/// * §5.11.6 **`mode_info()`** — `FrameIsIntra == 1` (key /
/// intra-only frames) routes through the FULL §5.11.7
/// composition (r281):
/// [`Self::decode_intra_frame_mode_info_prefix`] (§5.11.7
/// lines 1-10) + [`Self::decode_use_intrabc`] (the §5.11.7
/// `use_intrabc` element), then on the `use_intrabc == 1` arm
/// the fixed no-bit assignments + §7.10.2
/// [`Self::find_mv_stack`]`( 0 )` + the §5.11.26
/// `assign_mv( 0 )` intra-block-copy body (`compMode = NEWMV`
/// forced, `PredMv[ 0 ]` via the three-stage fallback chain,
/// `read_mv( 0 )` under `MvCtx = MV_INTRABC_CONTEXT` /
/// `force_integer_mv = 1`), and on the `else` arm the r280
/// [`Self::decode_intra_frame_mode_info_else_arm`] composite
/// (`intra_frame_y_mode`, `intra_angle_info_y`, the
/// `HasChroma` arm, §5.11.46 `palette_mode_info` with entries,
/// and §5.11.24 `filter_intra_mode_info`). The §8.3.2
/// `has_palette_y` neighbour ctx is derived from the walker's
/// own `PaletteSizes[ 0 ]` grid. The §5.11.18
/// `inter_frame_mode_info()` inter arm runs through the r190
/// inter-arm helper when `inter_ctx` is supplied and returns
/// [`crate::Error::DecodeBlockInterFrameUnsupported`] otherwise.
/// * §5.11.5 **`palette_tokens()`** — gated by the §5.11.49
/// `if ( PaletteSize{Y,UV} )` guard. On a palette block each
/// plane's `color_index_map_{y,uv}` `NS(PaletteSize)` literal +
/// anti-diagonal `palette_color_idx_{y,uv}` S() walk run via
/// [`palette_tokens_plane`] (r281); the decoded
/// `ColorMap{Y,UV}` contents are consumed for arithmetic-coder
/// sync (per-sample map tracking on the walker is a follow-up).
/// * §5.11.16 **`read_block_tx_size()`** — landed in r167 via
/// [`Self::read_block_tx_size`]. On the implemented intra arm
/// (`is_inter == 0`, which forces the §5.11.16 `else` arm
/// `read_tx_size(!skip || !is_inter)`) the walker invokes
/// `read_tx_size` per §5.11.15, reads `tx_depth` against the
/// `Tx{8x8,16x16,32x32,64x64}Cdf` row selected by §8.3.2 (when
/// `MiSize > BLOCK_4X4 && TxMode == TX_MODE_SELECT`), and
/// stamps the resulting `TxSize` into both `TxSizes[]` and
/// `InterTxSizes[]` over the block's `bh4 * bw4` footprint. The
/// §5.11.16 inter `TX_MODE_SELECT && !skip && !Lossless` arm
/// (the §5.11.17 `read_var_tx_size` recursion) is a follow-up
/// target (the `is_inter == 1` paths — intrabc + inter-frame —
/// use the §5.11.15 `else` arm meanwhile).
/// * §5.11.5 grid-fill **for the writes the walker computed** —
/// `YModes[][]`, `RefFrames[][][0..2]`, `IsInters[][]`,
/// `SkipModes[][]`, `Skips[][]`, `MiSizes[][]`, `SegmentIds[][]`,
/// `TxSizes[][]`, `InterTxSizes[][]`
/// already happen inside the composed leaves (the §5.11.5
/// per-element grid-fill loops are factored into each leaf's
/// own write). The walker additionally records the §5.11.5
/// `IsCompound = RefFrame[1] > INTRA_FRAME` derivation (always
/// `false` on the intra arm because `RefFrame[1] = NONE`).
///
/// Remaining follow-up targets on this path:
///
/// * §5.11.18 [`crate::Error::DecodeBlockInterFrameUnsupported`]
/// — `inter_frame_mode_info()` for `FrameIsIntra == 0` when the
/// caller passes `inter_ctx = None` (legacy intra-only callers).
/// * §5.11.17 `read_var_tx_size` — the §5.11.16 inter var-tx
/// recursion (the `is_inter == 1` paths use the §5.11.15
/// `else` arm meanwhile).
/// * Per-sample `ColorMapY` / `ColorMapUV` + `UVModes[][]` walker
/// grids (the §5.11.49 reads run; the decoded maps are not yet
/// retained for §7.11.4 palette prediction).
///
/// ## Caller contract
///
/// The §5.11.5 grid stamps that happen in the composed leaves
/// (Skips / SkipModes / SegmentIds / MiSizes / YModes / IsInters /
/// cdef_idx / TxSizes / InterTxSizes / PaletteSizes /
/// PaletteColors, plus RefFrames / Mvs / InterpFilters on the
/// `is_inter == 1` arms) are observable on the walker's grid
/// accessors after a successful call. The §5.11.5 stamps not yet
/// covered (`UVModes` / intra-arm `RefFrames` / `DeltaLFs`) need
/// their own walker fields and land in the round that implements
/// their consumers.
///
/// On the stub paths the walker still emits a
/// [`DecodedBlockRecord`] leaf (the same one
/// [`Self::decode_partition`] would emit) so callers can observe
/// the §5.11.5 entry through [`Self::blocks`] regardless of the
/// stub fire site.
///
/// Returns the §5.11.5 per-block derived state ([`DecodedBlock`])
/// on the no-stub path. Returns a §5.11.5 stub variant of
/// [`crate::Error`] when the walker reaches a follow-up target,
/// or [`crate::Error::PartitionWalkOutOfRange`] for caller bugs
/// (out-of-range `sub_size`, `mi_row` / `mi_col` outside the
/// frame's mi extent, invalid `bit_depth`).
#[allow(clippy::too_many_arguments)]
pub fn decode_block_syntax(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
// §5.11.6 dispatch — `FrameIsIntra` (§5.9.4 derivation):
// true ⇔ frame_type ∈ {KEY_FRAME, INTRA_ONLY_FRAME}.
frame_is_intra: bool,
// §5.11.5 prologue — sequence-header chroma derivation
// (§5.5.2): `subsampling_x` / `subsampling_y` are 0/1, and
// `NumPlanes = mono_chrome ? 1 : 3`.
subsampling_x: u8,
subsampling_y: u8,
num_planes: u8,
// §5.11.7 / §5.11.8 dispatcher gates — caller threads these
// through to `decode_intra_frame_mode_info_prefix`. Field
// semantics match the §5.11.7 dispatcher (av1-spec p.65).
seg_id_pre_skip: bool,
segmentation_enabled: bool,
seg_skip: &[bool; MAX_SEGMENTS],
last_active_seg_id: u8,
lossless_array: &[bool; MAX_SEGMENTS],
coded_lossless: bool,
enable_cdef: bool,
allow_intrabc: bool,
cdef_bits: u32,
read_deltas: bool,
use_128x128_superblock: bool,
delta_q_res: u8,
delta_lf_present: bool,
delta_lf_multi: bool,
mono_chrome: bool,
delta_lf_res: u8,
// §5.9.5 frame-header `allow_screen_content_tools` — the
// §5.11.7 / §5.11.22 `palette_mode_info( )` outer gate.
allow_screen_content_tools: bool,
// §5.5.2 sequence-header `enable_filter_intra` — the §5.11.24
// `filter_intra_mode_info( )` outer gate.
enable_filter_intra: bool,
// §5.5.2 `color_config( )` `BitDepth` (8 / 10 / 12) — the
// §5.11.46 palette-entry `L(BitDepth)` literals + `minBits`
// derivations. Callers without palette-eligible content still
// pass a valid value (8 is the conventional identity).
bit_depth: u8,
// §5.9.21 / §6.8.21 `TxMode == TX_MODE_SELECT`. Threaded
// through to `read_block_tx_size` per §5.11.16. Source: the
// §5.9.21 frame-header `TxMode` derivation
// (`read_tx_mode`); `false` corresponds to `TX_MODE_LARGEST`
// / `ONLY_4X4`.
tx_mode_select: bool,
// r190: optional inter-arm context bundle. `None` ⇒ the
// walker keeps the pre-r190 short-circuit on the
// `frame_is_intra == false` branch
// (`Err(crate::Error::DecodeBlockInterFrameUnsupported)`),
// matching legacy intra-only callers. `Some(&ctx)` ⇒ the
// §5.11.18 `inter_frame_mode_info()` dispatcher runs to
// completion, the §5.11.23 inter-block aggregate is plumbed
// through to [`Self::compute_prediction`] +
// [`Self::residual`], and the walker advances past the
// historical r189 stub site.
inter_ctx: Option<&InterFrameContext>,
// r286: §5.9.12 per-frame quantiser state + §5.9.21
// `reduced_tx_set`. Threaded into the §5.11.34 `residual()`
// dispatch's [`ResidualContext`] so the §5.11.47
// `transform_type()` reader's `(segmentation_enabled ?
// get_qindex(1, segment_id) : base_q_idx) > 0` guard and its
// §5.11.48 `get_tx_set()` reduction match whatever quantiser
// the bitstream was written under. With the `base_q_idx == 0`
// neutral default the guard is always false and the §5.11.47
// path stays bit-silent (`TxType == DCT_DCT`) — the historical
// behaviour. The encoder mirror's
// [`crate::encoder::partition_tree`] write side reads the same
// `quant` / `reduced_tx_set` so the per-luma-TU `intra_tx_type`
// / `inter_tx_type` S() agrees bit-for-bit.
quant: &QuantizerParams,
reduced_tx_set: bool,
) -> Result<DecodedBlock, crate::Error> {
// §5.11.5 prologue — range guards (caller-bug detection).
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.5.2 `color_config( )` constrains `BitDepth` to 8 / 10 /
// 12. Guard at the dispatcher level (mirroring the §5.11.22 /
// §5.11.7-else composites) so the bit stream stays untouched
// on a caller bug regardless of which arm fires.
if !matches!(bit_depth, 8 | 10 | 12) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// Reset the per-block §5.11.35 palette-prediction state — a
// previous palette block's sizes / maps must never leak into
// this block's `transform_block` dispatch.
self.current_palette_size_y = 0;
self.current_palette_size_uv = 0;
self.current_color_map_y = None;
self.current_color_map_uv = None;
// §5.11.5 lines 1-3: `MiRow = r`, `MiCol = c`, `MiSize =
// subSize`. We surface these as fields of the returned
// `DecodedBlock` rather than mutating any shared state — the
// spec's `MiRow` / `MiCol` / `MiSize` are per-block "current
// block coordinates" passed implicitly to every sub-routine,
// and our composed leaves take them as explicit arguments.
let mi_row_b = mi_row;
let mi_col_b = mi_col;
let mi_size = sub_size;
// §5.11.5 lines 4-5: `bw4 = Num_4x4_Blocks_Wide[ subSize ]`,
// `bh4 = Num_4x4_Blocks_High[ subSize ]`. Bounded by
// [`NUM_4X4_BLOCKS_WIDE`] / [`NUM_4X4_BLOCKS_HIGH`]'s spec
// values (1..=32).
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
// §5.11.5 lines 6-13: `HasChroma` three-arm dispatch (spec
// text):
// if ( bh4 == 1 && subsampling_y && (MiRow & 1) == 0 )
// HasChroma = 0
// else if ( bw4 == 1 && subsampling_x && (MiCol & 1) == 0 )
// HasChroma = 0
// else
// HasChroma = NumPlanes > 1
// The first two arms both yield `false`; we collapse them
// into a single `if` with an `||` to satisfy clippy's
// `if_same_then_else` lint while preserving the spec's
// semantics one-to-one. The collapsed condition reads as
// "either chroma-edge-case fires".
let chroma_y_edge_case = bh4 == 1 && subsampling_y != 0 && (mi_row_b & 1) == 0;
let chroma_x_edge_case = bw4 == 1 && subsampling_x != 0 && (mi_col_b & 1) == 0;
let has_chroma = if chroma_y_edge_case || chroma_x_edge_case {
false
} else {
num_planes > 1
};
// §5.11.5 lines 14-17: `AvailU` / `AvailL` / `AvailUChroma` /
// `AvailLChroma` derivation.
let avail_u = self
.geometry
.is_inside(mi_row_b as i32 - 1, mi_col_b as i32);
let avail_l = self
.geometry
.is_inside(mi_row_b as i32, mi_col_b as i32 - 1);
let (avail_u_chroma, avail_l_chroma) = if has_chroma {
// §5.11.5 lines 18-21: chroma fix-ups for the
// `subsampling_y && bh4 == 1` (chroma above-neighbour is
// two rows up) and `subsampling_x && bw4 == 1` (chroma
// left-neighbour is two columns left) edge cases.
let auc = if subsampling_y != 0 && bh4 == 1 {
self.geometry
.is_inside(mi_row_b as i32 - 2, mi_col_b as i32)
} else {
avail_u
};
let alc = if subsampling_x != 0 && bw4 == 1 {
self.geometry
.is_inside(mi_row_b as i32, mi_col_b as i32 - 2)
} else {
avail_l
};
(auc, alc)
} else {
// §5.11.5 lines 22-25: `AvailUChroma = 0`,
// `AvailLChroma = 0` for the no-chroma block (sub-8 luma
// edge cases).
(false, false)
};
// §5.11.5 line `mode_info( )` — §5.11.6 dispatch. The
// implemented intra-frame arm composes the §5.11.7
// `intra_frame_mode_info` body through the
// `decode_intra_frame_mode_info_prefix` dispatcher (lines 1-10:
// `skip = 0`, `intra_segment_id`, `skip_mode = 0`, `read_skip`,
// post-skip `intra_segment_id`, `read_cdef`, `read_delta_qindex`,
// `read_delta_lf`, `ReadDeltas = 0`, `RefFrame[0] = INTRA_FRAME`,
// `RefFrame[1] = NONE`); then the `use_intrabc` element; then
// — on the implemented `use_intrabc == 0` / `allow_intrabc ==
// false` arm — the `intra_frame_y_mode` element. The §5.11.7
// follow-on body (`intra_angle_info_y`, `uv_mode`,
// `intra_angle_info_uv`, `palette_mode_info`,
// `filter_intra_mode_info`) and the `use_intrabc == 1` body
// are the next round's targets.
//
// The inter-frame arm composes §5.11.18 through
// [`Self::decode_inter_frame_mode_info`]; both terminal arms
// (§5.11.22 / §5.11.23) are next-round targets and surface as
// [`crate::Error::IntraBlockModeInfoUnsupported`] /
// [`crate::Error::InterBlockModeInfoUnsupported`]. The
// §5.11.18 walker doesn't yet receive the full
// segmentation-feature / skip-mode-present state from the
// §5.11.5 driver, so the inter arm here surfaces the
// [`crate::Error::DecodeBlockInterFrameUnsupported`] umbrella
// stub — direct callers of `decode_inter_frame_mode_info` get
// the §5.11.22 / §5.11.23 distinction.
if !frame_is_intra {
// r190 wire-up — route through the §5.11.18
// [`Self::decode_inter_frame_mode_info`] dispatcher when the
// caller supplies an [`InterFrameContext`]. The cascade
// (§5.11.18 prologue + §5.11.20 `read_is_inter` + §5.11.23
// inter-block body if `is_inter == 1`, else §5.11.22 stub)
// runs to completion; the §5.11.5 walker then threads the
// resulting MV / ref-frame / interp-filter state into
// [`Self::compute_prediction`] (the §5.11.33 inter arm
// already emits per-4x4 inter tasks per r189) and
// [`Self::residual`] (the §5.11.34 `is_inter && !Lossless
// && !plane` `transform_tree` arm). Callers passing `None`
// (the historical signature) keep the pre-r190 stub.
let ctx = match inter_ctx {
Some(c) => c,
None => return Err(crate::Error::DecodeBlockInterFrameUnsupported),
};
return self.decode_block_syntax_inter_arm(
decoder,
cdfs,
mi_row_b,
mi_col_b,
sub_size,
bw4,
bh4,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
subsampling_x,
subsampling_y,
seg_id_pre_skip,
segmentation_enabled,
seg_skip,
last_active_seg_id,
lossless_array,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
tx_mode_select,
allow_screen_content_tools,
enable_filter_intra,
bit_depth,
quant,
reduced_tx_set,
ctx,
);
}
let prefix = self.decode_intra_frame_mode_info_prefix(
decoder,
cdfs,
mi_row_b,
mi_col_b,
sub_size,
seg_id_pre_skip,
segmentation_enabled,
seg_skip,
last_active_seg_id,
lossless_array,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
)?;
let use_intrabc =
self.decode_use_intrabc(decoder, cdfs, mi_row_b, mi_col_b, sub_size, allow_intrabc)?;
// §5.11.7 post-`use_intrabc` dispatch — r281 threads BOTH
// arms to completion. Per-block mode-info state surfaced via
// the returned [`DecodedBlock`].
let y_mode: u8;
let is_inter: u8;
let uv_mode: Option<u8>;
let mut angle_delta_y: i8 = 0;
let mut angle_delta_uv: Option<i8> = None;
let mut cfl_alpha_u: Option<i8> = None;
let mut cfl_alpha_v: Option<i8> = None;
let mut palette_size_y: u8 = 0;
let mut palette_size_uv: u8 = 0;
let mut use_filter_intra: Option<u8> = None;
let mut filter_intra_mode: Option<u8> = None;
let mut mv: [[i32; 2]; 2] = [[0, 0], [0, 0]];
if use_intrabc != 0 {
// §5.11.7 `use_intrabc == 1` body (av1-spec p.65) — the
// fixed no-bit assignments (`is_inter = 1`, `YMode =
// UVMode = DC_PRED`, `motion_mode = SIMPLE`,
// `compound_type = COMPOUND_AVERAGE`, `PaletteSize{Y,UV} =
// 0`, `interp_filter[ 0..2 ] = BILINEAR`) followed by
// `find_mv_stack( 0 )` + `assign_mv( 0 )`.
y_mode = 0; // DC_PRED
is_inter = 1;
uv_mode = Some(0); // DC_PRED — §5.11.7 fixed assignment.
// §7.10.2 `find_mv_stack( 0 )` with `RefFrame =
// [ INTRA_FRAME, NONE ]`. `GlobalMvs[ 0 ]` is the zero
// vector on intra-block-copy blocks (§7.10.2.1: `ref ==
// INTRA_FRAME ⇒ mv = 0`), so identity global-motion
// tables are supplied; the §7.10.2.5 temporal scan is
// gated off (`use_ref_frame_mvs = 0` — temporal MV
// projection is an inter-frame feature, and §5.9.20
// `allow_intrabc` is reachable only on intra frames), so
// an all-invalid `MotionFieldMvs` grid is observably
// equivalent to a projected one.
let mut gm_params = [[0i32; 6]; 8];
for row in gm_params.iter_mut() {
row[2] = 1 << WARPEDMODEL_PREC_BITS;
row[5] = 1 << WARPEDMODEL_PREC_BITS;
}
let mfmv = MotionFieldMvs::new_invalid(self.mi_rows, self.mi_cols);
let stack = self.find_mv_stack(
mi_row_b,
mi_col_b,
sub_size,
/* ref_frame = */ [0, -1],
/* is_compound = */ false,
/* use_ref_frame_mvs = */ false,
[GM_TYPE_IDENTITY; 8],
gm_params,
[0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ true,
&mfmv,
)?;
// §5.11.26 `assign_mv( 0 )` intra-block-copy arm:
// `compMode` is forced to `NEWMV`, so `read_mv( 0 )`
// always fires. `PredMv[ 0 ]` comes from the three-stage
// fallback chain (`RefStackMv[ 0 ][ 0 ]` →
// `RefStackMv[ 1 ][ 0 ]` → the superblock-offset
// synthetic predictor); the §5.11.31 read runs under
// `MvCtx = MV_INTRABC_CONTEXT` with `force_integer_mv =
// 1` (§5.9.2 forces it on every intra frame) and no high
// precision.
let pred_mv = crate::encoder::block_mode_info::assign_mv_pred_mv_intrabc(
&stack,
use_128x128_superblock,
mi_row_b,
self.geometry.mi_row_start,
)?;
mv[0] = self.read_mv(
decoder,
cdfs,
mv_ctx(true),
pred_mv,
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ true,
)?;
// §5.11.5 grid-fill for the intra-block-copy block:
// `YModes = DC_PRED`, `RefFrames = [ INTRA_FRAME, NONE ]`,
// `IsInters = 1`, `InterpFilters[ 0..2 ] = BILINEAR`,
// `Mvs[ 0 ] = Mv[ 0 ]` over the `bh4 * bw4` footprint
// (the §5.11.5 `CompGroupIdxs` / `CompoundIdxs` stamps
// are explicitly skipped on `use_intrabc` blocks per the
// §5.11.5 `if ( !use_intrabc )` gate). Subsequent
// intra-block-copy blocks' §7.10.2 neighbour scans read
// these cells.
for dr in 0..bh4 {
let rr = mi_row_b + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col_b + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr * self.mi_cols + cc) as usize;
self.y_modes[cell] = 0; // DC_PRED
self.is_inters[cell] = 1;
self.ref_frames[cell * 2] = 0; // INTRA_FRAME
self.ref_frames[cell * 2 + 1] = -1; // NONE
self.interp_filters[cell * 2] = crate::inter_pred::BILINEAR;
self.interp_filters[cell * 2 + 1] = crate::inter_pred::BILINEAR;
// §6.10.27 conformance bound `Abs( Mv ) < (1 <<
// 14)` keeps the i32 → i16 cast lossless.
self.mvs[(cell * 2) * 2] = mv[0][0] as i16;
self.mvs[(cell * 2) * 2 + 1] = mv[0][1] as i16;
self.mvs[(cell * 2 + 1) * 2] = 0;
self.mvs[(cell * 2 + 1) * 2 + 1] = 0;
}
}
} else {
// §5.11.7 `else` arm — the full r280 composition:
// `intra_frame_y_mode` + `intra_angle_info_y` + the
// `HasChroma` arm (`uv_mode` / §5.11.45 `read_cfl_alphas`
// / `intra_angle_info_uv`) + §5.11.46 `palette_mode_info`
// + §5.11.24 `filter_intra_mode_info`. The `is_inter = 0`
// assignment is the arm's first no-bit line.
//
// §8.3.2 `has_palette_y` neighbour ctx (av1-spec p.378):
// ctx = ( AvailU && PaletteSizes[ 0 ][ MiRow - 1 ][
// MiCol ] > 0 ) + ( AvailL && PaletteSizes[ 0 ][ MiRow
// ][ MiCol - 1 ] > 0 )
// derived from the walker's own `PaletteSizes[ 0 ]` grid
// (stamped by previous blocks' §5.11.46 reads).
let above_palette_y = avail_u
&& mi_row_b > 0
&& self.palette_sizes[((mi_row_b - 1) * self.mi_cols + mi_col_b) as usize] > 0;
let left_palette_y = avail_l
&& mi_col_b > 0
&& self.palette_sizes[(mi_row_b * self.mi_cols + mi_col_b - 1) as usize] > 0;
let info = self.decode_intra_frame_mode_info_else_arm(
decoder,
cdfs,
mi_row_b,
mi_col_b,
sub_size,
prefix.lossless,
has_chroma,
allow_screen_content_tools,
enable_filter_intra,
subsampling_x != 0,
subsampling_y != 0,
above_palette_y,
left_palette_y,
bit_depth,
)?;
y_mode = info.y_mode;
is_inter = 0;
uv_mode = info.uv_mode;
angle_delta_y = info.angle_delta_y;
angle_delta_uv = info.angle_delta_uv;
cfl_alpha_u = info.cfl_alpha_u;
cfl_alpha_v = info.cfl_alpha_v;
palette_size_y = info.palette_size_y.unwrap_or(0);
palette_size_uv = info.palette_size_uv.unwrap_or(0);
use_filter_intra = info.use_filter_intra;
filter_intra_mode = info.filter_intra_mode;
}
// §5.11.5 line `palette_tokens( )` — §5.11.49, via the shared
// [`Self::read_palette_token_maps`] helper (r325 surfaced the
// decoded `ColorMap{Y,UV}` on the returned [`DecodedBlock`];
// r387 factored the reader for the §5.11.22 intra-in-inter
// arm).
let (color_map_y, color_map_uv) = self.read_palette_token_maps(
decoder,
cdfs,
mi_row_b,
mi_col_b,
sub_size,
palette_size_y,
palette_size_uv,
subsampling_x,
subsampling_y,
)?;
// Hand the palette sizes + decoded maps to the §5.11.34
// `residual()` dispatch (below) so its per-TU §5.11.35
// `transform_block` runs the `predict_palette` arm on this
// block's palette planes. The maps are cloned — the originals
// ride the returned [`DecodedBlock`] unchanged.
self.current_palette_size_y = palette_size_y;
self.current_palette_size_uv = palette_size_uv;
self.current_color_map_y = color_map_y.clone();
self.current_color_map_uv = color_map_uv.clone();
// §5.11.5 line `read_block_tx_size( )` — §5.11.16 reader. The
// walker has completed the §5.11.5 prologue + §5.11.6
// `mode_info()` (intra arm) + §5.11.49 `palette_tokens()`
// (no-op) and emits a [`DecodedBlockRecord`] so callers can
// observe the §5.11.5 entry through [`Self::blocks`]. Then it
// performs the §5.11.16 read against the running grid state
// and stamps `TxSizes[]` / `InterTxSizes[]` across the block
// footprint via [`Self::read_block_tx_size`].
//
// On the implemented intra arm `is_inter == 0`, so §5.11.16's
// outer `TX_MODE_SELECT && is_inter` gate is false and the
// `else` arm always fires — `read_tx_size(!skip || true)` =
// `read_tx_size(true)`. The §5.11.15 body inside handles the
// `Lossless` short-circuit (forces `TX_4X4`) and the
// `MiSize > BLOCK_4X4 && allowSelect && TxMode == TX_MODE_SELECT`
// `tx_depth` read.
self.decode_block(mi_row_b, mi_col_b, sub_size);
let tx_size = self.read_block_tx_size(
decoder,
cdfs,
mi_row_b,
mi_col_b,
sub_size,
prefix.lossless,
/* is_inter = */ is_inter != 0,
/* skip = */ prefix.skip != 0,
tx_mode_select,
)?;
// §5.11.5 line `if ( skip ) reset_block_context( bw4, bh4 )`.
// `reset_block_context` per §5.11.42 (av1-spec p.92) zeroes
// the `Above{Level,Dc}Context` / `Left{Level,Dc}Context`
// §6.10.2 arrays over the block's footprint (r284 — wired
// through [`Self::reset_txb_block_context`]; the encoder's
// `write_block_syntax` skip arm performs the same reset on
// its mirror walker).
if prefix.skip != 0 {
self.reset_txb_block_context(
mi_row_b,
mi_col_b,
bw4,
bh4,
has_chroma,
subsampling_x,
subsampling_y,
);
}
// §5.11.5 line `isCompound = RefFrame[ 1 ] > INTRA_FRAME`.
// On the intra arm `RefFrame[ 1 ] = NONE = -1 < INTRA_FRAME =
// 0` so `isCompound = false`.
let is_compound = false;
// Per-block derived state surfaced through the returned
// `DecodedBlock`. The §5.11.30 `compute_prediction()` /
// §5.11.34 `residual()` calls follow this point in the spec;
// §5.11.30 is wired through [`Self::compute_prediction`]
// (r180); §5.11.34 is wired through [`Self::residual`] (r181).
let db = DecodedBlock {
mi_row: mi_row_b,
mi_col: mi_col_b,
mi_size,
bw4,
bh4,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
skip: prefix.skip,
skip_mode: prefix.skip_mode,
segment_id: prefix.segment_id,
lossless: prefix.lossless,
cdef_idx: prefix.cdef_idx,
current_q_index: prefix.current_q_index,
current_delta_lf: prefix.current_delta_lf,
ref_frame: prefix.ref_frame,
use_intrabc,
is_inter,
y_mode,
uv_mode,
angle_delta_y,
angle_delta_uv,
cfl_alpha_u,
cfl_alpha_v,
palette_size_y,
palette_size_uv,
use_filter_intra,
filter_intra_mode,
mv,
is_compound,
// §5.11.33 `IsInterIntra` is always false on the §5.11.7
// intra arm: `RefFrame[1] = NONE = -1 ≠ INTRA_FRAME`, so
// the §5.11.33 gate cannot fire regardless of `is_inter`.
is_inter_intra: false,
tx_size,
// §5.11.49 `ColorMap{Y,UV}` — populated above on a palette
// block (`PaletteSize{Y,UV} > 0`), `None` otherwise.
color_map_y,
color_map_uv,
};
// §5.11.5 line `compute_prediction( )` — §5.11.33 dispatcher.
// The §5.11.33 body walks `for plane = 0..1 + HasChroma * 2`,
// computes the per-plane residual size + neighbour-availability
// flags, and on the reachable path (intra-only block with
// `YMode == DC_PRED`, no interintra) emits one DC_PRED
// `predict_intra` task per plane. We invoke
// [`Self::compute_prediction`] with the running per-block state
// and discard the readout — the per-plane sample buffers the
// tasks operate against live in `CurrFrame[plane][y][x]`,
// which the §5.11.5 syntax walker does not yet track.
//
// On the `is_inter == 0` walker path the dispatcher returns
// `Ok(_)` (one DC_PRED intra task per visited plane), and the
// walker advances to the §5.11.34 `residual()` stub.
//
// On the `is_inter == 1` path (the `use_intrabc == 1` arm —
// §5.11.7 sets `is_inter = 1` there) the dispatcher takes the
// §5.11.33 inter arm: the §5.11.5 grid stamps above committed
// `RefFrames[ .. ][ 0 ] = INTRA_FRAME` over the footprint, so
// the §5.11.33 `someUseIntra` scan fires and the per-4x4
// tasks carry the intra-block-copy candidate anchoring.
// `IsInterIntra` stays false (`RefFrame[ 1 ] = NONE ≠
// INTRA_FRAME`).
//
// `uv_mode` carries the §5.11.7 / §5.11.22 decoded `UVMode`
// (or `DC_PRED` when the `HasChroma` arm did not run — the
// §5.11.33 chroma planes are not visited in that case).
let _readout = self.compute_prediction(
mi_row_b,
mi_col_b,
sub_size,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
subsampling_x,
subsampling_y,
is_inter != 0,
/* y_mode = */ y_mode,
/* uv_mode = */ uv_mode.unwrap_or(0),
/* ref_frame_1_is_intra = */ false,
// §5.11.28 `interintra_mode` — the §5.11.5 walker emits no
// inter-intra blocks (`ref_frame_1_is_intra == false`), so
// this is unread; pass `II_DC_PRED` as the inert default.
/* interintra_mode = */
II_DC_PRED,
)?;
// §5.11.33 `is_inter` arm, realised — on the §5.11.7 intra
// path the only `is_inter == 1` blocks are `use_intrabc == 1`
// blocks, whose §7.11.3 prediction reads the SAME frame's
// already-reconstructed samples (§7.11.3.4 `refIdx == -1 ⇒
// ref = CurrFrame`). Write it into `CurrFrame[ plane ]` here,
// between `compute_prediction( )` and `residual( )`, so the
// §7.12.3 step-3 merge lands on the real prediction and later
// blocks read reconstructed neighbours.
if use_intrabc != 0 {
self.predict_intrabc_leaf_into_curr_frame(
mi_row_b,
mi_col_b,
sub_size,
has_chroma,
subsampling_x,
subsampling_y,
bit_depth,
mv[0],
)?;
}
// §5.11.5 line `residual( )` — §5.11.34 dispatcher.
//
// As of r181 the §5.11.34 outer dispatch + §5.11.36
// `transform_tree` recursion + §5.11.35 `transform_block`
// per-TU emit are wired through [`Self::residual`]. The
// per-TU §5.11.39 [`Self::coefficients`] reader is invoked on
// the `!skip` arm; the §5.11.40 `transform_type` /
// §5.11.41 `compute_tx_type` / §7.5 `get_scan` /
// `AboveLevelContext` / `LeftLevelContext` / `AboveDcContext`
// / `LeftDcContext` / §7.6 inverse-transform / §7.7
// reconstruct / §7.13 dequant axes are split-off (see
// [`ResidualReadout`]'s docs for the per-bullet rationale).
//
// On the §5.11.5 walker's reachable path:
// * `is_inter = 0` (intra-only — the §5.11.7 reader's
// reachable cell forces `YMode = DC_PRED`).
// * `skip == 0` is the conformant block — the walker's CDF
// rigging in `decode_block_syntax_reaches_compute_
// prediction_stub_after_intra_mode_info` forces `skip =
// 0`, so the §5.11.34 dispatcher will visit the
// `coefficients()` reader per TU.
// * `skip == 1` short-circuits the per-TU bitstream read;
// the dispatcher returns the per-TU task enumeration
// only.
//
// The dispatcher surfaces
// [`crate::Error::ResidualReconstructUnsupported`] when the
// §5.11.39 reader reports `eob > 0` on the `!skip` path
// (the §7.6 inverse-transform pass). On the gate-closed
// (`all_zero == 1` for every TU) path the dispatcher returns
// [`ResidualReadout`] cleanly and the §5.11.5 walker advances
// past the §5.11.34 call site.
// r286 — `ResidualContext` now carries the caller-threaded
// §5.9.12 `quant` + §5.9.21 `reduced_tx_set` so the §5.11.47
// `transform_type` reader's `set > 0 && qIdx > 0` guard +
// §5.11.48 `get_tx_set()` reduction reflect the real frame
// quantiser (the encoder mirror's write side reads the same
// pair). With the `base_q_idx == 0` neutral default the guard
// is always false and the reader stays on its `DCT_DCT`
// short-circuit — the historical behaviour every prior fixture
// relied on. `intra_dir` uses the §8.3.2 [`intra_dir`]
// derivation (the §5.11.42 `use_filter_intra` →
// `Filter_Intra_Mode_To_Intra_Dir` remap) so the
// `intra_tx_type` CDF axis agrees with the write side.
// §7.12.2 `get_qindex( 0, segment_id )` reads `CurrentQIndex`
// — the §5.11.12 per-superblock running value, NOT the frame
// `base_q_idx` (they differ exactly when `delta_q_present ==
// 1`; the r405 deltaq streams shift every block's dequant).
let mut block_quant = *quant;
block_quant.current_q_index = prefix.current_q_index.clamp(0, 255) as u8;
let neutral_ctx = ResidualContext {
quant: block_quant,
reduced_tx_set,
intra_dir: intra_dir(
use_filter_intra == Some(1),
y_mode as usize,
filter_intra_mode.unwrap_or(0) as usize,
)
.unwrap_or(0),
segment_id: prefix.segment_id,
seg_qm_level: quant.seg_qm_level_for(prefix.segment_id),
uv_mode: uv_mode.unwrap_or(0),
// §7.11.2.1 prediction gate: the intra arm runs for a true
// intra block. `use_intrabc == 1` sets `is_inter = 1`
// (§5.11.7); the §7.11.3 IntraBC / motion-compensation path
// owns that block's prediction, so the §7.11.2.1 writer
// stays off it. A `use_filter_intra == 1` block IS still an
// intra block — its luma plane runs the §7.11.2.3 recursive
// arm (now wired below via `filter_intra_mode`), its chroma
// planes the usual UVMode dispatch — so it stays on the
// intra path.
is_intra: is_inter == 0,
y_mode,
angle_delta_y,
angle_delta_uv: angle_delta_uv.unwrap_or(0),
// §5.11.45 `read_cfl_alphas` outputs — `signX * (cfl_alpha_x
// + 1)` already folded by the reader. `None` (no CfL block)
// ⇒ `0`; the §7.11.5 process is only invoked on the `isCfl`
// arm so the inert default is never read otherwise.
cfl_alpha_u: cfl_alpha_u.unwrap_or(0),
cfl_alpha_v: cfl_alpha_v.unwrap_or(0),
// §5.11.24 `filter_intra_mode` — `Some(mode)` only when the
// block decoded `use_filter_intra == 1`; gates the §7.11.2.3
// luma recursive-intra arm.
filter_intra_mode: if use_filter_intra == Some(1) {
filter_intra_mode
} else {
None
},
avail_l,
avail_u,
avail_l_chroma,
avail_u_chroma,
enable_intra_edge_filter: self.enable_intra_edge_filter,
};
let _residual = self.residual(
decoder,
cdfs,
mi_row_b,
mi_col_b,
sub_size,
has_chroma,
subsampling_x,
subsampling_y,
is_inter != 0,
prefix.lossless,
prefix.skip != 0,
tx_size as usize,
use_128x128_superblock,
&neutral_ctx,
)?;
// §5.11.5 line `is_compound = RefFrame[ 1 ] > INTRA_FRAME` —
// already derived above. The §5.11.5 walker concludes the
// syntax pass; later sub-routines (§5.11.5 line
// `update_partition_context()` per §5.11.42, and the
// dispatcher-level §5.11.4 partition-tree continuation) live
// upstream of the per-block dispatcher.
//
// Suppress the per-block `is_compound` unused-variable note.
let _ = is_compound;
Ok(db)
}
/// r190 — internal helper: §5.11.6 inter-arm completion of the
/// [`Self::decode_block_syntax`] dispatch. Invoked when the caller
/// passes `frame_is_intra == false` AND an
/// [`InterFrameContext`]; the §5.11.5 prologue has already
/// committed to the `(bw4, bh4, has_chroma, avail_*)` derivations
/// the intra arm consumes, so we reuse them here.
///
/// Composes the §5.11.18 dispatcher
/// ([`Self::decode_inter_frame_mode_info`]) covering the pre-skip,
/// skip, post-skip segment-id cascade plus the §5.11.23
/// inter-block body when `is_inter == 1`; then §5.11.16
/// [`Self::read_block_tx_size`] (var-tx recursion is a next-arc
/// target, we use the §5.11.15 `else` arm for r190); then §5.11.33
/// [`Self::compute_prediction`] emitting one
/// [`PlanePredictionTask`] per (plane, 4x4 cell) with `mode ==
/// COMPUTE_PRED_MODE_INTER` per r189; then §5.11.34
/// [`Self::residual`] whose `is_inter && !Lossless && !plane`
/// transform_tree arm fires on luma (chroma uses the direct
/// iteration path).
///
/// Returns the per-block [`DecodedBlock`] aggregate with the
/// `is_inter == 1` arm's fields populated. Returns the inner
/// dispatcher's error variant otherwise — the
/// `IntraBlockModeInfoUnsupported` stub on the §5.11.22 (intra
/// arm of an inter-frame) path is still a next-arc target;
/// every other error variant matches the §5.11.18 / §5.11.23
/// reader contracts.
#[allow(clippy::too_many_arguments)]
fn decode_block_syntax_inter_arm(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
bw4: u32,
bh4: u32,
has_chroma: bool,
avail_u: bool,
avail_l: bool,
avail_u_chroma: bool,
avail_l_chroma: bool,
subsampling_x: u8,
subsampling_y: u8,
seg_id_pre_skip: bool,
segmentation_enabled: bool,
seg_skip: &[bool; MAX_SEGMENTS],
last_active_seg_id: u8,
lossless_array: &[bool; MAX_SEGMENTS],
coded_lossless: bool,
enable_cdef: bool,
allow_intrabc: bool,
cdef_bits: u32,
read_deltas: bool,
use_128x128_superblock: bool,
delta_q_res: u8,
delta_lf_present: bool,
delta_lf_multi: bool,
mono_chrome: bool,
delta_lf_res: u8,
tx_mode_select: bool,
// r387: §5.11.22 intra-in-inter argument set (mirrors the
// §5.11.7 arm) + the §5.9.12 quantiser pair for the §5.11.34
// `residual()` dispatch (pre-r387 the inter arm fed a neutral
// `QuantizerParams`, desynchronising the §5.11.47
// `transform_type()` guard + §7.12.3 dequant on any stream
// with `base_q_idx > 0`).
allow_screen_content_tools: bool,
enable_filter_intra: bool,
bit_depth: u8,
quant: &QuantizerParams,
reduced_tx_set: bool,
ctx: &InterFrameContext,
) -> Result<DecodedBlock, crate::Error> {
// §5.11.19 `predictedSegmentId = get_segment_id()` — the
// §5.11.21 min-scan over `PrevSegmentIds` at THIS block's
// footprint. `None` (no primary reference /
// `setup_past_independence()`) reads as the all-zero map.
let predicted_segment_id: u8 = if segmentation_enabled {
match ctx.prev_segment_ids {
Some(prev) => {
let v = get_segment_id(
prev,
self.mi_rows,
self.mi_cols,
self.mi_rows,
self.mi_cols,
mi_row,
mi_col,
sub_size,
)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// The `-1` unwritten-cell sentinel cannot appear in
// a §7.20-stored map (the store clamps); clamp
// defensively anyway.
v.max(0) as u8
}
None => 0,
}
} else {
0
};
let _ = seg_skip;
// §5.11.6 inter arm — full §5.11.18 cascade.
let info = self.decode_inter_frame_mode_info(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
seg_id_pre_skip,
segmentation_enabled,
ctx.segmentation_update_map,
ctx.segmentation_temporal_update,
predicted_segment_id,
last_active_seg_id,
lossless_array,
&ctx.seg_feature_active,
&ctx.seg_feature_data,
ctx.skip_mode_present,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
ctx.skip_mode_frame,
ctx.reference_select,
ctx.gm_type,
ctx.gm_params,
ctx.ref_frame_sign_bias,
ctx.allow_high_precision_mv,
ctx.force_integer_mv,
ctx.use_ref_frame_mvs,
ctx.is_motion_mode_switchable,
ctx.allow_warped_motion,
ctx.is_scaled_per_ref,
ctx.enable_interintra_compound,
ctx.enable_masked_compound,
ctx.enable_jnt_comp,
ctx.order_hints,
ctx.interpolation_filter,
ctx.enable_dual_filter,
ctx.motion_field_mvs,
)?;
// §5.11.18 lines 23-26 dispatch: `if ( is_inter )
// inter_block_mode_info() else intra_block_mode_info()`. The
// r387 §5.11.22 arm (intra block inside an inter frame) runs
// the same composite as the §5.11.7 else-arm from
// `intra_angle_info_y()` onward, with the `y_mode` S() against
// `TileYModeCdf[ Size_Group[ MiSize ] ]`.
if info.is_inter == 0 {
return self.decode_block_syntax_intra_in_inter_tail(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
bw4,
bh4,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
subsampling_x,
subsampling_y,
use_128x128_superblock,
allow_screen_content_tools,
enable_filter_intra,
bit_depth,
quant,
reduced_tx_set,
tx_mode_select,
&info,
);
}
let inter_block = info
.inter_block
.ok_or(crate::Error::InterBlockModeInfoUnsupported)?;
// §5.11.5 line `read_block_tx_size( )` — §5.11.16 reader. The
// walker emits a [`DecodedBlockRecord`] (mirrors the intra
// arm), then performs the §5.11.16 read.
self.decode_block(mi_row, mi_col, sub_size);
let tx_size = self.read_block_tx_size(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
info.lossless,
/* is_inter = */ true,
/* skip = */ info.skip != 0,
tx_mode_select,
)?;
// §5.11.5 line `if ( skip ) reset_block_context( bw4, bh4 )` —
// §5.11.42 zeroes the §6.10.2 `Above{Level,Dc}Context` /
// `Left{Level,Dc}Context` arrays over the footprint (r387: the
// inter arm previously skipped this reset, desynchronising the
// §8.3.2 `all_zero` / `dc_sign` ctx walks of subsequent leaves
// after any skip-coded inter block).
if info.skip != 0 {
self.reset_txb_block_context(
mi_row,
mi_col,
bw4,
bh4,
has_chroma,
subsampling_x,
subsampling_y,
);
}
// §5.11.5 `isCompound = RefFrame[1] > INTRA_FRAME` from the
// §5.11.23 aggregate (slot 1 in `LAST_FRAME = 1..=ALTREF_FRAME
// = 7` ⇒ true; `NONE = -1` or `INTRA_FRAME = 0` ⇒ false).
let is_compound = inter_block.is_compound;
// §5.11.33 `IsInterIntra = is_inter && RefFrame[1] ==
// INTRA_FRAME`. The §5.11.28 inner `if ( interintra )` arm
// imperatively sets `RefFrame[1] = INTRA_FRAME = 0`; we read
// back the post-grid-stamp slot-1 value to drive the
// `compute_prediction` `IsInterIntra` gate.
let ref_frame_1 = inter_block.ref_frame[1];
let ref_frame_1_is_intra = ref_frame_1 == 0;
// §5.11.33 chroma plane mode-info: the §5.11.28
// interintra path supplies a chroma intra mode (the
// §5.11.28 inner arm sets `UVMode = DC_PRED`); on the
// non-interintra path the §5.11.33 chroma arm is unused
// for an inter block (`is_inter == 1`) and the per-plane
// task carries `mode == COMPUTE_PRED_MODE_INTER`.
let uv_mode_for_compute = 0u8; // DC_PRED — the §5.11.28 inner arm's default
// §5.11.5 line `compute_prediction( )` — §5.11.33 dispatcher.
// r189 emits one inter [`PlanePredictionTask`] per
// (plane, 4x4-cell) on the `is_inter == 1` arm; on the
// §5.11.28 inter-intra arm (`ref_frame_1_is_intra == true`)
// it prepends one intra-half task per plane (r301).
let readout = self.compute_prediction(
mi_row,
mi_col,
sub_size,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
subsampling_x,
subsampling_y,
/* is_inter = */ true,
/* y_mode = */ inter_block.y_mode,
/* uv_mode = */ uv_mode_for_compute,
ref_frame_1_is_intra,
// §5.11.28 `interintra_mode` — present (`Some`) on the
// inter-intra arm (`ref_frame_1_is_intra == true`); the
// §5.11.33 dispatcher reads it only there. `None` (the
// non-interintra path) maps to the inert `II_DC_PRED`.
/* interintra_mode = */
inter_block.interintra.interintra_mode.unwrap_or(II_DC_PRED),
)?;
// §5.11.33 `IsInterIntra` end-to-end propagation. The walker
// tracks no `CurrFrame[plane]` sample buffers, so it cannot
// invoke the §7.11.3.11 / §7.11.3.14 pixel-level blend inline;
// instead it reads the §5.11.33 dispatcher's per-block
// `is_inter_intra` verdict back out of the
// [`ComputePredictionReadout`] and surfaces it on the returned
// [`DecodedBlock`]. A stream consumer that maintains the
// per-plane buffers routes an `is_inter_intra == true` block —
// together with the §5.11.23 `RefFrame[0]` / `Mvs[..][0]` /
// §5.11.28 `interintra_mode` + optional wedge selectors carried
// on `inter_block` — through the shared
// [`crate::reconstruct_inter_intra_block`] driver (or, when it
// drives the §5.11.33 task list directly, through
// [`crate::reconstruct_inter_intra_from_dispatch`]).
//
// Defense-in-depth: the dispatcher derives its verdict from the
// same `(is_inter, ref_frame_1_is_intra)` pair this arm passed
// it, so the readout flag MUST agree with the locally-derived
// condition. A mismatch is a §5.11.33 dispatcher contract
// breach (a future spec/reader edit drifting the two apart),
// surfaced as a caller-bug guard rather than silently trusted.
let is_inter_intra = readout.is_inter_intra;
if is_inter_intra != ref_frame_1_is_intra {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// r387 — in-walk §5.11.33 pixel prediction. When the caller
// supplies the reference-pixel state
// ([`InterFrameContext::pixels`]), motion-compensate this leaf
// into `CurrFrame[ plane ]` NOW (the spec's per-block
// `compute_prediction()` position), so the §5.11.34
// `residual()` §7.12.3 step-3 merge below lands on the
// prediction and later intra blocks read reconstructed
// neighbours.
if let Some(px) = ctx.pixels {
// §5.11.27 LOCALWARP: a `WARPED_CAUSAL` leaf's §7.11.3.8
// least-squares fit runs at the in-walk
// `compute_prediction()` position. The §7.10.4 sample scan
// reads only already-decoded neighbour rows (never the
// current leaf's own cells), so re-running it here is
// bit-identical to the §5.11.27 read site.
if inter_block.motion_mode == MOTION_MODE_WARPED_CAUSAL {
let mut cands: Vec<crate::inter_pred::WarpSampleCand> = Vec::new();
self.find_warp_samples_list(
mi_row,
mi_col,
sub_size,
inter_block.ref_frame[0],
inter_block.mv,
&mut cands,
);
let lw = crate::inter_pred::warp_estimation(
&cands,
mi_row as i32,
mi_col as i32,
bw4 as i32,
bh4 as i32,
[inter_block.mv[0][0] as i16, inter_block.mv[0][1] as i16],
);
let origin = (mi_row * self.mi_cols + mi_col) as usize;
self.ensure_local_warp_params();
self.local_warp_params[origin * 6..origin * 6 + 6]
.copy_from_slice(&lw.local_warp_params);
self.local_warp_valid[origin] = u8::from(lw.local_valid);
}
// §5.11.33 `IsInterIntra` arm: the spec runs
// `predict_intra` (the translated `interintra_mode`) into
// `CurrFrame[ plane ]` BEFORE `predict_inter` reads it back
// as the §7.11.3.14 blend's `pred1`. The §5.11.33
// dispatcher above emitted one intra task per plane
// (`mode != COMPUTE_PRED_MODE_INTER`); drive each through
// the §7.11.2 writer.
if is_inter_intra {
let sb_mask: u32 = if use_128x128_superblock { 31 } else { 15 };
for task in readout
.tasks
.iter()
.filter(|t| t.mode != COMPUTE_PRED_MODE_INTER)
{
let (sub_x, sub_y) = if task.plane > 0 {
(subsampling_x, subsampling_y)
} else {
(0u8, 0u8)
};
let rows = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
let cols = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
self.ensure_curr_frame_plane(task.plane as usize, rows, cols);
// §5.11.33 `predict_intra( plane, baseX, baseY, …,
// log2W, log2H )` — the (w, h) pair maps onto the
// §7.11.2 writer's TX_SIZES_ALL geometry (inter-intra
// blocks span BLOCK_8X8..=BLOCK_32X32, all of whose
// plane extents have a TX_* twin).
let tx_sz = find_tx_size(1usize << task.log2_w, 1usize << task.log2_h)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
// §5.11.33 `BlockDecoded[ plane ][ (subBlockMiRow >>
// subY) - 1 ][ (subBlockMiCol >> subX) + num4x4W ]` /
// `[ (subBlockMiRow >> subY) + num4x4H ][ (subBlockMiCol
// >> subX) - 1 ]` — the above-right / below-left args.
let sub_block_mi_row = (mi_row & sb_mask) as i32;
let sub_block_mi_col = (mi_col & sb_mask) as i32;
let num4x4_w = 1i32 << (task.log2_w.saturating_sub(2));
let num4x4_h = 1i32 << (task.log2_h.saturating_sub(2));
let base_row = sub_block_mi_row >> sub_y;
let base_col = sub_block_mi_col >> sub_x;
let have_above_right = Self::block_decoded_slot(
task.plane as usize,
base_row - 1,
base_col + num4x4_w,
)
.map(|s| self.block_decoded[s] != 0)
.unwrap_or(false);
let have_below_left = Self::block_decoded_slot(
task.plane as usize,
base_row + num4x4_h,
base_col - 1,
)
.map(|s| self.block_decoded[s] != 0)
.unwrap_or(false);
// §7.11.2.8 `get_filter_type( plane )` + the
// frame-scope `enable_intra_edge_filter` — inert for
// the II mode set (DC / V / H / SMOOTH at
// `angle_delta == 0`), passed for §7.11.2 fidelity.
let (avail_u_block, avail_l_block) = if task.plane == 0 {
(avail_u, avail_l)
} else {
(avail_u_chroma, avail_l_chroma)
};
let filter_type = self.get_filter_type(
task.plane,
mi_row,
mi_col,
avail_u_block,
avail_l_block,
sub_x,
sub_y,
);
let eief = self.enable_intra_edge_filter;
self.predict_intra_into_curr_frame(
task.plane,
task.start_x,
task.start_y,
tx_sz,
task.mode as usize,
/* angle_delta = */ 0,
px.bit_depth,
sub_x,
sub_y,
task.have_above,
task.have_left,
have_above_right,
have_below_left,
eief,
filter_type,
/* filter_intra_mode = */ None,
);
}
}
let _ = px.bit_depth;
self.predict_inter_leaf_from_walk(mi_row, mi_col, sub_size, ctx)?;
}
// §5.11.5 line `residual( )` — §5.11.34 dispatcher.
// The §5.11.34 `is_inter && !Lossless && !plane`
// `transform_tree` arm fires on the luma plane; chroma
// falls through to the direct iteration path. r387: the
// caller-threaded §5.9.12 `quant` + §5.9.21 `reduced_tx_set`
// replace the historical neutral pair, so the §5.11.47
// `transform_type()` guard + §5.11.48 `get_tx_set()` reduction
// + §7.12.3 dequant reflect the real frame quantiser.
// §7.12.2: dequant reads the §5.11.12 `CurrentQIndex` running
// value (r405 — see the intra arm's note).
let mut block_quant = *quant;
block_quant.current_q_index = info.current_q_index.clamp(0, 255) as u8;
let neutral_ctx = ResidualContext {
quant: block_quant,
reduced_tx_set,
intra_dir: 0,
segment_id: info.segment_id,
seg_qm_level: quant.seg_qm_level_for(info.segment_id),
uv_mode: 0,
// Inter arm — the §7.11.3 motion-compensation path produces
// this block's prediction, not the §7.11.2.1 intra writer.
is_intra: false,
y_mode: 0,
angle_delta_y: 0,
angle_delta_uv: 0,
// Inter arm — no CfL block; §7.11.5 is intra-only.
cfl_alpha_u: 0,
cfl_alpha_v: 0,
// Inter arm — no filter-intra (§7.11.2.3 is intra-only).
filter_intra_mode: None,
avail_l: false,
avail_u: false,
avail_l_chroma: false,
avail_u_chroma: false,
enable_intra_edge_filter: self.enable_intra_edge_filter,
};
let _residual = self.residual(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
has_chroma,
subsampling_x,
subsampling_y,
/* is_inter = */ true,
info.lossless,
info.skip != 0,
tx_size as usize,
use_128x128_superblock,
&neutral_ctx,
)?;
Ok(DecodedBlock {
mi_row,
mi_col,
mi_size: sub_size,
bw4,
bh4,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
skip: info.skip,
skip_mode: info.skip_mode,
segment_id: info.segment_id,
lossless: info.lossless,
cdef_idx: info.cdef_idx,
current_q_index: info.current_q_index,
current_delta_lf: info.current_delta_lf,
// §5.11.23 `RefFrame[0..2]` from the inter aggregate.
ref_frame: [
inter_block.ref_frame[0] as i8,
inter_block.ref_frame[1] as i8,
],
use_intrabc: info.use_intrabc,
is_inter: 1,
y_mode: inter_block.y_mode,
// §5.11.23 inter blocks carry no chroma intra mode-info
// (the §5.11.28 interintra sub-arc's `UVMode = DC_PRED`
// is surfaced through the [`DecodedInterBlockModeInfo`]
// aggregate, not here) and no palette / filter-intra
// state (§5.11.23 lines 2-3 force `PaletteSize{Y,UV} =
// 0`).
uv_mode: None,
angle_delta_y: 0,
angle_delta_uv: None,
cfl_alpha_u: None,
cfl_alpha_v: None,
palette_size_y: 0,
palette_size_uv: 0,
use_filter_intra: None,
filter_intra_mode: None,
// §5.11.31 `Mv[ 0..2 ]` from the §5.11.23 aggregate.
mv: inter_block.mv,
is_compound,
// §5.11.33 `IsInterIntra`, read back from the
// `compute_prediction()` readout above.
is_inter_intra,
tx_size,
// §5.11.23 lines 2-3 force `PaletteSize{Y,UV} = 0`, so an
// inter block never carries a §5.11.49 colour-index map.
color_map_y: None,
color_map_uv: None,
})
}
/// r387 — §5.11.18 `else intra_block_mode_info()` completion: the
/// §5.11.5 tail for an **intra block inside an inter frame**. The
/// §5.11.18 prologue (skip-mode / skip / segment-id / cdef /
/// deltas / `read_is_inter`) has already run and surfaced `info`
/// with `is_inter == 0`; this method runs the §5.11.22 composite
/// ([`Self::decode_intra_block_mode_info`] — `y_mode` against
/// `TileYModeCdf[ Size_Group[ MiSize ] ]`, then the shared
/// `intra_angle_info_y` / `uv_mode` / palette / filter-intra
/// tail), the §5.11.49 `palette_tokens()`, the §5.11.16
/// `read_block_tx_size()` (intra ⇒ the §5.11.15 `else` arm), the
/// §5.11.42 skip reset, the §5.11.33 `compute_prediction()`
/// enumeration, and the §5.11.34 `residual()` (whose per-TU
/// §5.11.35 `predict_intra` writes this block's §7.11.2
/// prediction into `CurrFrame[ plane ]`, exactly like the
/// §5.11.7 intra-frame arm).
///
/// The `RefFrames[ .. ] = [ INTRA_FRAME, NONE ]` / `Mvs[ .. ] = 0`
/// grid stamps collapse to the walker's pre-fill values (no other
/// leaf writes this footprint), so no explicit stamp is needed;
/// `IsInters[ .. ] = 0` was stamped by the §5.11.20 read.
#[allow(clippy::too_many_arguments)]
fn decode_block_syntax_intra_in_inter_tail(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
bw4: u32,
bh4: u32,
has_chroma: bool,
avail_u: bool,
avail_l: bool,
avail_u_chroma: bool,
avail_l_chroma: bool,
subsampling_x: u8,
subsampling_y: u8,
use_128x128_superblock: bool,
allow_screen_content_tools: bool,
enable_filter_intra: bool,
bit_depth: u8,
quant: &QuantizerParams,
reduced_tx_set: bool,
tx_mode_select: bool,
info: &DecodedInterFrameModeInfo,
) -> Result<DecodedBlock, crate::Error> {
// §8.3.2 `has_palette_y` neighbour ctx (av1-spec p.378) from
// the walker's own `PaletteSizes[ 0 ]` grid — mirrors the
// §5.11.7 else-arm derivation.
let above_palette_y = avail_u
&& mi_row > 0
&& self.palette_sizes[((mi_row - 1) * self.mi_cols + mi_col) as usize] > 0;
let left_palette_y = avail_l
&& mi_col > 0
&& self.palette_sizes[(mi_row * self.mi_cols + mi_col - 1) as usize] > 0;
// §5.11.22 composite.
let ib = self.decode_intra_block_mode_info(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
info.lossless,
has_chroma,
allow_screen_content_tools,
enable_filter_intra,
subsampling_x != 0,
subsampling_y != 0,
above_palette_y,
left_palette_y,
bit_depth,
)?;
let y_mode = ib.y_mode;
let uv_mode = ib.uv_mode;
let angle_delta_y = ib.angle_delta_y;
let angle_delta_uv = ib.angle_delta_uv;
let cfl_alpha_u = ib.cfl_alpha_u;
let cfl_alpha_v = ib.cfl_alpha_v;
let palette_size_y = ib.palette_size_y.unwrap_or(0);
let palette_size_uv = ib.palette_size_uv.unwrap_or(0);
let use_filter_intra = ib.use_filter_intra;
let filter_intra_mode = ib.filter_intra_mode;
// §5.11.5 line `palette_tokens( )` — §5.11.49.
let (color_map_y, color_map_uv) = self.read_palette_token_maps(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
palette_size_y,
palette_size_uv,
subsampling_x,
subsampling_y,
)?;
// Hand the palette sizes + decoded maps to the §5.11.34
// `residual()` dispatch so its per-TU §5.11.35 runs the
// `predict_palette` arm on this block's palette planes.
self.current_palette_size_y = palette_size_y;
self.current_palette_size_uv = palette_size_uv;
self.current_color_map_y = color_map_y.clone();
self.current_color_map_uv = color_map_uv.clone();
// §5.11.5 line `read_block_tx_size( )` — intra ⇒ the §5.11.15
// `else` arm (`read_tx_size( !skip || !is_inter )` = always).
self.decode_block(mi_row, mi_col, sub_size);
let tx_size = self.read_block_tx_size(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
info.lossless,
/* is_inter = */ false,
/* skip = */ info.skip != 0,
tx_mode_select,
)?;
// §5.11.5 line `if ( skip ) reset_block_context( bw4, bh4 )`.
if info.skip != 0 {
self.reset_txb_block_context(
mi_row,
mi_col,
bw4,
bh4,
has_chroma,
subsampling_x,
subsampling_y,
);
}
// §5.11.5 line `compute_prediction( )` — §5.11.33 enumeration
// (the §7.11.2 prediction itself happens per-TU inside the
// §5.11.34 `residual()` dispatch below, mirroring the §5.11.7
// intra-frame arm).
let _readout = self.compute_prediction(
mi_row,
mi_col,
sub_size,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
subsampling_x,
subsampling_y,
/* is_inter = */ false,
y_mode,
uv_mode.unwrap_or(0),
/* ref_frame_1_is_intra = */ false,
/* interintra_mode = */ II_DC_PRED,
)?;
// §5.11.5 line `residual( )` — the full intra `ResidualContext`
// (mode-info + the caller-threaded §5.9.12 quantiser pair).
// §7.12.2: dequant reads the §5.11.12 `CurrentQIndex` running
// value (r405 — see the intra arm's note).
let mut block_quant = *quant;
block_quant.current_q_index = info.current_q_index.clamp(0, 255) as u8;
let res_ctx = ResidualContext {
quant: block_quant,
reduced_tx_set,
intra_dir: intra_dir(
use_filter_intra == Some(1),
y_mode as usize,
filter_intra_mode.unwrap_or(0) as usize,
)
.unwrap_or(0),
segment_id: info.segment_id,
seg_qm_level: quant.seg_qm_level_for(info.segment_id),
uv_mode: uv_mode.unwrap_or(0),
is_intra: true,
y_mode,
angle_delta_y,
angle_delta_uv: angle_delta_uv.unwrap_or(0),
cfl_alpha_u: cfl_alpha_u.unwrap_or(0),
cfl_alpha_v: cfl_alpha_v.unwrap_or(0),
filter_intra_mode: if use_filter_intra == Some(1) {
filter_intra_mode
} else {
None
},
avail_l,
avail_u,
avail_l_chroma,
avail_u_chroma,
enable_intra_edge_filter: self.enable_intra_edge_filter,
};
let _residual = self.residual(
decoder,
cdfs,
mi_row,
mi_col,
sub_size,
has_chroma,
subsampling_x,
subsampling_y,
/* is_inter = */ false,
info.lossless,
info.skip != 0,
tx_size as usize,
use_128x128_superblock,
&res_ctx,
)?;
Ok(DecodedBlock {
mi_row,
mi_col,
mi_size: sub_size,
bw4,
bh4,
has_chroma,
avail_u,
avail_l,
avail_u_chroma,
avail_l_chroma,
skip: info.skip,
skip_mode: info.skip_mode,
segment_id: info.segment_id,
lossless: info.lossless,
cdef_idx: info.cdef_idx,
current_q_index: info.current_q_index,
current_delta_lf: info.current_delta_lf,
// §5.11.22 line 1: `RefFrame = [ INTRA_FRAME, NONE ]`.
ref_frame: [ib.ref_frame[0] as i8, ib.ref_frame[1] as i8],
use_intrabc: info.use_intrabc,
is_inter: 0,
y_mode,
uv_mode,
angle_delta_y,
angle_delta_uv,
cfl_alpha_u,
cfl_alpha_v,
palette_size_y,
palette_size_uv,
use_filter_intra,
filter_intra_mode,
// §5.11.22 blocks carry no MV (intra), and `RefFrame[1] =
// NONE` forces both derived flags off.
mv: [[0, 0], [0, 0]],
is_compound: false,
is_inter_intra: false,
tx_size,
color_map_y,
color_map_uv,
})
}
/// `compute_prediction()` per §5.11.33 (av1-spec p.82-83) — the
/// per-block prediction-task enumeration the §5.11.5
/// [`Self::decode_block_syntax`] driver invokes between the
/// §5.11.16 `read_block_tx_size()` call and the §5.11.34
/// `residual()` call.
///
/// The spec body reads:
///
/// ```text
/// compute_prediction() {
/// sbMask = use_128x128_superblock ? 31 : 15
/// subBlockMiRow = MiRow & sbMask
/// subBlockMiCol = MiCol & sbMask
/// for ( plane = 0; plane < 1 + HasChroma * 2; plane++ ) {
/// planeSz = get_plane_residual_size( MiSize, plane )
/// num4x4W = Num_4x4_Blocks_Wide[ planeSz ]
/// num4x4H = Num_4x4_Blocks_High[ planeSz ]
/// log2W = MI_SIZE_LOG2 + Mi_Width_Log2[ planeSz ]
/// log2H = MI_SIZE_LOG2 + Mi_Height_Log2[ planeSz ]
/// subX = (plane > 0) ? subsampling_x : 0
/// subY = (plane > 0) ? subsampling_y : 0
/// baseX = (MiCol >> subX) * MI_SIZE
/// baseY = (MiRow >> subY) * MI_SIZE
/// candRow = (MiRow >> subY) << subY
/// candCol = (MiCol >> subX) << subX
/// IsInterIntra = ( is_inter && RefFrame[ 1 ] == INTRA_FRAME )
/// if ( IsInterIntra ) {
/// // §5.11.33 interintra_mode → mode translation
/// predict_intra( ... )
/// }
/// if ( is_inter ) {
/// predW = Block_Width[ MiSize ] >> subX
/// predH = Block_Height[ MiSize ] >> subY
/// someUseIntra = 0
/// for ( r = 0; r < (num4x4H << subY); r++ )
/// for ( c = 0; c < (num4x4W << subX); c++ )
/// if ( RefFrames[ candRow + r ][ candCol + c ][ 0 ]
/// == INTRA_FRAME ) someUseIntra = 1
/// if ( someUseIntra ) {
/// predW = num4x4W * 4 ; predH = num4x4H * 4
/// candRow = MiRow ; candCol = MiCol
/// }
/// r = 0
/// for ( y = 0; y < num4x4H * 4; y += predH ) {
/// c = 0
/// for ( x = 0; x < num4x4W * 4; x += predW ) {
/// predict_inter( plane, baseX + x, baseY + y,
/// predW, predH, candRow + r, candCol + c )
/// c++
/// }
/// r++
/// }
/// }
/// }
/// }
/// ```
///
/// ## Implemented in this round
///
/// * §5.11.33 prologue — `sbMask` derivation deferred to the
/// caller (it is unused for the §5.11.33 task list itself;
/// the `subBlockMiRow` / `subBlockMiCol` outputs feed the
/// §5.11.33 inner `predict_intra` argument list).
/// * §5.11.33 outer `for plane = 0..1 + HasChroma * 2` loop —
/// one [`PlanePredictionTask`] entry per visited plane on
/// the intra path.
/// * §5.11.38 `get_plane_residual_size` — derived per-plane
/// `planeSz`, then `num4x4W` / `num4x4H` / `log2W` / `log2H` /
/// `subX` / `subY` / `baseX` / `baseY`.
/// * §5.11.33 `IsInterIntra` arm — emits, per plane, one intra
/// [`PlanePredictionTask`] (the §7.11.3.1 inter-intra blend's
/// intra half) carrying the `interintra_mode → mode` translation
/// (`II_DC_PRED → DC_PRED`, `II_V_PRED → V_PRED`, `II_H_PRED →
/// H_PRED`, else `SMOOTH_PRED`; av1-spec p.82 lines 5142-5145),
/// ahead of the `is_inter` arm's per-4x4 inter tasks. The two
/// guards are non-exclusive: an inter-intra block fires BOTH.
/// * §5.11.33 `is_inter` arm — emits one [`PlanePredictionTask`]
/// per `(plane, i4, j4)` 4x4 sub-block carrying the §7.11.3.1
/// `predict_inter` arguments (`start_x = baseX + j4*4`, `start_y
/// = baseY + i4*4`, `log2_w = log2_h = 2`, `mode =
/// [`COMPUTE_PRED_MODE_INTER`]`). The §7.11.3.4 translational
/// 8-tap MC kernel is the standalone leaf
/// [`crate::inter_pred::block_inter_prediction`] (mirrors the
/// §7.11.2 intra leaf pattern — dispatcher emits tasks, leaves
/// are called separately against `RefFrames[refList]` /
/// `Mvs[candRow][candCol]` / `InterpFilters[candRow][candCol]`
/// state the §5.11.5 walker does not yet track).
/// * §5.11.33 `!is_inter` (intra) arm — emits one
/// [`PlanePredictionTask`] per plane carrying the §5.11.33
/// `predict_intra` arguments (`mode`, `baseX`, `baseY`,
/// `log2W`, `log2H`, `haveAbove` / `haveLeft` from the
/// §5.11.5 `AvailU` / `AvailL` / `AvailUChroma` / `AvailLChroma`
/// flags). Plane 0 carries `y_mode`; planes 1/2 carry `uv_mode`.
/// * §5.11.33 reachable-mode gate — `DC_PRED`, `V_PRED`,
/// `H_PRED`, `SMOOTH_PRED`, `SMOOTH_V_PRED`, `SMOOTH_H_PRED`,
/// and `PAETH_PRED` (the §7.11.2.5 DC sample-generation leaf is
/// [`crate::predict_intra_dc_pred`]; the §7.11.2.4 step-10 /
/// step-11 V_PRED / H_PRED sample-generation leaves are
/// [`crate::predict_intra_v_pred`] / [`crate::
/// predict_intra_h_pred`]; the §7.11.2.6 SMOOTH variants are
/// [`crate::predict_intra_smooth_pred`] / [`crate::
/// predict_intra_smooth_v_pred`] / [`crate::
/// predict_intra_smooth_h_pred`]; the §7.11.2.2 PAETH leaf is
/// [`crate::predict_intra_paeth_pred`], whose
/// `AboveRow[-1]` corner sample is derived by
/// [`crate::derive_above_left`]). The remaining six directional
/// D-modes (`D45_PRED` / `D135_PRED` / `D113_PRED` /
/// `D157_PRED` / `D203_PRED` / `D67_PRED`) surface
/// [`crate::Error::ComputePredictionIntraModeUnsupported`].
///
/// ## Argument contract
///
/// * `mi_row` / `mi_col` / `mi_size` — the §5.11.5 prologue's
/// `MiRow` / `MiCol` / `MiSize`.
/// * `has_chroma` — §5.11.5 prologue derivation.
/// * `avail_u` / `avail_l` — §5.11.5 prologue `AvailU` / `AvailL`.
/// * `avail_u_chroma` / `avail_l_chroma` — §5.11.5 prologue's
/// chroma-edge-case-corrected `AvailUChroma` / `AvailLChroma`.
/// * `subsampling_x` / `subsampling_y` — §5.5.2 sequence-header
/// chroma derivation.
/// * `is_inter` — §5.11.20 / §5.11.7 derived flag (per-block).
/// * `y_mode` — luma intra mode (one of `0..INTRA_MODES`); used
/// for plane 0's task.
/// * `uv_mode` — chroma intra mode (one of `0..INTRA_MODES + 1`,
/// the `+1` covering `UV_CFL_PRED`); used for planes 1 and 2.
/// * `ref_frame_1_is_intra` — caller-supplied flag for the
/// §5.11.33 `IsInterIntra = is_inter && RefFrame[1] ==
/// INTRA_FRAME` gate. `false` on the intra arm.
/// * `interintra_mode` — §5.11.28 `read_interintra_mode` ordinal
/// (`0..INTERINTRA_MODES`: `II_DC_PRED` / `II_V_PRED` /
/// `II_H_PRED` / `II_SMOOTH_PRED`). Consumed only when
/// `is_inter_intra` is true (translated to the intra-half task's
/// `mode`); ignored otherwise. An out-of-range value on the
/// inter-intra arm is a caller bug
/// ([`crate::Error::PartitionWalkOutOfRange`]).
///
/// ## Returns
///
/// A [`ComputePredictionReadout`] with one task per visited plane
/// on the supported intra-DC_PRED path. Returns
/// [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (`mi_size >= BLOCK_SIZES`, `mi_row >= mi_rows`,
/// `mi_col >= mi_cols`, `subsampling_{x,y} > 1`,
/// [`get_plane_residual_size`] returning `None`).
#[allow(clippy::too_many_arguments)]
pub fn compute_prediction(
&mut self,
mi_row: u32,
mi_col: u32,
mi_size: usize,
has_chroma: bool,
avail_u: bool,
avail_l: bool,
avail_u_chroma: bool,
avail_l_chroma: bool,
subsampling_x: u8,
subsampling_y: u8,
is_inter: bool,
y_mode: u8,
uv_mode: u8,
ref_frame_1_is_intra: bool,
interintra_mode: u8,
) -> Result<ComputePredictionReadout, crate::Error> {
// ---------- caller-bug guards ----------
if mi_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if subsampling_x > 1 || subsampling_y > 1 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.33 `IsInterIntra = is_inter && RefFrame[ 1 ] ==
// INTRA_FRAME`. The §5.11.28 `read_interintra_mode == 1` block
// class produces `is_inter == 1 && RefFrame[1] == INTRA_FRAME`
// (the §5.11.28 inner arm imperatively sets `RefFrame[1] =
// INTRA_FRAME`); when that path is wired through
// `decode_block_syntax` the caller passes
// `ref_frame_1_is_intra = true` and the gate fires.
let is_inter_intra = is_inter && ref_frame_1_is_intra;
// §5.11.33 inner `IsInterIntra` arm `interintra_mode → mode`
// translation (av1-spec p.82 lines 5142-5145): `II_DC_PRED →
// DC_PRED`, `II_V_PRED → V_PRED`, `II_H_PRED → H_PRED`, and the
// catch-all (`II_SMOOTH_PRED`) → `SMOOTH_PRED`. Computed once;
// only consumed on the `is_inter_intra` arm below. A
// `interintra_mode` outside `0..INTERINTRA_MODES` is a caller
// bug — the §5.11.28 `read_interintra_mode` reader caps its S()
// at `INTERINTRA_MODES = 4` cumulative frequencies.
if is_inter_intra && (interintra_mode as usize) >= INTERINTRA_MODES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let interintra_intra_mode = match interintra_mode {
II_V_PRED => V_PRED as u8,
II_H_PRED => H_PRED as u8,
// `II_DC_PRED` (the lead arm) plus the catch-all
// `II_SMOOTH_PRED` (av1-spec p.82 line 5145 `else mode =
// SMOOTH_PRED`).
II_DC_PRED => DC_PRED as u8,
_ => SMOOTH_PRED as u8,
};
// §5.11.33 outer loop bound: `1 + HasChroma * 2`. `HasChroma`
// is `1` (3 planes) or `0` (1 plane / luma-only). The
// [`Self::compute_prediction`] dispatcher visits each plane
// and emits one [`PlanePredictionTask`] per visit on the
// intra-DC arm.
let num_planes = 1u8 + if has_chroma { 2 } else { 0 };
let mut tasks: Vec<PlanePredictionTask> = Vec::with_capacity(num_planes as usize);
// §5.11.33 per-plane body.
for plane in 0..num_planes {
// §5.11.33 plane-loop scalars.
let (sub_x, sub_y) = if plane > 0 {
(subsampling_x, subsampling_y)
} else {
(0u8, 0u8)
};
let plane_sz = match get_plane_residual_size(mi_size, plane, sub_x, sub_y) {
Some(s) => s,
None => {
// §6.4.1 conformance: `get_plane_residual_size(
// MiSize, 1 ) != BLOCK_INVALID`. A `None` here is
// a caller-bug — a non-conformant `(MiSize,
// subsampling_x, subsampling_y)` triple.
return Err(crate::Error::PartitionWalkOutOfRange);
}
};
// §5.11.33 lines 4-7: per-plane sizes from the §3 tables.
let log2_w = (MI_SIZE_LOG2 + MI_WIDTH_LOG2[plane_sz]) as u32;
let log2_h = (MI_SIZE_LOG2 + MI_HEIGHT_LOG2[plane_sz]) as u32;
// §5.11.33 lines 9-10: `baseX = (MiCol >> subX) * MI_SIZE`,
// `baseY = (MiRow >> subY) * MI_SIZE`.
let base_x = (mi_col >> sub_x) * (MI_SIZE as u32);
let base_y = (mi_row >> sub_y) * (MI_SIZE as u32);
// §5.11.33 inner `IsInterIntra` arm (av1-spec p.82-83
// lines 5141-5163). On an inter-intra block BOTH the
// `IsInterIntra` `if` AND the `is_inter` `if` fire (they
// are two sibling, non-exclusive guards): the spec runs
// `predict_intra` first (the intra half of the §7.11.3.1
// inter-intra blend, written into `CurrFrame[plane]`), then
// `predict_inter` overwrites it with the §7.11.3.14-blended
// result. We mirror that ordering by emitting, per plane,
// one intra `PlanePredictionTask` (carrying the translated
// `interintra_mode → mode`) ahead of the `is_inter` arm's
// per-4x4 inter tasks. The intra half always covers the
// whole plane region in one `predict_intra` call (av1-spec
// line 5146: `predict_intra( plane, baseX, baseY, …, log2W,
// log2H )`), so it is a single `(log2_w, log2_h)` task at
// `(baseX, baseY)` regardless of `num4x4{W,H}`.
if is_inter_intra {
let (have_above_ii, have_left_ii) = if plane == 0 {
(avail_u, avail_l)
} else {
(avail_u_chroma, avail_l_chroma)
};
tasks.push(PlanePredictionTask {
plane,
start_x: base_x,
start_y: base_y,
log2_w,
log2_h,
mode: interintra_intra_mode,
have_left: have_left_ii,
have_above: have_above_ii,
});
}
// §5.11.33 per-plane `is_inter` branch.
if is_inter {
// §5.11.33 `predict_inter` body — §7.11.3 motion-
// compensated inter prediction (av1-spec p.257-265).
//
// The spec body (av1-spec p.84) enumerates the
// §7.11.3.1 `predict_inter` call once per `(plane, y,
// x)` 4x4-aligned position; the §7.11.3.1 driver
// itself groups those into 8x8-aligned `(i8, j8)`
// sub-blocks for the warp path (`i8 = 0..((h-1) >>
// 3)`, `j8 = 0..((w-1) >> 3)`, av1-spec p.258 line
// 14369). For the translational `useWarp == 0` path
// (the only path the §7.11.3 sample-generation leaves
// in this round cover), one `predict_inter` call per
// plane suffices — the kernel walks the whole `w × h`
// region in one pass. We emit one task per `(i4, j4)`
// 4x4-aligned position (per av1-spec p.84 the
// `predict_inter` argument is the per-TU sub-block);
// for the simplest translational case `num4x4W ==
// num4x4H == 1` (one 4x4 task per plane) holds at
// BLOCK_4X4, growing to `(num4x4H, num4x4W)` tasks
// at larger sizes.
//
// Block sizes per av1-spec p.84 line residual()
// `num4x4W = Num_4x4_Blocks_Wide[ planeSz ]`,
// `num4x4H = Num_4x4_Blocks_High[ planeSz ]`. We re-
// derive them inline from the (log2_w, log2_h)
// already computed — `num4x4{W,H} = 1 << (log2_{w,h}
// - 2)`, i.e. one 4x4 cell per 4 luma samples on each
// axis.
let num4x4_w = 1u32 << (log2_w - 2);
let num4x4_h = 1u32 << (log2_h - 2);
let (have_above_inter, have_left_inter) = if plane == 0 {
(avail_u, avail_l)
} else {
(avail_u_chroma, avail_l_chroma)
};
for i4 in 0..num4x4_h {
for j4 in 0..num4x4_w {
tasks.push(PlanePredictionTask {
plane,
start_x: base_x + j4 * 4,
start_y: base_y + i4 * 4,
log2_w: 2, // per-4x4-cell width
log2_h: 2, // per-4x4-cell height
mode: COMPUTE_PRED_MODE_INTER,
have_left: have_left_inter,
have_above: have_above_inter,
});
}
}
continue;
}
// §5.11.33 `!is_inter` (intra) arm. The §7.11.2.1 dispatch
// routes to one of §7.11.2.5 (DC_PRED) / §7.11.2.6
// (SMOOTH_*) / §7.11.2.4 (directional) / §7.11.2.2
// (PAETH_PRED) per `mode`. As of r187 seven of the
// thirteen Y intra modes land as standalone leaves:
// §7.11.2.5 DC_PRED ([`crate::predict_intra_dc_pred`]),
// the two §7.11.2.4 degenerate directional cases V_PRED
// (`pAngle == 90`, step 10) / H_PRED (`pAngle == 180`,
// step 11) ([`crate::predict_intra_v_pred`] /
// [`crate::predict_intra_h_pred`]), the three §7.11.2.6
// SMOOTH arms (SMOOTH / SMOOTH_V / SMOOTH_H —
// [`crate::predict_intra_smooth_pred`] /
// [`crate::predict_intra_smooth_v_pred`] /
// [`crate::predict_intra_smooth_h_pred`]), and §7.11.2.2
// PAETH ([`crate::predict_intra_paeth_pred`]). The six
// non-degenerate D-mode directional bodies (D45 / D67 /
// D113 / D135 / D157 / D203 — `3..=8`) are next-arc.
let plane_mode = if plane == 0 { y_mode } else { uv_mode };
// §5.11.33 reachable-mode gate (DC / V / H / SMOOTH* /
// PAETH today; D-modes 3..=8 next-arc).
if plane_mode as usize >= INTRA_MODES && plane_mode != UV_CFL_PRED as u8 {
// Out-of-range mode (caller bug — the §5.11.7 /
// §5.11.22 readers cap at `INTRA_MODES`).
return Err(crate::Error::PartitionWalkOutOfRange);
}
// r188: all 13 §6.10.x Y intra modes admitted.
// `DC_PRED == 0`, `V_PRED == 1`, `H_PRED == 2`, `D45_PRED ==
// 3`, `D135_PRED == 4`, `D113_PRED == 5`, `D157_PRED == 6`,
// `D203_PRED == 7`, `D67_PRED == 8`, `SMOOTH_PRED == 9`,
// `SMOOTH_V_PRED == 10`, `SMOOTH_H_PRED == 11`, `PAETH_PRED
// == 12`. The thirteen-mode supported set covers §7.11.2.5
// (DC) + §7.11.2.4 (the two step-10/step-11 degenerate cases
// V_PRED / H_PRED plus the six step-7/8/9 D-modes via
// [`predict_intra_d_mode`]) + §7.11.2.6 (all three SMOOTH
// arms) + §7.11.2.2 (PAETH).
//
// r384: `UV_CFL_PRED == 13` is additionally admitted on the
// CHROMA planes (`plane > 0`) — the §5.11.22 `uv_mode`
// reader legally emits it whenever CFL is allowed, and the
// §5.11.35 per-TU emitter reconstructs it as the §7.11.2
// `DC_PRED` base plus the §7.11.5 luma-AC contribution. The
// dispatcher forwards the `UV_CFL_PRED` ordinal on the task
// so a consumer can apply the same two-stage rendering. On
// the luma plane (`plane == 0`) the ordinal remains a caller
// bug (`ComputePredictionIntraModeUnsupported`) — the
// §5.11.7 y-mode alphabet caps at `INTRA_MODES`.
let supported = (plane_mode as usize) < INTRA_MODES
|| (plane > 0 && plane_mode as usize == UV_CFL_PRED);
if !supported {
return Err(crate::Error::ComputePredictionIntraModeUnsupported);
}
let (have_above, have_left) = if plane == 0 {
(avail_u, avail_l)
} else {
(avail_u_chroma, avail_l_chroma)
};
tasks.push(PlanePredictionTask {
plane,
start_x: base_x,
start_y: base_y,
log2_w,
log2_h,
mode: plane_mode,
have_left,
have_above,
});
}
Ok(ComputePredictionReadout {
is_inter_intra,
is_inter,
num_planes_visited: num_planes,
tasks,
})
}
/// `residual()` per §5.11.34 (av1-spec p.84-85) — the per-block
/// outer dispatch the §5.11.5 [`Self::decode_block_syntax`] walker
/// invokes after [`Self::compute_prediction`] to drive the per-TU
/// `transform_block` enumeration + bitstream-coefficient reads.
///
/// The spec body (av1-spec p.84-85) reads:
///
/// ```text
/// residual() {
/// sbMask = use_128x128_superblock ? 31 : 15
/// widthChunks = Max( 1, Block_Width[ MiSize ] >> 6 )
/// heightChunks = Max( 1, Block_Height[ MiSize ] >> 6 )
/// miSizeChunk = ( widthChunks > 1 || heightChunks > 1 )
/// ? BLOCK_64X64 : MiSize
/// for ( chunkY = 0; chunkY < heightChunks; chunkY++ ) {
/// for ( chunkX = 0; chunkX < widthChunks; chunkX++ ) {
/// miRowChunk = MiRow + ( chunkY << 4 )
/// miColChunk = MiCol + ( chunkX << 4 )
/// subBlockMiRow = miRowChunk & sbMask
/// subBlockMiCol = miColChunk & sbMask
/// for ( plane = 0; plane < 1 + HasChroma * 2; plane++ ) {
/// txSz = Lossless ? TX_4X4
/// : get_tx_size( plane, TxSize )
/// stepX = Tx_Width [ txSz ] >> 2
/// stepY = Tx_Height[ txSz ] >> 2
/// planeSz = get_plane_residual_size( miSizeChunk, plane )
/// num4x4W = Num_4x4_Blocks_Wide[ planeSz ]
/// num4x4H = Num_4x4_Blocks_High[ planeSz ]
/// subX = (plane > 0) ? subsampling_x : 0
/// subY = (plane > 0) ? subsampling_y : 0
/// baseX = (miColChunk >> subX) * MI_SIZE
/// baseY = (miRowChunk >> subY) * MI_SIZE
/// if ( is_inter && !Lossless && !plane ) {
/// transform_tree( baseX, baseY,
/// num4x4W * 4, num4x4H * 4 )
/// } else {
/// baseXBlock = (MiCol >> subX) * MI_SIZE
/// baseYBlock = (MiRow >> subY) * MI_SIZE
/// for ( y = 0; y < num4x4H; y += stepY )
/// for ( x = 0; x < num4x4W; x += stepX )
/// transform_block( plane, baseXBlock, baseYBlock,
/// txSz,
/// x + ( ( chunkX << 4 ) >> subX ),
/// y + ( ( chunkY << 4 ) >> subY ) )
/// }
/// }
/// }
/// }
/// }
/// ```
///
/// And the §5.11.36 `transform_tree( startX, startY, w, h )` body
/// (av1-spec p.86) the inter-luma arm recurses into:
///
/// ```text
/// transform_tree( startX, startY, w, h ) {
/// maxX = MiCols * MI_SIZE
/// maxY = MiRows * MI_SIZE
/// if ( startX >= maxX || startY >= maxY ) return
/// row = startY >> MI_SIZE_LOG2
/// col = startX >> MI_SIZE_LOG2
/// lumaTxSz = InterTxSizes[ row ][ col ]
/// lumaW = Tx_Width [ lumaTxSz ]
/// lumaH = Tx_Height[ lumaTxSz ]
/// if ( w <= lumaW && h <= lumaH ) {
/// txSz = find_tx_size( w, h )
/// transform_block( 0, startX, startY, txSz, 0, 0 )
/// } else if ( w > h ) {
/// transform_tree( startX, startY, w/2, h )
/// transform_tree( startX + w/2, startY, w/2, h )
/// } else if ( w < h ) {
/// transform_tree( startX, startY, w, h/2 )
/// transform_tree( startX, startY + h/2, w, h/2 )
/// } else {
/// transform_tree( startX, startY, w/2, h/2 )
/// transform_tree( startX + w/2, startY, w/2, h/2 )
/// transform_tree( startX, startY + h/2, w/2, h/2 )
/// transform_tree( startX + w/2, startY + h/2, w/2, h/2 )
/// }
/// }
/// ```
///
/// The §5.11.35 `transform_block( plane, baseX, baseY, txSz, x, y )`
/// per-TU body invoked at every leaf is the
/// `predict_intra` / `predict_palette` /
/// `predict_chroma_from_luma` (intra) + `coeffs` + `reconstruct`
/// composition; the §5.11.39 [`Self::coefficients`] reader is the
/// `eob = coeffs( plane, startX, startY, txSz )` leaf the
/// `if ( !skip )` gate wraps.
///
/// ## Implemented in this round (r181)
///
/// * §5.11.34 outer `widthChunks` / `heightChunks` / `miSizeChunk`
/// derivation (av1-spec p.84 lines 2-5).
/// * §5.11.34 outer chunk-loop + per-plane loop (lines 8-30).
/// * §5.11.37 [`get_tx_size`] for the per-plane `txSz` derivation
/// on the §5.11.34 line 12 `Lossless ? TX_4X4 :
/// get_tx_size( plane, TxSize )` selector.
/// * §5.11.38 [`get_plane_residual_size`] for the per-plane
/// `planeSz` derivation (line 15).
/// * §5.11.34 inter-luma `transform_tree(baseX, baseY, num4x4W * 4,
/// num4x4H * 4)` recursion via the internal
/// `Self::residual_transform_tree` helper (av1-spec p.86 §5.11.36
/// body).
/// * §5.11.34 non-inter / chroma direct iteration via the
/// `for ( y = 0; y < num4x4H; y += stepY ) for ( x = 0; x <
/// num4x4W; x += stepX ) transform_block(plane, ...)` loop with
/// the §5.11.35 prologue's `stepX` / `stepY`.
/// * §5.11.35 `transform_block` early-return on `startX >= maxX ||
/// startY >= maxY`.
/// * §5.11.35 `if ( !skip ) coeffs( plane, startX, startY, txSz )`
/// per-leaf call via [`Self::coefficients`]. Each per-TU outcome
/// is recorded in [`ResidualReadout::coeffs`] in `tasks` order.
/// * §5.11.34 `BLOCK_INVALID` chroma plane suppression — the
/// spec's `get_plane_residual_size` returns `BLOCK_INVALID` for
/// chroma planes that would underflow the 4×4 minimum residual
/// size; on `None` here the plane visit short-circuits with no
/// TU emission (matching the spec's implicit "no chroma residual"
/// semantics for the sub-block chroma-edge cases).
///
/// ## Explicit split-off list (see [`ResidualReadout`] for the
/// full per-bullet rationale)
///
/// * §5.11.40 `transform_type` S() per-luma-TU read (defaulted to
/// `DCT_DCT` ⇒ `tx_class = TX_CLASS_2D`).
/// * §5.11.41 `compute_tx_type( plane, txSz, x4, y4 )`
/// (defaulted to `DCT_DCT` alongside §5.11.40).
/// * §7.5 `get_scan( txSz )` over `Default_Scan_*` / `Mrow_Scan_*`
/// / `Mcol_Scan_*` (defaulted to identity scan `0..segEob`).
/// * `AboveLevelContext` / `LeftLevelContext` / `AboveDcContext` /
/// `LeftDcContext` per-plane context arrays (`txb_skip_ctx` and
/// `dc_sign_ctx` are passed as `0`, the clean-state context).
/// * §5.11.35 `predict_intra` / `predict_palette` /
/// `predict_chroma_from_luma` per-TU intra-prediction kernels
/// (the §7.11.2.x sample-generation leaves; only DC_PRED has
/// a standalone leaf today via [`predict_intra_dc_pred`]).
/// * §5.11.35 `reconstruct( plane, startX, startY, txSz )` —
/// §7.12.3 step-1 dequantization is LANDED (r183) via
/// [`dequantize_step1`]; §7.13 inverse transform is LANDED
/// (r182) via [`crate::transform::inverse_transform_2d`];
/// §7.12.3 step-3 frame-buffer merge is LANDED (r185) and
/// writes into the walker's per-plane [`Self::curr_frame`]
/// sample buffer with `flipLR` / `flipUD` destination
/// remapping for the FLIPADST family and a `Clip1` envelope
/// per `BitDepth`. The §9.5.3
/// `Quantizer_Matrix[15][2][QM_TOTAL_SIZE]` table is LANDED
/// (r204) in [`crate::qmatrix`]; the `using_qmatrix &&
/// plane_tx_type < IDTX && seg_qm_level < 15` arm of step-1
/// now evaluates `q2 = Round2( q * Quantizer_Matrix[ ... ],
/// 5 )` per §7.12.3.
/// * §5.11.35 `LoopfilterTxSizes[ plane ][ row ][ col ] = txSz` /
/// `BlockDecoded[ plane ][ row ][ col ] = 1` per-TU stamps.
/// * §7.12.3 step-1 `Dequant[][]` per-`(plane, pos)`
/// dequantization (placeholder identity in r182).
///
/// ## Argument contract
///
/// * `decoder` / `cdfs` — the per-tile bitstream reader and CDF
/// context. Forwarded to [`Self::coefficients`] for per-TU reads.
/// * `mi_row` / `mi_col` / `mi_size` — the §5.11.5 prologue's
/// `MiRow` / `MiCol` / `MiSize`.
/// * `has_chroma` — §5.11.5 prologue derivation (drives the
/// `1 + HasChroma * 2` plane-loop bound).
/// * `subsampling_x` / `subsampling_y` — §5.5.2 sequence-header
/// chroma derivation. `0` / `0` for 4:4:4, `1` / `0` for 4:2:2,
/// `1` / `1` for 4:2:0.
/// * `is_inter` — §5.11.20 / §5.11.7 derived flag (per-block).
/// Gates the §5.11.34 `is_inter && !Lossless && !plane`
/// `transform_tree` arm.
/// * `lossless` — `LosslessArray[ segment_id ]` per §6.8.11. Gates
/// the §5.11.34 `Lossless ? TX_4X4 : get_tx_size(...)` selector
/// and the inter-luma `transform_tree` arm.
/// * `skip` — §5.11.11 `skip` element. Gates the §5.11.35 `if (
/// !skip ) coeffs(...)` per-leaf call.
/// * `tx_size` — §5.11.15 / §5.11.16 `TxSize` (the per-block luma
/// transform size). Routed through `get_tx_size( plane, TxSize )`
/// for the chroma plane.
/// * `use_128x128_superblock` — §5.9.15 sequence-header bit. Drives
/// the §5.11.34 `sbMask = use_128x128_superblock ? 31 : 15` (used
/// for the `subBlockMiRow` / `subBlockMiCol` derivation the
/// §5.11.35 `BlockDecoded` stamps consume — no-op on this walker
/// today; the field is plumbed for the round that wires
/// `BlockDecoded`).
///
/// ## Returns
///
/// A [`ResidualReadout`] with the per-block enumeration. Returns
/// [`crate::Error::PartitionWalkOutOfRange`] for caller-bug
/// arguments (`mi_size >= BLOCK_SIZES`, `mi_row >= mi_rows`,
/// `mi_col >= mi_cols`, `subsampling_{x,y} > 1`,
/// `tx_size >= TX_SIZES_ALL`). Returns
/// [`crate::Error::ResidualCoefficientsTxSizeUnsupported`] when a
/// per-leaf `txSz` falls outside the §5.11.39 reader's accepted
/// range. r182 LIFTS
/// [`crate::Error::ResidualReconstructUnsupported`]: the §7.13
/// inverse transform now fires through
/// [`crate::transform::inverse_transform_2d`] on the `!skip &&
/// eob > 0` path; the residual is captured on
/// [`ResidualReadout::residuals`].
#[allow(clippy::too_many_arguments)]
pub fn residual(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
mi_size: usize,
has_chroma: bool,
subsampling_x: u8,
subsampling_y: u8,
is_inter: bool,
lossless: bool,
skip: bool,
tx_size: usize,
use_128x128_superblock: bool,
ctx: &ResidualContext,
) -> Result<ResidualReadout, crate::Error> {
// ---------- caller-bug guards ----------
if mi_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if subsampling_x > 1 || subsampling_y > 1 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if tx_size >= TX_SIZES_ALL {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.11.34 lines 3-4: `widthChunks` / `heightChunks`.
let block_w = block_width(mi_size);
let block_h = block_height(mi_size);
let width_chunks = core::cmp::max(1, block_w >> 6);
let height_chunks = core::cmp::max(1, block_h >> 6);
// §5.11.34 line 5: `miSizeChunk`.
let mi_size_chunk = if width_chunks > 1 || height_chunks > 1 {
BLOCK_64X64
} else {
mi_size
};
// §5.11.34 line 22-ish: `1 + HasChroma * 2`.
let num_planes = 1u8 + if has_chroma { 2 } else { 0 };
let mut readout = ResidualReadout {
width_chunks: width_chunks as u8,
height_chunks: height_chunks as u8,
mi_size_chunk: mi_size_chunk as u8,
num_planes_visited: num_planes,
tasks: Vec::new(),
coeffs: Vec::new(),
residuals: Vec::new(),
skip,
};
// §5.11.34 outer chunk loop. For `MiSize < BLOCK_128X128` the
// body executes once with `chunkY = chunkX = 0`. For
// `BLOCK_128X64` / `BLOCK_64X128` / `BLOCK_128X128` it
// executes 2 / 2 / 4 times respectively.
for chunk_y in 0..height_chunks {
for chunk_x in 0..width_chunks {
// §5.11.34 lines 10-11: `miRowChunk` / `miColChunk`.
let mi_row_chunk = mi_row + ((chunk_y as u32) << 4);
let mi_col_chunk = mi_col + ((chunk_x as u32) << 4);
// §5.11.34 per-plane body.
for plane in 0..num_planes {
// §5.11.34 line 12: `txSz`.
let tx_sz = if lossless {
TX_4X4
} else {
match get_tx_size(plane, tx_size, mi_size, subsampling_x, subsampling_y) {
Some(t) => t,
None => return Err(crate::Error::PartitionWalkOutOfRange),
}
};
// §5.11.34 lines 13-14: `stepX` / `stepY`.
let step_x = TX_WIDTH[tx_sz] >> 2;
let step_y = TX_HEIGHT[tx_sz] >> 2;
// §5.11.34 line 15: `planeSz`.
let plane_sz = match get_plane_residual_size(
mi_size_chunk,
plane,
subsampling_x,
subsampling_y,
) {
Some(s) => s,
// §6.4.1 conformance: a `None` here for chroma
// is the implicit "no chroma residual" path —
// the §5.11.5 `HasChroma` derivation should
// have caught it. Treat as no-emit for chroma,
// caller-bug for luma.
None => {
if plane == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
continue;
}
};
// §5.11.34 lines 16-17: `num4x4W` / `num4x4H`.
let num4x4_w = NUM_4X4_BLOCKS_WIDE[plane_sz];
let num4x4_h = NUM_4X4_BLOCKS_HIGH[plane_sz];
// §5.11.34 lines 18-19: `subX` / `subY`.
let sub_x = if plane > 0 { subsampling_x } else { 0 };
let sub_y = if plane > 0 { subsampling_y } else { 0 };
// §5.11.34 lines 23-24: `if ( is_inter && !Lossless
// && !plane ) transform_tree(...)` arm vs. the
// else arm.
if is_inter && !lossless && plane == 0 {
// §5.11.34 line 24: `transform_tree(baseX,
// baseY, num4x4W * 4, num4x4H * 4)`.
let base_x = (mi_col_chunk >> sub_x) * (MI_SIZE as u32);
let base_y = (mi_row_chunk >> sub_y) * (MI_SIZE as u32);
let w = (num4x4_w * 4) as u32;
let h = (num4x4_h * 4) as u32;
self.residual_transform_tree(
decoder,
cdfs,
base_x,
base_y,
w,
h,
chunk_x as u8,
chunk_y as u8,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
&mut readout,
)?;
} else {
// §5.11.34 line 26-27: `baseXBlock` /
// `baseYBlock` (note the original `MiCol` /
// `MiRow`, NOT the chunk-offset ones).
let base_x_block = (mi_col >> sub_x) * (MI_SIZE as u32);
let base_y_block = (mi_row >> sub_y) * (MI_SIZE as u32);
// §5.11.34 lines 28-30: per-TU iteration.
let mut y = 0usize;
while y < num4x4_h {
let mut x = 0usize;
while x < num4x4_w {
let x_arg = (x as u32) + (((chunk_x as u32) << 4) >> sub_x);
let y_arg = (y as u32) + (((chunk_y as u32) << 4) >> sub_y);
self.transform_block_emit(
decoder,
cdfs,
plane,
base_x_block,
base_y_block,
tx_sz,
x_arg,
y_arg,
sub_x,
sub_y,
chunk_x as u8,
chunk_y as u8,
/* from_transform_tree = */ false,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
&mut readout,
)?;
if step_x == 0 {
break;
}
x += step_x;
}
if step_y == 0 {
break;
}
y += step_y;
}
}
}
}
}
Ok(readout)
}
/// `transform_tree( startX, startY, w, h )` per §5.11.36 (av1-spec
/// p.86) — the inter-luma recursive split the §5.11.34 outer
/// dispatch invokes for the `is_inter && !Lossless && !plane` arm.
///
/// This is the internal recursion driven from [`Self::residual`].
/// Each leaf (when `w <= lumaW && h <= lumaH`) is routed through
/// [`Self::transform_block_emit`] with `plane = 0` (the inter-luma
/// arm only).
///
/// The spec body decides the split direction by comparing the
/// requested `(w, h)` against `Tx_Width[ InterTxSizes[row][col] ]`
/// / `Tx_Height[ ... ]`; the walker's
/// [`Self::inter_tx_size_at`] helper reads the in-grid
/// `InterTxSizes` ordinal at the current top-left.
///
/// The recursion is bounded by the §5.11.34 outer `transform_tree(
/// baseX, baseY, num4x4W * 4, num4x4H * 4 )` invocation; for every
/// reachable `MiSize` (max `128×128` luma) the recursion depth is
/// bounded by `log2( max(w, h) / 4 ) <= 5`.
#[allow(clippy::too_many_arguments)]
fn residual_transform_tree(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
start_x: u32,
start_y: u32,
w: u32,
h: u32,
chunk_x: u8,
chunk_y: u8,
skip: bool,
is_inter: bool,
lossless: bool,
mi_row: u32,
mi_col: u32,
mi_size: usize,
use_128x128_superblock: bool,
ctx: &ResidualContext,
readout: &mut ResidualReadout,
) -> Result<(), crate::Error> {
// §5.11.36 lines 1-3: `maxX = MiCols * MI_SIZE` / `maxY = MiRows
// * MI_SIZE` early-return.
let max_x = self.mi_cols * (MI_SIZE as u32);
let max_y = self.mi_rows * (MI_SIZE as u32);
if start_x >= max_x || start_y >= max_y {
return Ok(());
}
// §5.11.36 lines 4-5: `row` / `col` for the `InterTxSizes[
// row ][ col ]` lookup.
let row = (start_y >> (MI_SIZE_LOG2 as u32)) as i32;
let col = (start_x >> (MI_SIZE_LOG2 as u32)) as i32;
let luma_tx_sz = self.inter_tx_size_at(row, col) as usize;
if luma_tx_sz >= TX_SIZES_ALL {
return Err(crate::Error::ResidualTransformTreeUnsupported);
}
let luma_w = TX_WIDTH[luma_tx_sz] as u32;
let luma_h = TX_HEIGHT[luma_tx_sz] as u32;
// §5.11.36 lines 7-8: leaf-emit on `w <= lumaW && h <= lumaH`.
if w <= luma_w && h <= luma_h {
// §5.11.36 line 9: `txSz = find_tx_size( w, h )`.
let tx_sz = match find_tx_size(w as usize, h as usize) {
Some(t) => t,
None => return Err(crate::Error::ResidualTransformTreeUnsupported),
};
// §5.11.36 line 10: `transform_block( 0, startX, startY,
// txSz, 0, 0 )`. The `x` / `y` arguments are `0` (the leaf
// is the whole tx region — `startX` / `startY` already at
// the leaf's top-left).
return self.transform_block_emit(
decoder,
cdfs,
/* plane = */ 0,
/* base_x = */ start_x,
/* base_y = */ start_y,
tx_sz,
/* x = */ 0,
/* y = */ 0,
/* sub_x = */ 0,
/* sub_y = */ 0,
chunk_x,
chunk_y,
/* from_transform_tree = */ true,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
);
}
// §5.11.36 lines 12-25: per-direction split.
if w > h {
self.residual_transform_tree(
decoder,
cdfs,
start_x,
start_y,
w / 2,
h,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
self.residual_transform_tree(
decoder,
cdfs,
start_x + w / 2,
start_y,
w / 2,
h,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
} else if w < h {
self.residual_transform_tree(
decoder,
cdfs,
start_x,
start_y,
w,
h / 2,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
self.residual_transform_tree(
decoder,
cdfs,
start_x,
start_y + h / 2,
w,
h / 2,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
} else {
self.residual_transform_tree(
decoder,
cdfs,
start_x,
start_y,
w / 2,
h / 2,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
self.residual_transform_tree(
decoder,
cdfs,
start_x + w / 2,
start_y,
w / 2,
h / 2,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
self.residual_transform_tree(
decoder,
cdfs,
start_x,
start_y + h / 2,
w / 2,
h / 2,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
self.residual_transform_tree(
decoder,
cdfs,
start_x + w / 2,
start_y + h / 2,
w / 2,
h / 2,
chunk_x,
chunk_y,
skip,
is_inter,
lossless,
mi_row,
mi_col,
mi_size,
use_128x128_superblock,
ctx,
readout,
)?;
}
Ok(())
}
/// `transform_block( plane, baseX, baseY, txSz, x, y )` per §5.11.35
/// (av1-spec p.85) — the per-TU emit + bitstream-coefficient-read
/// the §5.11.34 outer dispatch and the §5.11.36 `transform_tree`
/// recursion both terminate in.
///
/// The spec body composes (intra path):
///
/// ```text
/// startX = baseX + 4 * x
/// startY = baseY + 4 * y
/// ... // subBlockMi{Row,Col} for BlockDecoded stamps (deferred)
/// if ( startX >= maxX || startY >= maxY ) return // line 13
/// if ( !is_inter ) {
/// predict_intra( ... ) or predict_palette( ... ) or
/// predict_chroma_from_luma( ... ) // deferred
/// }
/// if ( !skip ) {
/// eob = coeffs( plane, startX, startY, txSz ) // §5.11.39
/// if ( eob > 0 ) reconstruct( ... ) // deferred
/// }
/// // LoopfilterTxSizes / BlockDecoded stamps // deferred
/// ```
///
/// This dispatcher records every per-TU task in
/// [`ResidualReadout::tasks`], invokes [`Self::coefficients`] on
/// the `!skip` path (with the splits noted in
/// [`ResidualReadout`]'s docs feeding the deferred arguments),
/// and — as of r182 — invokes
/// [`crate::transform::inverse_transform_2d`] (§7.13.3 2D
/// inverse transform) whenever the §5.11.39 reader reports
/// `eob > 0`, recording the per-TU `Residual[][]` on
/// [`ResidualReadout::residuals`]. The dispatcher returns
/// `Ok(())` cleanly; the previous
/// [`crate::Error::ResidualReconstructUnsupported`] gate has
/// been lifted.
#[allow(clippy::too_many_arguments)]
fn transform_block_emit(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
plane: u8,
base_x: u32,
base_y: u32,
tx_sz: usize,
x: u32,
y: u32,
sub_x: u8,
sub_y: u8,
chunk_x: u8,
chunk_y: u8,
from_transform_tree: bool,
skip: bool,
is_inter: bool,
lossless: bool,
mi_row: u32,
mi_col: u32,
mi_size: usize,
use_128x128_superblock: bool,
ctx: &ResidualContext,
readout: &mut ResidualReadout,
) -> Result<(), crate::Error> {
// §5.11.35 lines 1-2: `startX = baseX + 4 * x`, `startY =
// baseY + 4 * y`.
let start_x = base_x + 4 * x;
let start_y = base_y + 4 * y;
// §5.11.35 lines 10-11: `maxX = (MiCols * MI_SIZE) >> subX`,
// `maxY = (MiRows * MI_SIZE) >> subY`.
let max_x = (self.mi_cols * (MI_SIZE as u32)) >> sub_x;
let max_y = (self.mi_rows * (MI_SIZE as u32)) >> sub_y;
// §5.11.35 line 13: early-return on out-of-frame.
if start_x >= max_x || start_y >= max_y {
return Ok(());
}
if tx_sz >= TX_SIZES_ALL {
return Err(crate::Error::ResidualCoefficientsTxSizeUnsupported);
}
// §5.11.35 line `if ( !is_inter ) { ... predict_intra( ... ) }`
// (av1-spec p.85, line 5264) — the §7.11.2.1 intra prediction
// runs **before** the §5.11.39 coeff read + §7.13 reconstruct,
// writing `pred[i][j]` into `CurrFrame[plane][startY+i][startX+
// j]` so the subsequent §7.12.3 step-3 merge adds the residual
// on top. The non-palette / non-CfL / non-filter-intra arm is
// covered here; palette / CfL / filter-intra route elsewhere.
if !is_inter && ctx.is_intra {
// §5.11.35 palette arm: `if ( ( plane == 0 && PaletteSizeY )
// || ( plane != 0 && PaletteSizeUV ) ) predict_palette(...)`
// — a palette-coded plane takes the §7.11.4 sample write
// instead of the §7.11.2 intra prediction (av1-spec p.85).
let tu_palette_size = if plane == 0 {
self.current_palette_size_y
} else {
self.current_palette_size_uv
};
if tu_palette_size > 0 {
self.predict_palette_tu_into_curr_frame(
plane, start_x, start_y, x, y, tx_sz, mi_row, mi_col, sub_x, sub_y,
)?;
} else {
// §5.11.35 lines 5-9 superblock-local anchor (mirrors the
// BlockDecoded stamp at the function tail).
let row_anchor = (start_y << sub_y) >> MI_SIZE_LOG2;
let col_anchor = (start_x << sub_x) >> MI_SIZE_LOG2;
let sb_mask: u32 = if use_128x128_superblock { 31 } else { 15 };
let sub_block_mi_row = (row_anchor & sb_mask) as i32;
let sub_block_mi_col = (col_anchor & sb_mask) as i32;
let step_x4 = (TX_WIDTH[tx_sz] >> MI_SIZE_LOG2) as i32;
let step_y4 = (TX_HEIGHT[tx_sz] >> MI_SIZE_LOG2) as i32;
let base_row = sub_block_mi_row >> sub_y;
let base_col = sub_block_mi_col >> sub_x;
// §7.11.2.1 `haveAboveRight` / `haveBelowLeft` reads
// (av1-spec p.85, lines 5287-5292).
let have_above_right =
Self::block_decoded_slot(plane as usize, base_row - 1, base_col + step_x4)
.map(|s| self.block_decoded[s] != 0)
.unwrap_or(false);
let have_below_left =
Self::block_decoded_slot(plane as usize, base_row + step_y4, base_col - 1)
.map(|s| self.block_decoded[s] != 0)
.unwrap_or(false);
// §5.11.35 `haveLeft` / `haveAbove` (lines 5285-5286):
// `(plane==0 ? AvailL : AvailLChroma) || x > 0`.
let (avail_l, avail_u) = if plane == 0 {
(ctx.avail_l, ctx.avail_u)
} else {
(ctx.avail_l_chroma, ctx.avail_u_chroma)
};
let have_left = avail_l || x > 0;
let have_above = avail_u || y > 0;
// §5.11.35 lines 5276-5280: `isCfl = (plane > 0 && UVMode ==
// UV_CFL_PRED)`; `mode = (plane == 0) ? YMode : (isCfl ?
// DC_PRED : UVMode)`. The CfL chroma block first runs the
// §7.11.2 `DC_PRED` base here, then the §7.11.5
// `predict_chroma_from_luma` AC contribution (below) layers
// the reconstructed-luma high frequencies on top.
let is_cfl = plane > 0 && ctx.uv_mode as usize == UV_CFL_PRED;
let (mode, angle_delta) = if plane == 0 {
(ctx.y_mode as usize, ctx.angle_delta_y as i32)
} else if is_cfl {
(DC_PRED, 0)
} else {
(ctx.uv_mode as usize, ctx.angle_delta_uv as i32)
};
// §7.11.2.4 step-4 inputs: `enable_intra_edge_filter` is
// frame-scope (cached on `ctx`); `filterType` is the
// §7.11.2.8 `get_filter_type( plane )` neighbour smooth-mode
// check. Both planes are now covered: luma reads the §6.10.4
// `YModes[]` grid, chroma reads `UVModes[]` at the §7.11.2.8
// sub-sampled neighbour coordinates. The §7.11.2.8 lookup
// uses block-level availability (`AvailU` / `AvailL` for
// luma, `AvailUChroma` / `AvailLChroma` for chroma), not the
// `|| x > 0` chunk-extended `haveAbove` / `haveLeft`.
let (avail_u_block, avail_l_block) = if plane == 0 {
(ctx.avail_u, ctx.avail_l)
} else {
(ctx.avail_u_chroma, ctx.avail_l_chroma)
};
let filter_type = self.get_filter_type(
plane,
mi_row,
mi_col,
avail_u_block,
avail_l_block,
sub_x,
sub_y,
);
let eief = ctx.enable_intra_edge_filter;
// §7.11.2.1 first dispatch arm — `use_filter_intra` is
// luma-only (`plane == 0`). `ctx.filter_intra_mode` is
// `Some(mode)` only when the block decoded `use_filter_intra
// == 1`; pass it through for plane 0 so
// [`Self::predict_intra_into_curr_frame`] routes to the
// §7.11.2.3 recursive process, and `None` for chroma.
let fi_mode = if plane == 0 {
ctx.filter_intra_mode.map(|m| m as usize)
} else {
None
};
self.predict_intra_into_curr_frame(
plane,
start_x,
start_y,
tx_sz,
mode,
angle_delta,
ctx.quant.bit_depth,
sub_x,
sub_y,
have_above,
have_left,
have_above_right,
have_below_left,
eief,
filter_type,
fi_mode,
);
// §5.11.35 line `if ( isCfl ) predict_chroma_from_luma(...)`
// (av1-spec p.85, line 5296) — the §7.11.5 chroma-from-luma
// process runs **after** the chroma DC base above, reading
// the already-reconstructed luma plane (which was walked
// first by the §5.11.34 plane loop) and adding the
// alpha-scaled luma high frequencies on top of `DC_PRED`.
// `CflAlphaU` / `CflAlphaV` are the §5.11.45-decoded signed
// magnitudes carried on `ctx`.
if is_cfl {
let alpha = if plane == 1 {
ctx.cfl_alpha_u as i32
} else {
ctx.cfl_alpha_v as i32
};
self.predict_chroma_from_luma_into_curr_frame(
plane,
start_x,
start_y,
tx_sz,
sub_x,
sub_y,
alpha,
ctx.quant.bit_depth,
);
}
} // end of the §5.11.35 non-palette (predict_intra) arm
// §5.11.35 lines 5300-5302: `if ( plane == 0 ) { MaxLumaW =
// startX + stepX * 4; MaxLumaH = startY + stepY * 4 }`. The
// §7.11.5 luma-subsample clamp reads these back on the
// co-located chroma TUs that follow in the same block's
// plane loop.
if plane == 0 {
let step_x4 = (TX_WIDTH[tx_sz] >> MI_SIZE_LOG2) as u32;
let step_y4 = (TX_HEIGHT[tx_sz] >> MI_SIZE_LOG2) as u32;
self.max_luma_w = start_x + step_x4 * 4;
self.max_luma_h = start_y + step_y4 * 4;
}
}
// Record the per-TU task (spec-order emission). `plane_tx_type`
// is filled in below on the `!skip` arm once §5.11.40
// `compute_tx_type` runs; the `skip == true` path leaves it at
// the §5.11.40 `Lossless || txSzSqrUp > TX_32X32` fallback
// (DCT_DCT) — which is also the §7.12.3 step-3 flipUD / flipLR
// identity (neither flip flag fires for DCT_DCT, so a
// skip-path TU with no residual maps cleanly to a no-op step-3
// merge anyway).
readout.tasks.push(ResidualTuTask {
plane,
start_x,
start_y,
tx_size: tx_sz as u8,
chunk_x,
chunk_y,
from_transform_tree,
plane_tx_type: DCT_DCT as u8,
});
// §5.11.35 line 32: `if ( !skip ) eob = coeffs( plane, startX,
// startY, txSz )` — invoke the §5.11.39 reader for the
// bitstream-coefficient read.
if !skip {
// r183 — §5.11.47 `transform_type` + §5.11.40
// `compute_tx_type` + §7.12.3 step-1 dequantization.
//
// The §5.11.39 reader needs the §8.3.2 `tx_class`
// (consumed by the `coeff_base{_eob}` / `coeff_br` ctx
// walks); the §5.11.40 `compute_tx_type` derivation
// produces the `PlaneTxType` whose `get_tx_class` reduction
// feeds that. On the luma plane (`plane == 0`) the
// §5.11.47 `transform_type` S() read produces `TxType`
// and stamps it into `TxTypes[]`; the §5.11.40 derivation
// then collapses to `TxTypes[y4][x4]`. On chroma the
// §5.11.40 derivation falls back to `Mode_To_Txfm[UVMode]`
// (or `TxTypes[y4][x4]` on the inter path).
let tx_w = TX_WIDTH[tx_sz];
let tx_h = TX_HEIGHT[tx_sz];
// 4×4-luma-sample coordinates of this TU's top-left, in
// mi-units. The §5.11.47 `TxTypes[ y4 + j ][ x4 + i ]`
// grid stamp + the §5.11.40 `TxTypes[ y4 ][ x4 ]`
// chroma lookup both consume these.
let x4 = start_x >> 2;
let y4 = start_y >> 2;
// §8.3.2 `all_zero` / `dc_sign` ctx — the neighbour-array
// walks over `Above{Level,Dc}Context` /
// `Left{Level,Dc}Context` (r284; the encoder's
// `write_transform_block_intra` derives the same pair from
// its mirror walker). Symbol-free derivations, so they may
// run ahead of the reads.
let txb_skip_ctx = self
.txb_skip_ctx(plane, tx_sz, mi_size, x4, y4, sub_x, sub_y)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let dc_sign_ctx = self.dc_sign_ctx(plane, tx_sz, x4, y4, sub_x, sub_y);
// §5.11.39 line 13: `all_zero S()` — the FIRST coefficient
// symbol. The §5.11.47 `transform_type()` read below is
// gated on `all_zero == 0` per the §5.11.39 else-arm
// ordering (r384 fix — the read previously ran ahead of
// `all_zero`, desynchronising real encoder-produced
// streams).
let all_zero = self.read_all_zero(decoder, cdfs, tx_sz, txb_skip_ctx)?;
if all_zero {
// §5.11.39 `if ( all_zero )` arm: the luma plane stamps
// `TxTypes[ y4 + j ][ x4 + i ] = DCT_DCT` over the TU
// footprint; no further reads for this TU.
if plane == 0 {
let w4 = (tx_w >> 2) as u32;
let h4 = (tx_h >> 2) as u32;
self.stamp_tx_type(x4, y4, w4, h4, DCT_DCT as u8);
}
// §5.11.39 tail — culLevel = 0 / dcCategory = 0 stamps
// fire on the gate-closed arm too.
self.stamp_txb_level_context(plane, tx_sz, x4, y4, 0, 0);
readout.coeffs.push(CoefficientsReadout {
all_zero: true,
eob: 0,
cul_level: 0,
dc_category: 0,
});
readout.residuals.push(None);
} else {
// §5.11.47 `transform_type` S() — luma-only on the
// `all_zero == 0` arm. The chroma plane uses
// `compute_tx_type`'s `Mode_To_Txfm[UVMode]` fallback
// without a per-TU S() read.
if plane == 0 {
let tt = self.transform_type(
decoder,
cdfs,
tx_sz,
is_inter,
ctx.intra_dir,
ctx.reduced_tx_set,
ctx.segment_id,
&ctx.quant,
)?;
self.stamp_tx_type(x4, y4, tt.w4 as u32, tt.h4 as u32, tt.tx_type);
}
// §5.11.40 `compute_tx_type( plane, txSz, x4, y4 )`.
// Derives the per-plane `PlaneTxType` after the §5.11.47
// stamp.
let tx_set = if is_inter {
inter_tx_type_set(
tx_size_sqr_index(tx_sz) as u32,
TX_SIZE_SQR_UP[tx_sz] as u32,
ctx.reduced_tx_set,
)
} else {
intra_tx_type_set(
tx_size_sqr_index(tx_sz) as u32,
TX_SIZE_SQR_UP[tx_sz] as u32,
ctx.reduced_tx_set,
)
};
let plane_tx_type = {
// Pull TxTypes[] reads through a closure over a
// shared borrow of the grid (never a copy: the grid
// is `MiRows * MiCols` bytes and this runs once per
// transform block, so a per-block copy is quadratic
// in frame area — the r452 fuzz OOM).
let tx_types_grid = &self.tx_types;
let mi_cols = self.mi_cols;
let mi_rows = self.mi_rows;
compute_tx_type(
plane as usize,
tx_sz,
lossless,
is_inter,
tx_set,
mi_row,
mi_col,
x4,
y4,
sub_x as u32,
sub_y as u32,
ctx.uv_mode as usize,
|yy, xx| {
if yy >= mi_rows || xx >= mi_cols {
DCT_DCT
} else {
tx_types_grid[(yy * mi_cols + xx) as usize] as usize
}
},
)
};
// Back-fill the task's `plane_tx_type` with the §5.11.40
// derivation — the §7.12.3 step-3 merge below consults it
// for the `flipUD` / `flipLR` destination remap. The task
// was pushed above with the DCT_DCT placeholder; on this
// arm we replace it with the actual `PlaneTxType`.
if let Some(last) = readout.tasks.last_mut() {
last.plane_tx_type = plane_tx_type as u8;
}
// §8.3.2 tx_class derivation — fold the §3 `*_DCT` /
// `*_ADST` / `*_FLIPADST` constants into TX_CLASS_2D /
// TX_CLASS_HORIZ / TX_CLASS_VERT.
let tx_class = match plane_tx_type {
V_DCT | V_ADST | V_FLIPADST => TX_CLASS_VERT,
H_DCT | H_ADST | H_FLIPADST => TX_CLASS_HORIZ,
_ => TX_CLASS_2D,
};
// §7.5 / §5.11.41 `get_scan( txSz )` — routes per
// `(txSz, PlaneTxType)` to the per-size Default / Mrow /
// Mcol scan table the §5.11.39 reader walks.
let scan = crate::scan::get_scan(tx_sz, plane_tx_type);
let mut quant = vec![0i32; tx_w * tx_h];
let coeffs = self.coefficients_gate_open(
decoder,
cdfs,
plane,
is_inter as u8,
tx_sz,
tx_class,
dc_sign_ctx,
scan,
&mut quant,
)?;
// §5.11.39 tail — stamp the TU's `culLevel` / `dcCategory`
// into the §6.10.2 context arrays.
self.stamp_txb_level_context(
plane,
tx_sz,
x4,
y4,
coeffs.cul_level,
coeffs.dc_category,
);
readout.coeffs.push(coeffs);
// §5.11.35 line 34: `if ( eob > 0 ) reconstruct(...)` —
// §7.12.3 dequantization (step 1) + §7.13 inverse
// transform + §7.12.3 step-3 frame-buffer merge.
if coeffs.eob > 0 {
let dequant = dequantize_step1(
&quant,
tx_sz,
plane,
ctx.segment_id,
plane_tx_type,
ctx.seg_qm_level[plane as usize],
&ctx.quant,
);
let residual = crate::transform::inverse_transform_2d(
&dequant,
tx_sz,
plane_tx_type,
ctx.quant.bit_depth as u32,
lossless,
);
// §7.12.3 step-3 (av1-spec p.295) — merge the per-TU
// `Residual[i][j]` into `CurrFrame[plane][y + yy][x +
// xx]` with the `flipLR` / `flipUD` destination remap
// for the FLIPADST family and a `Clip1` envelope
// against `(1 << BitDepth) - 1`. The buffer is
// allocated lazily on the first per-plane merge call
// (see [`Self::ensure_curr_frame_plane`]).
self.curr_frame_step3_merge(
plane,
start_x,
start_y,
tx_sz,
plane_tx_type,
&residual,
ctx.quant.bit_depth,
sub_x,
sub_y,
);
readout.residuals.push(Some(residual));
} else {
readout.residuals.push(None);
}
}
} else if plane == 0 {
// §5.11.39 `all_zero == 1` luma stamp — the §5.11.39
// gate-closed path writes `TxTypes[ y4 + j ][ x4 + i ] =
// DCT_DCT` over the TU footprint. The §5.11.34 skip arm
// doesn't reach §5.11.39 at all, but the §5.11.40
// `compute_tx_type` chroma path on the next iteration
// reads `TxTypes[]` — so on the `skip == true` arm we
// also pre-stamp DCT_DCT to preserve the invariant.
let x4 = start_x >> 2;
let y4 = start_y >> 2;
let w4 = (TX_WIDTH[tx_sz] >> 2) as u32;
let h4 = (TX_HEIGHT[tx_sz] >> 2) as u32;
self.stamp_tx_type(x4, y4, w4, h4, DCT_DCT as u8);
}
// §5.11.35 lines 35-43: `LoopfilterTxSizes[ plane ][ ... ]` /
// `BlockDecoded[ plane ][ ... ]` per-TU stamps.
//
// The `LoopfilterTxSizes[]` half is still deferred (the walker
// doesn't allocate the per-plane LF grid yet). The
// `BlockDecoded[]` half (§6.10.3) lands here: the per-TU stamp
// marks every 4×4 cell the transform unit covers as decoded so
// the §5.11.35 `predict_intra` above-right / below-left
// availability derivation on subsequent TUs reads the correct
// border state.
//
// §5.11.35 lines 5-9 derive the superblock-local anchor:
// row = ( startY << subY ) >> MI_SIZE_LOG2
// col = ( startX << subX ) >> MI_SIZE_LOG2
// sbMask = use_128x128_superblock ? 31 : 15
// subBlockMiRow = row & sbMask
// subBlockMiCol = col & sbMask
// stepX = Tx_Width[ txSz ] >> MI_SIZE_LOG2
// stepY = Tx_Height[ txSz ] >> MI_SIZE_LOG2
let row = (start_y << sub_y) >> MI_SIZE_LOG2;
let col = (start_x << sub_x) >> MI_SIZE_LOG2;
let sb_mask: u32 = if use_128x128_superblock { 31 } else { 15 };
let sub_block_mi_row = (row & sb_mask) as i32;
let sub_block_mi_col = (col & sb_mask) as i32;
let step_x = (TX_WIDTH[tx_sz] >> MI_SIZE_LOG2) as i32;
let step_y = (TX_HEIGHT[tx_sz] >> MI_SIZE_LOG2) as i32;
// §5.11.35 lines 40-43: `BlockDecoded[ plane ][
// ( subBlockMiRow >> subY ) + i ][ ( subBlockMiCol >> subX ) +
// j ] = 1` for `i ∈ 0..stepY`, `j ∈ 0..stepX`.
let base_row = sub_block_mi_row >> sub_y;
let base_col = sub_block_mi_col >> sub_x;
for i in 0..step_y {
for j in 0..step_x {
if let Some(slot) =
Self::block_decoded_slot(plane as usize, base_row + i, base_col + j)
{
self.block_decoded[slot] = 1;
}
}
}
Ok(())
}
/// `decode_partition_syntax( r, c, bSize )` — §5.11.4 partition
/// tree walker that calls [`Self::decode_block_syntax`] at every
/// leaf (instead of the leaf-only [`Self::decode_block`] used by
/// [`Self::decode_partition`]).
///
/// Same recursive shape as [`Self::decode_partition`]: identical
/// `partition` / `split_or_horz` / `split_or_vert` symbol reads,
/// identical `partition_subsize` dispatch, identical edge-of-frame
/// fall-throughs. The only difference is that at every
/// `decode_block` call site in the §5.11.4 partition-decode
/// pseudocode we route through [`Self::decode_block_syntax`] —
/// which performs the §5.11.5 syntax walk for the leaf — instead
/// of the leaf-only emitter.
///
/// On the first leaf that hits a §5.11.5 stub the walker
/// short-circuits with the stub variant of [`crate::Error`]. The
/// partition state stamped before the short-circuit (every leaf
/// that succeeded fully) remains observable on the grid
/// accessors.
#[allow(clippy::too_many_arguments)]
pub fn decode_partition_syntax(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
r: u32,
c: u32,
b_size: usize,
frame_is_intra: bool,
subsampling_x: u8,
subsampling_y: u8,
num_planes: u8,
seg_id_pre_skip: bool,
segmentation_enabled: bool,
seg_skip: &[bool; MAX_SEGMENTS],
last_active_seg_id: u8,
lossless_array: &[bool; MAX_SEGMENTS],
coded_lossless: bool,
enable_cdef: bool,
allow_intrabc: bool,
cdef_bits: u32,
read_deltas: bool,
use_128x128_superblock: bool,
delta_q_res: u8,
delta_lf_present: bool,
delta_lf_multi: bool,
mono_chrome: bool,
delta_lf_res: u8,
// r281: §5.11.7 / §5.11.22 / §5.11.24 / §5.11.46 gates. See
// [`Self::decode_block_syntax`]'s parameters of the same
// names for the contracts.
allow_screen_content_tools: bool,
enable_filter_intra: bool,
bit_depth: u8,
tx_mode_select: bool,
// r190: optional inter-arm context. See
// [`Self::decode_block_syntax`]'s `inter_ctx` parameter for
// the contract.
inter_ctx: Option<&InterFrameContext>,
// r286: §5.9.12 quantiser state + §5.9.21 `reduced_tx_set`. See
// [`Self::decode_block_syntax`]'s `quant` / `reduced_tx_set`
// parameters for the contract — threaded unchanged into every
// leaf so the §5.11.47 `transform_type()` guard reflects the
// real frame quantiser.
quant: &QuantizerParams,
reduced_tx_set: bool,
) -> Result<(), crate::Error> {
// §5.11.4 line 1: `if ( r >= MiRows || c >= MiCols ) return 0`.
if r >= self.mi_rows || c >= self.mi_cols {
return Ok(());
}
if b_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let num4x4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let half_block4x4 = num4x4 >> 1;
let quarter_block4x4 = half_block4x4 >> 1;
let has_rows = (r + half_block4x4) < self.mi_rows;
let has_cols = (c + half_block4x4) < self.mi_cols;
// §5.11.4 partition decode (mirrors `decode_partition`).
let partition = if b_size < BLOCK_8X8 {
PARTITION_NONE
} else {
let bsl = MI_WIDTH_LOG2[b_size] as u32;
let pctx = self.partition_ctx_for(r, c, bsl);
if has_rows && has_cols {
let cdf = cdfs
.partition_cdf(bsl, pctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
decoder.read_symbol(cdf)? as usize
} else if has_cols {
let cdf_row = cdfs
.partition_cdf(bsl, pctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let mut bin = split_or_horz_cdf(cdf_row, b_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let s = decoder.read_symbol(&mut bin)?;
if s == 0 {
PARTITION_HORZ
} else {
PARTITION_SPLIT
}
} else if has_rows {
let cdf_row = cdfs
.partition_cdf(bsl, pctx)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let mut bin = split_or_vert_cdf(cdf_row, b_size)
.ok_or(crate::Error::PartitionWalkOutOfRange)?;
let s = decoder.read_symbol(&mut bin)?;
if s == 0 {
PARTITION_VERT
} else {
PARTITION_SPLIT
}
} else {
PARTITION_SPLIT
}
};
let sub_size =
partition_subsize(partition, b_size).ok_or(crate::Error::PartitionWalkOutOfRange)?;
let split_size = partition_subsize(PARTITION_SPLIT, b_size);
// Helper closure-style call site: every `decode_block( r, c,
// sz )` in the §5.11.4 pseudocode routes through
// `decode_block_syntax`.
macro_rules! db {
($self:ident, $r:expr, $c:expr, $sz:expr) => {
$self.decode_block_syntax(
decoder,
cdfs,
$r,
$c,
$sz,
frame_is_intra,
subsampling_x,
subsampling_y,
num_planes,
seg_id_pre_skip,
segmentation_enabled,
seg_skip,
last_active_seg_id,
lossless_array,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
allow_screen_content_tools,
enable_filter_intra,
bit_depth,
tx_mode_select,
inter_ctx,
quant,
reduced_tx_set,
)?
};
}
match partition {
PARTITION_NONE => {
db!(self, r, c, sub_size);
}
PARTITION_HORZ => {
db!(self, r, c, sub_size);
if has_rows {
db!(self, r + half_block4x4, c, sub_size);
}
}
PARTITION_VERT => {
db!(self, r, c, sub_size);
if has_cols {
db!(self, r, c + half_block4x4, sub_size);
}
}
PARTITION_SPLIT => {
self.decode_partition_syntax(
decoder,
cdfs,
r,
c,
sub_size,
frame_is_intra,
subsampling_x,
subsampling_y,
num_planes,
seg_id_pre_skip,
segmentation_enabled,
seg_skip,
last_active_seg_id,
lossless_array,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
allow_screen_content_tools,
enable_filter_intra,
bit_depth,
tx_mode_select,
inter_ctx,
quant,
reduced_tx_set,
)?;
self.decode_partition_syntax(
decoder,
cdfs,
r,
c + half_block4x4,
sub_size,
frame_is_intra,
subsampling_x,
subsampling_y,
num_planes,
seg_id_pre_skip,
segmentation_enabled,
seg_skip,
last_active_seg_id,
lossless_array,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
allow_screen_content_tools,
enable_filter_intra,
bit_depth,
tx_mode_select,
inter_ctx,
quant,
reduced_tx_set,
)?;
self.decode_partition_syntax(
decoder,
cdfs,
r + half_block4x4,
c,
sub_size,
frame_is_intra,
subsampling_x,
subsampling_y,
num_planes,
seg_id_pre_skip,
segmentation_enabled,
seg_skip,
last_active_seg_id,
lossless_array,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
allow_screen_content_tools,
enable_filter_intra,
bit_depth,
tx_mode_select,
inter_ctx,
quant,
reduced_tx_set,
)?;
self.decode_partition_syntax(
decoder,
cdfs,
r + half_block4x4,
c + half_block4x4,
sub_size,
frame_is_intra,
subsampling_x,
subsampling_y,
num_planes,
seg_id_pre_skip,
segmentation_enabled,
seg_skip,
last_active_seg_id,
lossless_array,
coded_lossless,
enable_cdef,
allow_intrabc,
cdef_bits,
read_deltas,
use_128x128_superblock,
delta_q_res,
delta_lf_present,
delta_lf_multi,
mono_chrome,
delta_lf_res,
allow_screen_content_tools,
enable_filter_intra,
bit_depth,
tx_mode_select,
inter_ctx,
quant,
reduced_tx_set,
)?;
}
PARTITION_HORZ_A => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
db!(self, r, c, split);
db!(self, r, c + half_block4x4, split);
db!(self, r + half_block4x4, c, sub_size);
}
PARTITION_HORZ_B => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
db!(self, r, c, sub_size);
db!(self, r + half_block4x4, c, split);
db!(self, r + half_block4x4, c + half_block4x4, split);
}
PARTITION_VERT_A => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
db!(self, r, c, split);
db!(self, r + half_block4x4, c, split);
db!(self, r, c + half_block4x4, sub_size);
}
PARTITION_VERT_B => {
let split = split_size.ok_or(crate::Error::PartitionWalkOutOfRange)?;
db!(self, r, c, sub_size);
db!(self, r, c + half_block4x4, split);
db!(self, r + half_block4x4, c + half_block4x4, split);
}
// PARTITION_HORZ_4: the spec writes `r +
// quarterBlock4x4 * { 0, 1, 2, 3 }`; clippy folds the
// `* 0` and `* 1` cases, so the body is shown with the
// folded forms (mirrors the [`Self::decode_partition`]
// sibling).
PARTITION_HORZ_4 => {
db!(self, r, c, sub_size);
db!(self, r + quarter_block4x4, c, sub_size);
db!(self, r + quarter_block4x4 * 2, c, sub_size);
if r + quarter_block4x4 * 3 < self.mi_rows {
db!(self, r + quarter_block4x4 * 3, c, sub_size);
}
}
// The spec's `else { ... }` arm (PARTITION_VERT_4 — the
// tenth and final partition value). Same clippy-folded
// pattern as PARTITION_HORZ_4.
PARTITION_VERT_4 => {
db!(self, r, c, sub_size);
db!(self, r, c + quarter_block4x4, sub_size);
db!(self, r, c + quarter_block4x4 * 2, sub_size);
if c + quarter_block4x4 * 3 < self.mi_cols {
db!(self, r, c + quarter_block4x4 * 3, sub_size);
}
}
_ => return Err(crate::Error::PartitionWalkOutOfRange),
}
Ok(())
}
/// `decode_tile()` per §5.11.2 (av1-spec p.59-60) — the superblock
/// loop that drives [`Self::decode_partition_syntax`] across an
/// entire tile, assembling the per-plane `CurrFrame[plane]`
/// reconstruction as it goes.
///
/// ```text
/// decode_tile() {
/// clear_above_context()
/// ... DeltaLF / RefSgrXqd / RefLrWiener init ...
/// sbSize = use_128x128_superblock ? BLOCK_128X128 : BLOCK_64X64
/// sbSize4 = Num_4x4_Blocks_Wide[ sbSize ]
/// for ( r = MiRowStart; r < MiRowEnd; r += sbSize4 ) {
/// clear_left_context()
/// for ( c = MiColStart; c < MiColEnd; c += sbSize4 ) {
/// ReadDeltas = delta_q_present
/// clear_cdef( r, c )
/// clear_block_decoded_flags( r, c, sbSize4 )
/// read_lr( r, c, sbSize )
/// decode_partition( r, c, sbSize )
/// }
/// }
/// }
/// ```
///
/// This entry covers the pixel-producing core of the loop: the
/// per-superblock §5.11.3 [`Self::clear_block_decoded_flags`] reset
/// followed by the §5.11.4 [`Self::decode_partition_syntax`] walk of
/// the superblock's partition tree at `(r, c)`. Each leaf
/// reconstructs its intra prediction + residual into
/// `CurrFrame[plane]` (via [`Self::predict_intra_into_curr_frame`] +
/// the §7.12.3 step-3 merge), so after the loop the walker's
/// per-plane buffers (read via [`Self::curr_frame`]) hold the
/// reconstructed tile samples (pre-§7.14 loop-filter / §7.15 CDEF /
/// §7.17 loop-restoration post passes).
///
/// The §5.11.2 in-loop bookkeeping the spec interleaves —
/// `clear_above_context` / `clear_left_context` (§8.3.1 entropy
/// neighbour reset), `DeltaLF` / `RefSgrXqd` / `RefLrWiener`
/// initialisation, `clear_cdef`, `read_lr` — is **not** driven here:
/// the entropy neighbour arrays are the `TileCdfContext`'s
/// responsibility, `clear_cdef` is a no-op for the §7.15-disabled
/// path, and `read_lr` consumes loop-restoration syntax that the
/// post-pass owns. A frame that signals those tools active needs the
/// caller to interleave the corresponding per-SB reads; the partition
/// walk itself is faithful to §5.11.4 regardless.
///
/// `read_deltas` is the §5.11.2 `ReadDeltas = delta_q_present`
/// initialisation applied at each superblock (the per-block
/// §5.11.37 `read_delta_qindex` consumes + clears it).
///
/// Propagates any [`crate::Error`] the first failing partition /
/// block / coefficient read surfaces; the partition state stamped
/// before the short-circuit stays observable on the grid accessors.
#[allow(clippy::too_many_arguments)]
pub fn decode_tile_syntax(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
params: &TileDecodeParams<'_>,
inter_ctx: Option<&InterFrameContext>,
quant: &QuantizerParams,
read_deltas: bool,
) -> Result<(), crate::Error> {
self.decode_tile_syntax_with_lr(decoder, cdfs, params, None, inter_ctx, quant, read_deltas)
}
/// [`Self::decode_tile_syntax`] with the §5.11.2 `read_lr( r, c,
/// sbSize )` per-superblock loop-restoration read interleaved.
///
/// When `lr_params` is `Some(_)` and any plane's
/// `FrameRestorationType != RESTORE_NONE`, the §5.11.57 unit-window
/// walk consumes the per-unit §5.11.58 `read_lr_unit` syntax at
/// each superblock **before** the §5.11.4 partition walk — the
/// spec's `read_lr` / `decode_partition` ordering inside the
/// §5.11.2 superblock loop. Omitting the read on a frame that
/// signals restoration would desynchronise the arithmetic decoder;
/// passing `None` (or an all-`RESTORE_NONE` `lr_params`) preserves
/// the historical behaviour bit-for-bit. The decoded units land on
/// the walker's per-plane §7.17 `LrType` / `LrWiener` / `LrSgrSet`
/// / `LrSgrXqd` grids consumed by
/// [`Self::loop_restore_frame_from_grid`].
#[allow(clippy::too_many_arguments)]
pub fn decode_tile_syntax_with_lr(
&mut self,
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
params: &TileDecodeParams<'_>,
lr_params: Option<&LrParams>,
inter_ctx: Option<&InterFrameContext>,
quant: &QuantizerParams,
read_deltas: bool,
) -> Result<(), crate::Error> {
// §5.11.2: `sbSize = use_128x128_superblock ? BLOCK_128X128 :
// BLOCK_64X64`; `sbSize4 = Num_4x4_Blocks_Wide[ sbSize ]`.
let sb_size = if params.use_128x128_superblock {
BLOCK_128X128
} else {
BLOCK_64X64
};
let sb_size4 = NUM_4X4_BLOCKS_WIDE[sb_size] as u32;
if sb_size4 == 0 {
return Err(crate::Error::PartitionWalkOutOfRange);
}
// §5.5.2 `enable_intra_edge_filter` is frame-scope; cache it on
// the walker so the §7.11.2.1 prediction write (deep inside the
// §5.11.34 residual dispatch) can consult it without threading
// the bit through every layer of the §5.11.4/§5.11.5 walk.
self.enable_intra_edge_filter = params.enable_intra_edge_filter;
let mi_row_start = self.geometry.mi_row_start;
let mi_row_end = self.geometry.mi_row_end;
let mi_col_start = self.geometry.mi_col_start;
let mi_col_end = self.geometry.mi_col_end;
// §5.11.2 `for ( r = MiRowStart; r < MiRowEnd; r += sbSize4 )`.
let mut r = mi_row_start;
while r < mi_row_end {
// §5.11.2 `clear_left_context()` — the row-indexed left
// entropy contexts reset at every superblock-row entry (a
// no-op on the first row of a freshly constructed / freshly
// `begin_tile`-d walker, whose arrays are already zero).
self.clear_txb_left_context();
// §5.11.2 `for ( c = MiColStart; c < MiColEnd; c += sbSize4 )`.
let mut c = mi_col_start;
while c < mi_col_end {
// §5.11.3 `clear_block_decoded_flags( r, c, sbSize4 )` —
// reset the §6.10.3 per-superblock availability grid.
self.clear_block_decoded_flags(
r,
c,
sb_size4,
params.num_planes,
params.subsampling_x,
params.subsampling_y,
);
// §5.11.2 `ReadDeltas = delta_q_present` — re-armed at
// every superblock; the first block's §5.11.7/§5.11.18
// `ReadDeltas = 0` line disarms it (r405: the walker
// previously read the §5.11.12/§5.11.13 delta symbols
// at EVERY leaf, desynchronising the arithmetic
// decoder from the second leaf of the first
// delta-signalling superblock onwards).
self.read_deltas_pending = true;
// §5.11.2 `read_lr( r, c, sbSize )` — the §5.11.57
// per-superblock loop-restoration unit reads, ahead of
// the partition walk.
if let Some(lr) = lr_params {
// `read_lr` itself skips RESTORE_NONE planes, so an
// all-NONE `lr_params` consumes no symbols.
self.read_lr(decoder, cdfs, r, c, sb_size, lr)?;
}
// §5.11.4 `decode_partition( r, c, sbSize )`.
self.decode_partition_syntax(
decoder,
cdfs,
r,
c,
sb_size,
params.frame_is_intra,
params.subsampling_x,
params.subsampling_y,
params.num_planes,
params.seg_id_pre_skip,
params.segmentation_enabled,
¶ms.seg_skip,
params.last_active_seg_id,
params.lossless_array,
params.coded_lossless,
params.enable_cdef,
params.allow_intrabc,
params.cdef_bits,
read_deltas,
params.use_128x128_superblock,
params.delta_q_res,
params.delta_lf_present,
params.delta_lf_multi,
params.mono_chrome,
params.delta_lf_res,
params.allow_screen_content_tools,
params.enable_filter_intra,
params.bit_depth,
params.tx_mode_select,
inter_ctx,
quant,
params.reduced_tx_set,
)?;
c += sb_size4;
}
r += sb_size4;
}
Ok(())
}
/// View of the §5.11.5 / §5.11.31 / §7.10 `Mvs[][][..][..]` grid
/// after the walk. Indexed row-major with the list/comp innermost:
/// `mvs()[ ((r * MiCols + c) * 2 + list) * 2 + comp ]`. Cells that
/// no §5.11.31 `assign_mv` call has populated stay at `0`; cells
/// inside a decoded inter-block's `bh4 * bw4` footprint carry the
/// per-list / per-component motion vector (the spec writes
/// `Mvs[r + y][c + x][refList] = Mv[refList]` over the footprint).
/// The pre-fill `0` value is unobservable on the conformant path:
/// every reader gates on `IsInters[mvRow][mvCol] == 1`, and
/// `IsInters[]` starts at `0` for unwritten cells.
#[must_use]
pub fn mvs(&self) -> &[i16] {
&self.mvs
}
/// Helper to read `Mvs[ r ][ c ][ list ][ comp ]` for the §7.10.2
/// stack-search neighbour walks. Returns `0` for out-of-grid
/// coordinates (matching the spec's pre-fill, which the §7.10.2.7
/// `IsInters[mvRow][mvCol] == 0 ⇒ return` short-circuit ensures is
/// never observed via the conformant path).
#[inline]
fn mv_at(&self, r: i32, c: i32, list: usize, comp: usize) -> i16 {
debug_assert!(list < 2, "Mvs list must be 0 or 1");
debug_assert!(comp < 2, "Mvs comp must be 0 or 1");
if r < 0 || c < 0 {
return 0;
}
let (r, c) = (r as u32, c as u32);
if r >= self.mi_rows || c >= self.mi_cols {
return 0;
}
let cell = (r * self.mi_cols + c) as usize;
self.mvs[(cell * 2 + list) * 2 + comp]
}
/// Test-only setter to seed a synthetic inter-block neighbour for
/// §7.10.2 search-stack coverage. Stamps `IsInters[r][c] = 1`,
/// `RefFrames[r][c][0..2]`, `YModes[r][c]`, `Mvs[r][c][0..2][0..2]`
/// and `MiSizes[r][c]` over the `bh4 * bw4` footprint anchored at
/// `(mi_row, mi_col)` so a subsequent [`Self::find_mv_stack`] call
/// observes the cell as a decoded inter neighbour. Out-of-grid
/// cells inside the footprint are silently skipped (matching the
/// §5.11.5 grid-fill convention). Caller-bug guards on `mi_size`
/// are deferred to debug-only asserts since this helper is gated
/// behind `#[cfg(any(test, feature = "test-helpers"))]`.
///
/// `mvs[list][comp]` matches the §7.10 component ordering:
/// `comp = 0` is the vertical (row) component, `comp = 1` is the
/// horizontal (column) component.
#[cfg(test)]
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)]
pub(crate) fn stamp_inter_neighbour(
&mut self,
mi_row: u32,
mi_col: u32,
mi_size: usize,
ref_frame: [i8; 2],
y_mode: u8,
mvs: [[i16; 2]; 2],
) {
debug_assert!(mi_size < BLOCK_SIZES);
let bw4 = NUM_4X4_BLOCKS_WIDE[mi_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[mi_size] as u32;
for dr in 0..bh4 {
let rr = mi_row + dr;
if rr >= self.mi_rows {
break;
}
for dc in 0..bw4 {
let cc = mi_col + dc;
if cc >= self.mi_cols {
break;
}
let cell = (rr * self.mi_cols + cc) as usize;
self.is_inters[cell] = 1;
self.ref_frames[cell * 2] = ref_frame[0];
self.ref_frames[cell * 2 + 1] = ref_frame[1];
self.y_modes[cell] = y_mode;
self.mi_sizes[cell] = mi_size;
// §5.11.5 `Mvs[r + y][c + x][refList] = Mv[refList]`.
self.mvs[(cell * 2) * 2] = mvs[0][0];
self.mvs[(cell * 2) * 2 + 1] = mvs[0][1];
self.mvs[(cell * 2 + 1) * 2] = mvs[1][0];
self.mvs[(cell * 2 + 1) * 2 + 1] = mvs[1][1];
}
}
}
/// `find_mv_stack( isCompound )` per **§7.10.2** (av1-spec p.217) —
/// the motion-vector-stack derivation reached from the §5.11.23
/// `inter_block_mode_info()` body once the §5.11.25
/// `read_ref_frames()` prologue has committed `RefFrame[0..2]`.
///
/// Constructs and returns the [`FindMvStackResult`] aggregate, which
/// carries every output of the §7.10.2 process consumed by the
/// §5.11.23 post-`find_mv_stack` readers:
///
/// * `num_mv_found` — `NumMvFound`, in `0..=MAX_REF_MV_STACK_SIZE`.
/// * `new_mv_count` — `NewMvCount`, the number of NEWMV-class
/// candidates contributing to the close-match neighbourhood (used
/// by §7.10.2.14 to derive `numNew` for `NewMvContext`).
/// * `ref_stack_mv` — `RefStackMv[idx][list][comp]` with the
/// per-component motion-vector value in 1/8-luma-sample units.
/// * `weight_stack` — `WeightStack[idx]` after the §7.10.2 step 15
/// `REF_CAT_LEVEL` bonus is applied to the close-match prefix.
/// * `global_mvs` — `GlobalMvs[list]` per §7.10.2.1 setup, used by
/// the §5.11.23 GLOBALMV / GLOBAL_GLOBALMV arms.
/// * `zero_mv_context`, `new_mv_context`, `ref_mv_context` — the
/// §7.10.2.14 ctx-derivation outputs read by the §8.3.2
/// `new_mv` / `zero_mv` / `ref_mv` CDF lookups.
/// * `drl_ctx_stack` — `DrlCtxStack[idx]` per §7.10.2.14, in
/// `0..DRL_MODE_CONTEXTS`.
///
/// ## r205 scope
///
/// This round closes the §7.10.2 driver by wiring **§7.10.2.5
/// temporal scan** + **§7.10.2.6 temporal sample** at step 17.
/// The full surface now covers §7.10.2 steps 1-36, including
/// §7.10.2.1 (setup-global-mv), §7.10.2.2 (scan-row), §7.10.2.3
/// (scan-col), §7.10.2.4 (scan-point), §7.10.2.5 (temporal-scan),
/// §7.10.2.6 (temporal-sample), §7.10.2.7 (add-ref-mv-candidate),
/// §7.10.2.8 (search-stack), §7.10.2.9 (compound-search-stack),
/// §7.10.2.10 (lower-precision), §7.10.2.11 (sorting), §7.10.2.12
/// (extra-search) + §7.10.2.13 (add-extra-mv candidate), and
/// §7.10.2.14 (context + clamping).
///
/// The §7.10.2.5 / §7.10.2.6 step-17 body consults the caller's
/// `MotionFieldMvs` grid (sized for the current frame's
/// `MiRows × MiCols` per §7.9.1). On a freshly-initialised grid
/// (`MotionFieldMvs::new_invalid`), every slot carries the
/// `MFMV_INVALID` sentinel and the §7.10.2.6
/// `if (candMv[0] == -1 << 15) return` early-out fires for every
/// position — so callers that pass `use_ref_frame_mvs == true`
/// with an unprojected grid see the same observable output as
/// `use_ref_frame_mvs == false`.
///
/// Caller-bug guards (`Err(PartitionWalkOutOfRange)` with no
/// mutation):
/// * `sub_size >= BLOCK_SIZES`
/// * `mi_row >= MiRows` or `mi_col >= MiCols`
/// * `ref_frame[0] < INTRA_FRAME = 0` or `> ALTREF_FRAME = 7`
/// * `ref_frame[1] < NONE = -1` or `> ALTREF_FRAME = 7`
/// * `is_compound != (ref_frame[1] > INTRA_FRAME)` — the §5.11.23
/// isCompound derivation is the caller's responsibility, so a
/// mismatch is a caller bug.
///
/// ## Spec line-by-line mapping
///
/// 1-2: `NumMvFound = 0`, `NewMvCount = 0`.
/// 3-4: `GlobalMvs[0..numLists]` via §7.10.2.1 setup-global-mv.
/// 5: `FoundMatch = 0`.
/// 6-7: §7.10.2.2 `scan_row(-1)`; `foundAboveMatch = FoundMatch`.
/// 8-9: §7.10.2.3 `scan_col(-1)`; `foundLeftMatch = FoundMatch`.
/// 10-11: §7.10.2.4 `scan_point(-1, bw4)` if `Max(bw4, bh4) <= 16`;
/// merge into `foundAboveMatch`.
/// 12-14: `CloseMatches`, `numNearest`, `numNew` snapshot.
/// 15: `WeightStack[0..numNearest] += REF_CAT_LEVEL`.
/// 16: `ZeroMvContext = 0`.
/// 17: §7.10.2.5 temporal scan — **deferred to next arc**.
/// 18-20: §7.10.2.4 `scan_point(-1, -1)` (top-left); merge into
/// `foundAboveMatch`; reset `FoundMatch = 0`.
/// 21-23: §7.10.2.2 `scan_row(-3)`; merge / reset.
/// 24-26: §7.10.2.3 `scan_col(-3)`; merge / reset.
/// 27-29: §7.10.2.2 `scan_row(-5)` if `bh4 > 1`; merge / reset.
/// 30-31: §7.10.2.3 `scan_col(-5)` if `bw4 > 1`; merge.
/// 32: `TotalMatches = foundAboveMatch + foundLeftMatch`.
/// 33-34: §7.10.2.11 sort `[0..numNearest)` and
/// `[numNearest..NumMvFound)`.
/// 35: §7.10.2.12 extra-search if `NumMvFound < 2`.
/// 36: §7.10.2.14 context + clamping.
///
/// The result honours the §3 `MAX_REF_MV_STACK_SIZE = 8` cap on
/// `NumMvFound`; any candidate beyond the cap is dropped per the
/// §7.10.2.8 / §7.10.2.9 "process has no effect" line.
#[allow(clippy::too_many_arguments)]
pub fn find_mv_stack(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
ref_frame: [i32; 2],
is_compound: bool,
use_ref_frame_mvs: bool,
gm_type: [i32; 8],
gm_params: [[i32; 6]; 8],
ref_frame_sign_bias: [i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
motion_field_mvs: &MotionFieldMvs,
) -> Result<FindMvStackResult, crate::Error> {
// §7.10 caller-bug guards.
if sub_size >= BLOCK_SIZES {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if mi_row >= self.mi_rows || mi_col >= self.mi_cols {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(0..=7).contains(&ref_frame[0]) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if !(-1..=7).contains(&ref_frame[1]) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
if is_compound != (ref_frame[1] > 0) {
return Err(crate::Error::PartitionWalkOutOfRange);
}
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as i32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as i32;
let num_lists: usize = if is_compound { 2 } else { 1 };
// §7.10.2.1 setup-global-mv for slot 0 (and slot 1 if compound).
let mut global_mvs: [[i32; 2]; 2] = [[0, 0], [0, 0]];
global_mvs[0] = setup_global_mv(
mi_row,
mi_col,
sub_size,
ref_frame[0],
&gm_type,
&gm_params,
allow_high_precision_mv,
force_integer_mv,
);
if is_compound {
global_mvs[1] = setup_global_mv(
mi_row,
mi_col,
sub_size,
ref_frame[1],
&gm_type,
&gm_params,
allow_high_precision_mv,
force_integer_mv,
);
}
// Stack state across the §7.10.2 step body.
let mut state = MvStackState::new();
// 5-7: scan_row(-1); foundAboveMatch = FoundMatch.
state.found_match = 0;
self.scan_row(
mi_row,
mi_col,
sub_size,
-1,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
let mut found_above_match = state.found_match;
state.found_match = 0;
// 8-9: scan_col(-1); foundLeftMatch = FoundMatch.
self.scan_col(
mi_row,
mi_col,
sub_size,
-1,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
let mut found_left_match = state.found_match;
state.found_match = 0;
// 10-11: scan_point(-1, bw4) if Max(bw4, bh4) <= 16.
if core::cmp::max(bw4, bh4) <= 16 {
self.scan_point(
mi_row,
mi_col,
-1,
bw4,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
if state.found_match == 1 {
found_above_match = 1;
}
}
state.found_match = 0;
// 12-14: CloseMatches, numNearest, numNew snapshot.
let close_matches = found_above_match + found_left_match;
let num_nearest = state.num_mv_found;
let num_new = state.new_mv_count;
// 15: WeightStack[0..numNearest] += REF_CAT_LEVEL.
if num_nearest > 0 {
for idx in 0..num_nearest as usize {
state.weight_stack[idx] = state.weight_stack[idx].saturating_add(REF_CAT_LEVEL);
}
}
// 16: ZeroMvContext = 0 (already initialised by MvStackState::new).
// 17: §7.10.2.5 temporal scan — only invoked when the frame
// opted into per-block motion-field reuse (§5.9.20
// `use_ref_frame_mvs == 1`). The §7.10.2.6 body
// short-circuits on every cell with the `MFMV_INVALID`
// sentinel (which a freshly-initialised
// `MotionFieldMvs::new_invalid` grid carries everywhere), so
// a caller that opts in without populating the grid is
// observably equivalent to a caller that opts out.
if use_ref_frame_mvs {
self.temporal_scan(
mi_row,
mi_col,
sub_size,
is_compound,
ref_frame,
&global_mvs,
allow_high_precision_mv,
force_integer_mv,
motion_field_mvs,
&mut state,
);
}
// 18-19: scan_point(-1, -1) top-left; merge into foundAboveMatch.
self.scan_point(
mi_row,
mi_col,
-1,
-1,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
if state.found_match == 1 {
found_above_match = 1;
}
// 20: FoundMatch = 0.
state.found_match = 0;
// 21-23: scan_row(-3); merge / reset.
self.scan_row(
mi_row,
mi_col,
sub_size,
-3,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
if state.found_match == 1 {
found_above_match = 1;
}
state.found_match = 0;
// 24-26: scan_col(-3); merge / reset.
self.scan_col(
mi_row,
mi_col,
sub_size,
-3,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
if state.found_match == 1 {
found_left_match = 1;
}
state.found_match = 0;
// 27-29: scan_row(-5) if bh4 > 1; merge / reset.
if bh4 > 1 {
self.scan_row(
mi_row,
mi_col,
sub_size,
-5,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
if state.found_match == 1 {
found_above_match = 1;
}
state.found_match = 0;
}
// 30-31: scan_col(-5) if bw4 > 1; merge.
if bw4 > 1 {
self.scan_col(
mi_row,
mi_col,
sub_size,
-5,
is_compound,
ref_frame,
&global_mvs,
&gm_type,
allow_high_precision_mv,
force_integer_mv,
&mut state,
);
if state.found_match == 1 {
found_left_match = 1;
}
}
// 32: TotalMatches.
let total_matches = found_above_match + found_left_match;
// 33-34: §7.10.2.11 sort the two halves [0..num_nearest) and
// [num_nearest..NumMvFound).
sort_stack_segment(&mut state, 0, num_nearest as usize, is_compound);
let end_after_nearest = state.num_mv_found as usize;
sort_stack_segment(
&mut state,
num_nearest as usize,
end_after_nearest,
is_compound,
);
// 35: §7.10.2.12 extra search if NumMvFound < 2.
if state.num_mv_found < 2 {
self.extra_search(
mi_row,
mi_col,
sub_size,
is_compound,
ref_frame,
&global_mvs,
&ref_frame_sign_bias,
&mut state,
);
}
// 36: §7.10.2.14 context + clamping.
let mut drl_ctx_stack = [0u32; MAX_REF_MV_STACK_SIZE];
let num_mv_found = state.num_mv_found as usize;
#[allow(clippy::needless_range_loop)]
for idx in 0..num_mv_found {
let mut z: u32 = 0;
if (idx + 1) < num_mv_found {
let w0 = state.weight_stack[idx];
let w1 = state.weight_stack[idx + 1];
if w0 >= REF_CAT_LEVEL {
if w1 < REF_CAT_LEVEL {
z = 1;
}
} else {
z = 2;
}
}
drl_ctx_stack[idx] = z;
}
// §7.10.2.14 clamp.
let bw_samples = (NUM_4X4_BLOCKS_WIDE[sub_size] as i32) * 4;
let bh_samples = (NUM_4X4_BLOCKS_HIGH[sub_size] as i32) * 4;
let row_border = MV_BORDER + bh_samples * 8;
let col_border = MV_BORDER + bw_samples * 8;
for list in 0..num_lists {
for idx in 0..state.num_mv_found as usize {
let mv = state.ref_stack_mv[idx][list];
state.ref_stack_mv[idx][list][0] =
clamp_mv_row(mv[0], row_border, self.mi_rows as i32, bh4, mi_row as i32);
state.ref_stack_mv[idx][list][1] =
clamp_mv_col(mv[1], col_border, self.mi_cols as i32, bw4, mi_col as i32);
}
}
// §7.10.2.14 NewMvContext / RefMvContext derivation.
let (new_mv_ctx, ref_mv_ctx) = match close_matches {
0 => (core::cmp::min(total_matches, 1), total_matches),
1 => (3 - core::cmp::min(num_new, 1), 2 + total_matches),
_ => (5 - core::cmp::min(num_new, 1), 5),
};
Ok(FindMvStackResult {
num_mv_found: state.num_mv_found,
new_mv_count: state.new_mv_count,
ref_stack_mv: state.ref_stack_mv,
weight_stack: state.weight_stack,
global_mvs,
new_mv_context: new_mv_ctx,
ref_mv_context: ref_mv_ctx,
zero_mv_context: state.zero_mv_context,
drl_ctx_stack,
close_matches,
total_matches,
num_nearest,
num_new,
})
}
/// §7.10.2.2 scan_row(deltaRow). Walks `end4 = Min(Min(bw4,
/// MiCols - MiCol), 16)` 4x4-cell positions horizontally above
/// the block, invoking `add_ref_mv_candidate` for each in-tile
/// neighbour. The §7.10.2.2 `deltaCol` adjustments and
/// `useStep16` stride land alongside the §7.10.2.2 body proper.
#[allow(clippy::too_many_arguments)]
fn scan_row(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
delta_row: i32,
is_compound: bool,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
gm_type: &[i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
state: &mut MvStackState,
) {
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as i32;
let mi_cols_remaining = (self.mi_cols as i32) - (mi_col as i32);
let end4 = core::cmp::min(core::cmp::min(bw4, mi_cols_remaining), 16);
let mut delta_row = delta_row;
let mut delta_col: i32 = 0;
let use_step16 = bw4 >= 16;
if delta_row.abs() > 1 {
delta_row += (mi_row as i32) & 1;
delta_col = 1 - ((mi_col as i32) & 1);
}
let mut i: i32 = 0;
while i < end4 {
let mv_row = (mi_row as i32) + delta_row;
let mv_col = (mi_col as i32) + delta_col + i;
if !self.geometry.is_inside(mv_row, mv_col) {
break;
}
let neighbour_size = self.mi_size_at(mv_row, mv_col);
// §5.11.4 unfilled cell ⇒ BLOCK_INVALID. Fall back to a
// 4x4-wide step so the walk still advances through
// not-yet-decoded cells (the §7.10.2.7 gate on `IsInters`
// will skip the candidate). The §5.11.4 walker fills
// `MiSizes[]` for every decoded leaf before §5.11.23 fires,
// so this fallback only matters in tests that consult an
// unwritten grid.
let neighbour_width4 = if neighbour_size < BLOCK_SIZES {
NUM_4X4_BLOCKS_WIDE[neighbour_size] as i32
} else {
1
};
let mut len = core::cmp::min(bw4, neighbour_width4);
if delta_row.abs() > 1 {
len = core::cmp::max(2, len);
}
if use_step16 {
len = core::cmp::max(4, len);
}
let weight = (len as u32) * 2;
self.add_ref_mv_candidate(
mv_row as u32,
mv_col as u32,
is_compound,
weight,
ref_frame,
global_mvs,
gm_type,
allow_high_precision_mv,
force_integer_mv,
state,
);
i += len;
}
}
/// §7.10.2.3 scan_col(deltaCol). Vertical mirror of
/// [`Self::scan_row`].
#[allow(clippy::too_many_arguments)]
fn scan_col(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
delta_col: i32,
is_compound: bool,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
gm_type: &[i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
state: &mut MvStackState,
) {
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as i32;
let mi_rows_remaining = (self.mi_rows as i32) - (mi_row as i32);
let end4 = core::cmp::min(core::cmp::min(bh4, mi_rows_remaining), 16);
let mut delta_row: i32 = 0;
let mut delta_col = delta_col;
let use_step16 = bh4 >= 16;
if delta_col.abs() > 1 {
delta_row = 1 - ((mi_row as i32) & 1);
delta_col += (mi_col as i32) & 1;
}
let mut i: i32 = 0;
while i < end4 {
let mv_row = (mi_row as i32) + delta_row + i;
let mv_col = (mi_col as i32) + delta_col;
if !self.geometry.is_inside(mv_row, mv_col) {
break;
}
let neighbour_size = self.mi_size_at(mv_row, mv_col);
let neighbour_height4 = if neighbour_size < BLOCK_SIZES {
NUM_4X4_BLOCKS_HIGH[neighbour_size] as i32
} else {
1
};
let mut len = core::cmp::min(bh4, neighbour_height4);
if delta_col.abs() > 1 {
len = core::cmp::max(2, len);
}
if use_step16 {
len = core::cmp::max(4, len);
}
let weight = (len as u32) * 2;
self.add_ref_mv_candidate(
mv_row as u32,
mv_col as u32,
is_compound,
weight,
ref_frame,
global_mvs,
gm_type,
allow_high_precision_mv,
force_integer_mv,
state,
);
i += len;
}
}
/// §7.10.2.4 scan_point(deltaRow, deltaCol). Single-cell candidate
/// at the corner offset; the §7.10.2.4 body gates on
/// `RefFrames[mvRow][mvCol][0] != INTRA_FRAME` (the "has been
/// written" check the spec phrases as "this checks that the
/// candidate location has been decoded"). Our pre-fill identity
/// for `RefFrames[]` is `[INTRA_FRAME = 0, NONE = -1]`, matching
/// the spec exactly.
#[allow(clippy::too_many_arguments)]
fn scan_point(
&self,
mi_row: u32,
mi_col: u32,
delta_row: i32,
delta_col: i32,
is_compound: bool,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
gm_type: &[i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
state: &mut MvStackState,
) {
let mv_row = (mi_row as i32) + delta_row;
let mv_col = (mi_col as i32) + delta_col;
let weight: u32 = 4;
if !self.geometry.is_inside(mv_row, mv_col) {
return;
}
// §7.10.2.4 "RefFrames[ mvRow ][ mvCol ][ 0 ] has been written
// for this frame (this checks that the candidate location has
// been decoded)". Decoded-ness is tracked on the `MiSizes[]`
// grid — every §5.11.5 leaf stamps its footprint, unwritten
// cells carry the `BLOCK_INVALID` pre-fill. (The historical
// `RefFrames[ .. ][ 0 ] != INTRA_FRAME` proxy also rejected
// DECODED intra-block-copy candidates, whose §5.11.7
// `RefFrame[ 0 ] = INTRA_FRAME` stamp equals the pre-fill —
// dropping the §7.10.2 step-10 top-right / step-18 top-left
// candidate from the intrabc PredMv chain.)
if mv_row < 0 || mv_col < 0 {
return;
}
if (mv_row as u32) >= self.mi_rows || (mv_col as u32) >= self.mi_cols {
return;
}
if self.mi_sizes[(mv_row as u32 * self.mi_cols + mv_col as u32) as usize] == BLOCK_INVALID {
return;
}
self.add_ref_mv_candidate(
mv_row as u32,
mv_col as u32,
is_compound,
weight,
ref_frame,
global_mvs,
gm_type,
allow_high_precision_mv,
force_integer_mv,
state,
);
}
/// §7.10.2.7 add_ref_mv_candidate. Gates on
/// `IsInters[mvRow][mvCol] == 1`, then dispatches to either
/// §7.10.2.8 search-stack (single) or §7.10.2.9 compound-search-
/// stack (compound), based on whether the neighbour's ref-frame
/// pair matches the current block's `RefFrame[]`.
#[allow(clippy::too_many_arguments)]
fn add_ref_mv_candidate(
&self,
mv_row: u32,
mv_col: u32,
is_compound: bool,
weight: u32,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
gm_type: &[i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
state: &mut MvStackState,
) {
if self.is_inter_at(mv_row as i32, mv_col as i32) == 0 {
return;
}
if !is_compound {
// §7.10.2.7 single-pred: for each candList in 0..2, if the
// candidate's RefFrames[..][candList] == RefFrame[0], run
// the search-stack process on that list.
for cand_list in 0..2 {
let cand_ref = self.ref_frame_at(mv_row as i32, mv_col as i32, cand_list) as i32;
if cand_ref == ref_frame[0] {
self.search_stack(
mv_row,
mv_col,
cand_list,
weight,
ref_frame,
global_mvs,
gm_type,
allow_high_precision_mv,
force_integer_mv,
state,
);
}
}
} else {
// §7.10.2.7 compound-pred: only fires when the candidate's
// RefFrames[0] == RefFrame[0] AND RefFrames[1] == RefFrame[1].
let cand0 = self.ref_frame_at(mv_row as i32, mv_col as i32, 0) as i32;
let cand1 = self.ref_frame_at(mv_row as i32, mv_col as i32, 1) as i32;
if cand0 == ref_frame[0] && cand1 == ref_frame[1] {
self.compound_search_stack(
mv_row,
mv_col,
weight,
ref_frame,
global_mvs,
allow_high_precision_mv,
force_integer_mv,
state,
);
}
}
}
/// §7.10.2.8 search-stack. Adds a single-prediction candidate MV to
/// the stack — either as a new slot or by accumulating weight on
/// an existing match.
#[allow(clippy::too_many_arguments)]
fn search_stack(
&self,
mv_row: u32,
mv_col: u32,
cand_list: usize,
weight: u32,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
gm_type: &[i32; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
state: &mut MvStackState,
) {
let cand_mode = self.y_mode_at(mv_row as i32, mv_col as i32);
// §7.10.2.8 candMv derivation:
// • If candMode in {GLOBALMV, GLOBAL_GLOBALMV} AND
// GmType[RefFrame[0]] > TRANSLATION AND large == 1,
// candMv = GlobalMvs[0].
// • Otherwise candMv = Mvs[mvRow][mvCol][candList].
//
// `large = Min(Block_Width[candSize], Block_Height[candSize])
// >= 8` — in 4x4-block units, `min(bw4, bh4) >= 2`. The
// GlobalMvs arm matters for warp-class global motion
// (ROTZOOM / AFFINE): §7.10.2.1 evaluates the model at the
// CURRENT block's centre, which differs from the neighbour's
// stamped `Mvs[]` (evaluated at the neighbour's centre). For
// IDENTITY / TRANSLATION models the two are byte-identical and
// the arm's `> TRANSLATION` gate skips it anyway.
let cand_size = self.mi_size_at(mv_row as i32, mv_col as i32);
let large = core::cmp::min(
NUM_4X4_BLOCKS_WIDE[cand_size],
NUM_4X4_BLOCKS_HIGH[cand_size],
) >= 2;
let ref0 = ref_frame[0];
let use_global = (cand_mode == MODE_GLOBALMV || cand_mode == MODE_GLOBAL_GLOBALMV)
&& (0..8).contains(&ref0)
&& gm_type[ref0 as usize] > GM_TYPE_TRANSLATION
&& large;
let cand_mv: [i32; 2] = if use_global {
global_mvs[0]
} else {
let v = self.mv_at(mv_row as i32, mv_col as i32, cand_list, 0) as i32;
let h = self.mv_at(mv_row as i32, mv_col as i32, cand_list, 1) as i32;
[v, h]
};
let cand_mv = lower_mv_precision(cand_mv, allow_high_precision_mv, force_integer_mv);
if has_newmv(cand_mode) {
state.new_mv_count = state.new_mv_count.saturating_add(1);
}
state.found_match = 1;
// Search for an existing match.
for idx in 0..state.num_mv_found as usize {
if state.ref_stack_mv[idx][0] == cand_mv {
state.weight_stack[idx] = state.weight_stack[idx].saturating_add(weight);
return;
}
}
if (state.num_mv_found as usize) < MAX_REF_MV_STACK_SIZE {
let idx = state.num_mv_found as usize;
state.ref_stack_mv[idx][0] = cand_mv;
state.weight_stack[idx] = weight;
state.num_mv_found += 1;
}
}
/// §7.10.2.9 compound-search-stack. Dual-MV variant of
/// [`Self::search_stack`].
#[allow(clippy::too_many_arguments)]
fn compound_search_stack(
&self,
mv_row: u32,
mv_col: u32,
weight: u32,
_ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
allow_high_precision_mv: bool,
force_integer_mv: bool,
state: &mut MvStackState,
) {
let mut cand_mvs: [[i32; 2]; 2] = [
[
self.mv_at(mv_row as i32, mv_col as i32, 0, 0) as i32,
self.mv_at(mv_row as i32, mv_col as i32, 0, 1) as i32,
],
[
self.mv_at(mv_row as i32, mv_col as i32, 1, 0) as i32,
self.mv_at(mv_row as i32, mv_col as i32, 1, 1) as i32,
],
];
let cand_mode = self.y_mode_at(mv_row as i32, mv_col as i32);
// §7.10.2.9: if candMode == GLOBAL_GLOBALMV, replace each list's
// MV with the per-ref global motion vector (when its GmType >
// TRANSLATION). The caller's `find_mv_stack` body computed
// `global_mvs[list]` already via setup-global-mv (which itself
// consulted gm_type); since setup-global-mv returns
// `[0, 0]` for `IDENTITY`/`TRANSLATION` only when the ref is
// INTRA_FRAME or IDENTITY (the §7.10.2.1 short-circuit), the
// `> TRANSLATION` test the spec gates on at this point is
// already baked into the global_mvs value. We replicate the
// spec literally by always replacing — this matches the
// observable output for any non-IDENTITY model.
if cand_mode == MODE_GLOBAL_GLOBALMV {
cand_mvs[0] = global_mvs[0];
cand_mvs[1] = global_mvs[1];
}
cand_mvs[0] = lower_mv_precision(cand_mvs[0], allow_high_precision_mv, force_integer_mv);
cand_mvs[1] = lower_mv_precision(cand_mvs[1], allow_high_precision_mv, force_integer_mv);
state.found_match = 1;
for idx in 0..state.num_mv_found as usize {
if state.ref_stack_mv[idx][0] == cand_mvs[0]
&& state.ref_stack_mv[idx][1] == cand_mvs[1]
{
state.weight_stack[idx] = state.weight_stack[idx].saturating_add(weight);
if has_newmv(cand_mode) {
state.new_mv_count = state.new_mv_count.saturating_add(1);
}
return;
}
}
if (state.num_mv_found as usize) < MAX_REF_MV_STACK_SIZE {
let idx = state.num_mv_found as usize;
state.ref_stack_mv[idx][0] = cand_mvs[0];
state.ref_stack_mv[idx][1] = cand_mvs[1];
state.weight_stack[idx] = weight;
state.num_mv_found += 1;
}
if has_newmv(cand_mode) {
state.new_mv_count = state.new_mv_count.saturating_add(1);
}
}
/// §7.10.2.12 extra search process. Two-pass partial-match scan
/// (above row, then left column) feeding §7.10.2.13
/// `add_extra_mv_candidate`, then a global-motion fill.
#[allow(clippy::too_many_arguments)]
fn extra_search(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
is_compound: bool,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
ref_frame_sign_bias: &[i32; 8],
state: &mut MvStackState,
) {
let w4_base = NUM_4X4_BLOCKS_WIDE[sub_size] as i32;
let h4_base = NUM_4X4_BLOCKS_HIGH[sub_size] as i32;
let mut w4 = core::cmp::min(16, w4_base);
let mut h4 = core::cmp::min(16, h4_base);
w4 = core::cmp::min(w4, (self.mi_cols as i32) - (mi_col as i32));
h4 = core::cmp::min(h4, (self.mi_rows as i32) - (mi_row as i32));
let num4x4 = core::cmp::min(w4, h4);
let mut ref_id_count: [usize; 2] = [0, 0];
let mut ref_diff_count: [usize; 2] = [0, 0];
let mut ref_id_mvs: [[[i32; 2]; 2]; 2] = Default::default();
let mut ref_diff_mvs: [[[i32; 2]; 2]; 2] = Default::default();
for pass in 0..2 {
let mut idx: i32 = 0;
while idx < num4x4 && state.num_mv_found < 2 {
let (mv_row, mv_col) = if pass == 0 {
((mi_row as i32) - 1, (mi_col as i32) + idx)
} else {
((mi_row as i32) + idx, (mi_col as i32) - 1)
};
if !self.geometry.is_inside(mv_row, mv_col) {
break;
}
self.add_extra_mv_candidate(
mv_row as u32,
mv_col as u32,
is_compound,
ref_frame,
ref_frame_sign_bias,
state,
&mut ref_id_count,
&mut ref_id_mvs,
&mut ref_diff_count,
&mut ref_diff_mvs,
);
let neighbour_size = self.mi_size_at(mv_row, mv_col);
let step = if neighbour_size < BLOCK_SIZES {
if pass == 0 {
NUM_4X4_BLOCKS_WIDE[neighbour_size] as i32
} else {
NUM_4X4_BLOCKS_HIGH[neighbour_size] as i32
}
} else {
1
};
idx += step.max(1);
}
}
if is_compound {
// Build combinedMvs[2][2] per the spec's §7.10.2.12 compound
// fill loop. The two-element nested arrays are sized by the
// spec literal; the index is meaningful as the spec's `idx`.
#[allow(clippy::needless_range_loop)]
let mut combined_mvs: [[[i32; 2]; 2]; 2] = Default::default();
for list in 0..2 {
let mut comp_count = 0;
#[allow(clippy::needless_range_loop)]
for idx in 0..ref_id_count[list] {
combined_mvs[comp_count][list] = ref_id_mvs[list][idx];
comp_count += 1;
if comp_count == 2 {
break;
}
}
let mut idx = 0;
while idx < ref_diff_count[list] && comp_count < 2 {
combined_mvs[comp_count][list] = ref_diff_mvs[list][idx];
comp_count += 1;
idx += 1;
}
while comp_count < 2 {
combined_mvs[comp_count][list] = global_mvs[list];
comp_count += 1;
}
}
if state.num_mv_found == 1 {
let nth = state.num_mv_found as usize;
if combined_mvs[0][0] == state.ref_stack_mv[0][0]
&& combined_mvs[0][1] == state.ref_stack_mv[0][1]
{
state.ref_stack_mv[nth][0] = combined_mvs[1][0];
state.ref_stack_mv[nth][1] = combined_mvs[1][1];
} else {
state.ref_stack_mv[nth][0] = combined_mvs[0][0];
state.ref_stack_mv[nth][1] = combined_mvs[0][1];
}
state.weight_stack[nth] = 2;
state.num_mv_found += 1;
} else {
// num_mv_found == 0 — emit both combined slots.
#[allow(clippy::needless_range_loop)]
for idx in 0..2 {
let nth = state.num_mv_found as usize;
if nth >= MAX_REF_MV_STACK_SIZE {
break;
}
state.ref_stack_mv[nth][0] = combined_mvs[idx][0];
state.ref_stack_mv[nth][1] = combined_mvs[idx][1];
state.weight_stack[nth] = 2;
state.num_mv_found += 1;
}
}
}
// §7.10.2.12 single-pred tail: "the candidates have already
// been added to RefStackMv, and this process simply extends
// with global motion candidates":
//
// for ( idx = NumMvFound; idx < 2; idx++ )
// RefStackMv[ idx ][ 0 ] = GlobalMvs[ 0 ]
//
// NumMvFound is NOT incremented (spec note), and the
// §7.10.2.14 clamp loop (`idx < NumMvFound`) never touches
// these slots — but §5.11.26 `assign_mv` DOES read them: a
// NEARESTMV block with an empty stack takes
// `PredMv[ 0 ] = RefStackMv[ 0 ][ 0 ]`, and a NEARMV block
// with `NumMvFound == 1` takes `RefStackMv[ RefMvIdx = 1 ]`.
// Omitting the fill left those reads at the zero prefill,
// which diverges whenever GlobalMvs[ 0 ] is nonzero (any
// TRANSLATION/ROTZOOM/AFFINE global-motion reference).
if !is_compound {
for idx in (state.num_mv_found as usize)..2 {
state.ref_stack_mv[idx][0] = global_mvs[0];
}
}
}
/// §7.10.2.13 add_extra_mv_candidate. Populates `RefIdMvs[list]` /
/// `RefDiffMvs[list]` (compound) or directly appends to
/// `RefStackMv` (single).
#[allow(clippy::too_many_arguments)]
fn add_extra_mv_candidate(
&self,
mv_row: u32,
mv_col: u32,
is_compound: bool,
ref_frame: [i32; 2],
ref_frame_sign_bias: &[i32; 8],
state: &mut MvStackState,
ref_id_count: &mut [usize; 2],
ref_id_mvs: &mut [[[i32; 2]; 2]; 2],
ref_diff_count: &mut [usize; 2],
ref_diff_mvs: &mut [[[i32; 2]; 2]; 2],
) {
if is_compound {
for cand_list in 0..2 {
let cand_ref = self.ref_frame_at(mv_row as i32, mv_col as i32, cand_list) as i32;
if cand_ref > 0 {
for list in 0..2 {
let mut cand_mv = [
self.mv_at(mv_row as i32, mv_col as i32, cand_list, 0) as i32,
self.mv_at(mv_row as i32, mv_col as i32, cand_list, 1) as i32,
];
if cand_ref == ref_frame[list] && ref_id_count[list] < 2 {
ref_id_mvs[list][ref_id_count[list]] = cand_mv;
ref_id_count[list] += 1;
} else if ref_diff_count[list] < 2 {
let cand_ref_bias = ref_frame_sign_bias[cand_ref as usize];
let list_ref_bias = ref_frame_sign_bias[ref_frame[list] as usize];
if cand_ref_bias != list_ref_bias {
cand_mv[0] = -cand_mv[0];
cand_mv[1] = -cand_mv[1];
}
ref_diff_mvs[list][ref_diff_count[list]] = cand_mv;
ref_diff_count[list] += 1;
}
}
}
}
} else {
for cand_list in 0..2 {
let cand_ref = self.ref_frame_at(mv_row as i32, mv_col as i32, cand_list) as i32;
if cand_ref > 0 {
let mut cand_mv = [
self.mv_at(mv_row as i32, mv_col as i32, cand_list, 0) as i32,
self.mv_at(mv_row as i32, mv_col as i32, cand_list, 1) as i32,
];
let cand_ref_bias = ref_frame_sign_bias[cand_ref as usize];
let base_ref_bias = ref_frame_sign_bias[ref_frame[0] as usize];
if cand_ref_bias != base_ref_bias {
cand_mv[0] = -cand_mv[0];
cand_mv[1] = -cand_mv[1];
}
let mut found_idx = state.num_mv_found as usize;
for idx in 0..state.num_mv_found as usize {
if state.ref_stack_mv[idx][0] == cand_mv {
found_idx = idx;
break;
}
}
if found_idx == state.num_mv_found as usize
&& (state.num_mv_found as usize) < MAX_REF_MV_STACK_SIZE
{
state.ref_stack_mv[found_idx][0] = cand_mv;
state.weight_stack[found_idx] = 2;
state.num_mv_found += 1;
}
}
}
}
}
/// §7.10.2.5 temporal scan process (av1-spec p.223 — the
/// `scan_blk` outer + extension loops). Walks the per-block 4×4
/// step grid `(bw4, bh4)` with the §7.10.2.5 stride
/// `stepW4 = (bw4 >= 16) ? 4 : 2`, `stepH4 = (bh4 >= 16) ? 4 : 2`,
/// invoking `add_tpl_ref_mv` (= §7.10.2.6 temporal-sample) for
/// each cell. Then, when the block satisfies the §7.10.2.5
/// `allowExtension` predicate (`BLOCK_8X8 <= block < BLOCK_64X64`
/// on both axes), the three §7.10.2.5 `tplSamplePos[3][2]` corner
/// offsets `{bh4, -2}, {bh4, bw4}, {bh4 - 2, bw4}` are tested
/// against the §7.10.2.5 `check_sb_border` 16×16-MI window.
///
/// The function is a no-op when the block's §7.10.2.5 step grid
/// produces zero candidate positions (which only happens for
/// degenerate `bw4 == 0` / `bh4 == 0` inputs the caller-bug
/// guards already exclude).
#[allow(clippy::too_many_arguments)]
fn temporal_scan(
&self,
mi_row: u32,
mi_col: u32,
sub_size: usize,
is_compound: bool,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
allow_high_precision_mv: bool,
force_integer_mv: bool,
motion_field_mvs: &MotionFieldMvs,
state: &mut MvStackState,
) {
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as i32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as i32;
let step_w4 = if bw4 >= 16 { 4 } else { 2 };
let step_h4 = if bh4 >= 16 { 4 } else { 2 };
// §7.10.2.5 `for (deltaRow = 0; deltaRow < Min(bh4, 16);
// deltaRow += stepH4) for (deltaCol = 0; deltaCol <
// Min(bw4, 16); deltaCol += stepW4) add_tpl_ref_mv(...)`.
let max_dr = core::cmp::min(bh4, 16);
let max_dc = core::cmp::min(bw4, 16);
let mut dr = 0i32;
while dr < max_dr {
let mut dc = 0i32;
while dc < max_dc {
self.temporal_sample(
mi_row,
mi_col,
dr,
dc,
is_compound,
ref_frame,
global_mvs,
allow_high_precision_mv,
force_integer_mv,
motion_field_mvs,
state,
);
dc += step_w4;
}
dr += step_h4;
}
// §7.10.2.5 `allowExtension = (bh4 in [BLOCK_8X8, BLOCK_64X64))
// && (bw4 in [BLOCK_8X8, BLOCK_64X64))`. The thresholds are
// 2 (BLOCK_8X8 in 4×4 units) and 16 (BLOCK_64X64 in 4×4
// units).
let bw8 = NUM_4X4_BLOCKS_WIDE[BLOCK_8X8] as i32;
let bh8 = NUM_4X4_BLOCKS_HIGH[BLOCK_8X8] as i32;
let bw64 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as i32;
let bh64 = NUM_4X4_BLOCKS_HIGH[BLOCK_64X64] as i32;
let allow_extension = bh4 >= bh8 && bh4 < bh64 && bw4 >= bw8 && bw4 < bw64;
if allow_extension {
// §7.10.2.5 `tplSamplePos[3][2] = { {bh4, -2}, {bh4, bw4},
// {bh4 - 2, bw4} }`.
let tpl_sample_pos: [[i32; 2]; 3] = [[bh4, -2], [bh4, bw4], [bh4 - 2, bw4]];
for pos in &tpl_sample_pos {
let dr = pos[0];
let dc = pos[1];
// §7.10.2.5 `check_sb_border(deltaRow, deltaCol)` —
// the candidate must remain inside the current
// superblock's 16×16-MI footprint.
let row = ((mi_row as i32) & 15) + dr;
let col = ((mi_col as i32) & 15) + dc;
if !(0..16).contains(&row) || !(0..16).contains(&col) {
continue;
}
self.temporal_sample(
mi_row,
mi_col,
dr,
dc,
is_compound,
ref_frame,
global_mvs,
allow_high_precision_mv,
force_integer_mv,
motion_field_mvs,
state,
);
}
}
}
/// §7.10.2.6 temporal sample process (av1-spec p.223-226 — the
/// `add_tpl_ref_mv` body). Reads
/// `MotionFieldMvs[ RefFrame[ list ] ][ y8 ][ x8 ]` for each
/// reference frame and inserts the §7.10.2.10
/// `lower_mv_precision`-rounded value into the §7.10.2 stack
/// with weight 2 (or increments an existing slot's weight by 2
/// on a duplicate hit, per the §7.10.2.6 dedupe inner loop).
///
/// Side effects:
/// * Updates `state.ref_stack_mv[..]` / `state.weight_stack[..]`
/// / `state.num_mv_found` exactly as the §7.10.2.6 spec body
/// prescribes.
/// * Sets `state.zero_mv_context` to `0` or `1` on the centre
/// cell `deltaRow == 0 && deltaCol == 0` based on whether the
/// temporal MV diverges from `GlobalMvs[..]` by >= 16
/// 1/8-luma-sample units (the spec's `Abs(candMv - GlobalMvs)
/// >= 16` test).
///
/// Caller-side dependencies: the §7.10.2.6 `is_inside` gate is
/// applied to `(mvRow | 1, mvCol | 1)`, exactly as the spec
/// body specifies. An out-of-tile position is silently dropped.
#[allow(clippy::too_many_arguments)]
fn temporal_sample(
&self,
mi_row: u32,
mi_col: u32,
delta_row: i32,
delta_col: i32,
is_compound: bool,
ref_frame: [i32; 2],
global_mvs: &[[i32; 2]; 2],
allow_high_precision_mv: bool,
force_integer_mv: bool,
motion_field_mvs: &MotionFieldMvs,
state: &mut MvStackState,
) {
// §7.10.2.6 `mvRow = (MiRow + deltaRow) | 1`, `mvCol = (MiCol +
// deltaCol) | 1`. The `| 1` aligns the read to the odd MI
// position the §7.9.2 projection stamped (§7.9.2 stores at
// `row = 2*y8 + 1`, `col = 2*x8 + 1`).
let mv_row = ((mi_row as i32) + delta_row) | 1;
let mv_col = ((mi_col as i32) + delta_col) | 1;
// §7.10.2.6 `if (is_inside(mvRow, mvCol) == 0) return`. Mirror
// the §7.10.2.4 `geometry.is_inside` gate.
if !self.geometry.is_inside(mv_row, mv_col) {
return;
}
// §7.10.2.6: x8 = mvCol >> 1; y8 = mvRow >> 1.
let x8 = (mv_col >> 1) as u32;
let y8 = (mv_row >> 1) as u32;
if delta_row == 0 && delta_col == 0 {
// §7.10.2.6 centre-cell ZeroMvContext base value (becomes
// `0` later only if both lists' MVs match GlobalMvs to
// within 16 1/8-sample units).
state.zero_mv_context = 1;
}
if !is_compound {
// §7.10.2.6 single-pred branch.
let r0 = ref_frame[0] as usize;
let raw = motion_field_mvs.get(r0, y8, x8);
if raw[0] == MFMV_INVALID {
return;
}
let cand_mv = lower_mv_precision(
[raw[0] as i32, raw[1] as i32],
allow_high_precision_mv,
force_integer_mv,
);
if delta_row == 0 && delta_col == 0 {
let g0 = global_mvs[0];
if (cand_mv[0] - g0[0]).abs() >= 16 || (cand_mv[1] - g0[1]).abs() >= 16 {
state.zero_mv_context = 1;
} else {
state.zero_mv_context = 0;
}
}
// §7.10.2.6 dedupe loop — match only on list 0.
let mut found_idx: Option<usize> = None;
for idx in 0..state.num_mv_found as usize {
if state.ref_stack_mv[idx][0] == cand_mv {
found_idx = Some(idx);
break;
}
}
if let Some(idx) = found_idx {
state.weight_stack[idx] = state.weight_stack[idx].saturating_add(2);
} else if (state.num_mv_found as usize) < MAX_REF_MV_STACK_SIZE {
let idx = state.num_mv_found as usize;
state.ref_stack_mv[idx][0] = cand_mv;
// §7.10.2.6 leaves slot 1 untouched on single-pred.
state.weight_stack[idx] = 2;
state.num_mv_found += 1;
}
} else {
// §7.10.2.6 compound-pred branch — both lists must
// produce a non-sentinel projection or the whole sample
// is dropped.
let r0 = ref_frame[0] as usize;
let r1 = ref_frame[1] as usize;
let raw0 = motion_field_mvs.get(r0, y8, x8);
if raw0[0] == MFMV_INVALID {
return;
}
let raw1 = motion_field_mvs.get(r1, y8, x8);
if raw1[0] == MFMV_INVALID {
return;
}
let cand0 = lower_mv_precision(
[raw0[0] as i32, raw0[1] as i32],
allow_high_precision_mv,
force_integer_mv,
);
let cand1 = lower_mv_precision(
[raw1[0] as i32, raw1[1] as i32],
allow_high_precision_mv,
force_integer_mv,
);
if delta_row == 0 && delta_col == 0 {
let g0 = global_mvs[0];
let g1 = global_mvs[1];
if (cand0[0] - g0[0]).abs() >= 16
|| (cand0[1] - g0[1]).abs() >= 16
|| (cand1[0] - g1[0]).abs() >= 16
|| (cand1[1] - g1[1]).abs() >= 16
{
state.zero_mv_context = 1;
} else {
state.zero_mv_context = 0;
}
}
// §7.10.2.6 dedupe across both lists.
let mut found_idx: Option<usize> = None;
for idx in 0..state.num_mv_found as usize {
if state.ref_stack_mv[idx][0] == cand0 && state.ref_stack_mv[idx][1] == cand1 {
found_idx = Some(idx);
break;
}
}
if let Some(idx) = found_idx {
state.weight_stack[idx] = state.weight_stack[idx].saturating_add(2);
} else if (state.num_mv_found as usize) < MAX_REF_MV_STACK_SIZE {
let idx = state.num_mv_found as usize;
state.ref_stack_mv[idx][0] = cand0;
state.ref_stack_mv[idx][1] = cand1;
state.weight_stack[idx] = 2;
state.num_mv_found += 1;
}
}
}
}
/// §7.10.2 working-state bag (`NumMvFound` / `NewMvCount` /
/// `RefStackMv` / `WeightStack` / `FoundMatch` / `ZeroMvContext`)
/// passed by mutable reference through the §7.10.2 sub-process driver.
#[derive(Debug, Clone, Copy)]
struct MvStackState {
num_mv_found: u32,
new_mv_count: u32,
ref_stack_mv: [[[i32; 2]; 2]; MAX_REF_MV_STACK_SIZE],
weight_stack: [u32; MAX_REF_MV_STACK_SIZE],
found_match: u32,
zero_mv_context: u32,
}
impl MvStackState {
fn new() -> Self {
Self {
num_mv_found: 0,
new_mv_count: 0,
ref_stack_mv: [[[0; 2]; 2]; MAX_REF_MV_STACK_SIZE],
weight_stack: [0; MAX_REF_MV_STACK_SIZE],
found_match: 0,
zero_mv_context: 0,
}
}
}
/// `MotionFieldMvs[ ref ][ y ][ x ][ comp ]` — the per-frame motion
/// field used by §7.10.2.5 / §7.10.2.6 temporal MV scan (av1-spec
/// p.212-216, p.223-226). Indexed by reference-frame
/// (`LAST_FRAME..=ALTREF_FRAME = 1..=7`), 8×8-luma-sample grid
/// position `(y8, x8)`, and motion-vector component
/// (`0 = row, 1 = col`). Stored as a flat `Vec<i16>` of length `7 * h8
/// * w8 * 2`.
///
/// The grid is populated by §7.9 `motion_field_estimation` once per
/// inter frame from the saved reference-frame motion vectors plus
/// frame-distance projection. The §7.10.2.6 temporal-sample body
/// reads `MotionFieldMvs[ RefFrame[ list ] ][ y8 ][ x8 ]` for each
/// candidate position; an unprojected slot sentinels as
/// `[MFMV_INVALID, MFMV_INVALID]` and short-circuits the body
/// (§7.10.2.6 line "if (candMv[0] == -1 << 15) return").
///
/// [`MotionFieldMvs::new_invalid`] creates a grid sized for the
/// current frame's `MiRows × MiCols` (with `h8 = MiRows >> 1`,
/// `w8 = MiCols >> 1` per §7.9.1 setup) and initialises every slot
/// to the sentinel — exactly the state the §7.9.1 initialiser
/// produces before any projection runs.
#[derive(Debug, Clone)]
pub struct MotionFieldMvs {
h8: u32,
w8: u32,
/// Layout: `data[((ref - 1) * h8 + y8) * w8 + x8) * 2 + comp]`.
/// Slot 0 of the outer dimension corresponds to `LAST_FRAME = 1`
/// (the §7.9.1 grid skips `INTRA_FRAME = 0` since it is never an
/// inter prediction reference).
data: Vec<i16>,
}
impl MotionFieldMvs {
/// `REFS_PER_FRAME = 7` — `LAST_FRAME..=ALTREF_FRAME = 1..=7` are
/// the inter-prediction references whose motion fields the
/// §7.10.2.5 scan iterates over. `INTRA_FRAME = 0` is excluded
/// (intra blocks never expose motion vectors).
pub const NUM_REFS: usize = 7;
/// Construct a fresh `MotionFieldMvs` sized for the given frame
/// geometry (`mi_rows`, `mi_cols` in 4×4 units; the grid stores
/// `h8 = mi_rows >> 1`, `w8 = mi_cols >> 1` per §7.9.1). Every
/// slot is initialised to the [`MFMV_INVALID`] sentinel so the
/// §7.10.2.6 reader correctly short-circuits on an unprojected
/// position.
pub fn new_invalid(mi_rows: u32, mi_cols: u32) -> Self {
let h8 = mi_rows >> 1;
let w8 = mi_cols >> 1;
let total = Self::NUM_REFS * (h8 as usize) * (w8 as usize) * 2;
Self {
h8,
w8,
data: vec![MFMV_INVALID; total],
}
}
/// Height of the motion-field grid in 8×8-luma-sample units
/// (`MiRows >> 1` per §7.9.1).
#[inline]
pub fn h8(&self) -> u32 {
self.h8
}
/// Width of the motion-field grid in 8×8-luma-sample units
/// (`MiCols >> 1` per §7.9.1).
#[inline]
pub fn w8(&self) -> u32 {
self.w8
}
#[inline]
fn slot_index(&self, ref_frame: usize, y8: u32, x8: u32, comp: usize) -> usize {
debug_assert!(
(1..=7).contains(&ref_frame),
"MotionFieldMvs is indexed by LAST_FRAME..=ALTREF_FRAME (= 1..=7)"
);
debug_assert!(y8 < self.h8);
debug_assert!(x8 < self.w8);
debug_assert!(comp < 2);
((ref_frame - 1) * (self.h8 as usize) + (y8 as usize)) * (self.w8 as usize) * 2
+ (x8 as usize) * 2
+ comp
}
/// Read `MotionFieldMvs[ ref_frame ][ y8 ][ x8 ]` per §7.10.2.6.
/// Returns the [`MFMV_INVALID`] sentinel for any out-of-grid
/// position so the §7.10.2.6 short-circuit fires naturally on
/// frame-edge candidates that survive the §7.10.2.6 `is_inside`
/// pre-check but reference a `y8`/`x8` clipped to the grid edge.
#[inline]
pub fn get(&self, ref_frame: usize, y8: u32, x8: u32) -> [i16; 2] {
if !(1..=7).contains(&ref_frame) || y8 >= self.h8 || x8 >= self.w8 {
return [MFMV_INVALID, MFMV_INVALID];
}
let base = self.slot_index(ref_frame, y8, x8, 0);
[self.data[base], self.data[base + 1]]
}
/// Stamp `MotionFieldMvs[ ref_frame ][ y8 ][ x8 ] = [row, col]`
/// per §7.9.2. Out-of-range writes panic (debug builds) — caller
/// is responsible for honouring `(y8, x8) < (h8, w8)` and
/// `ref_frame in 1..=7`.
#[inline]
pub fn set(&mut self, ref_frame: usize, y8: u32, x8: u32, mv: [i16; 2]) {
let base = self.slot_index(ref_frame, y8, x8, 0);
self.data[base] = mv[0];
self.data[base + 1] = mv[1];
}
/// Stamp `MotionFieldMvs[ ref ][ y8 ][ x8 ] = mv` for every
/// reference frame `ref in LAST_FRAME..=ALTREF_FRAME`. Matches
/// the §7.9.2 inner loop body:
/// `for (dst = LAST_FRAME; dst <= ALTREF_FRAME; dst++)`.
#[inline]
pub fn set_all_refs(&mut self, y8: u32, x8: u32, mv: [i16; 2]) {
for ref_frame in 1..=7 {
self.set(ref_frame, y8, x8, mv);
}
}
}
/// `get_mv_projection( mv, numerator, denominator )` per §7.9.3
/// (av1-spec p.215). Scales a saved motion vector by
/// `numerator / denominator` using the [`DIV_MULT`] quantised inverse
/// table, then clamps each component into
/// `-(1 << MV_IN_USE_BITS) + 1 ..= (1 << MV_IN_USE_BITS) - 1`.
///
/// Spec body:
///
/// ```text
/// clippedDenominator = Min(MAX_FRAME_DISTANCE, denominator)
/// clippedNumerator = Clip3(-MAX_FRAME_DISTANCE, MAX_FRAME_DISTANCE, numerator)
/// for ( i = 0; i < 2; i++ ) {
/// scaled = Round2Signed(mv[i] * clippedNumerator * Div_Mult[clippedDenominator], 14)
/// projMv[i] = Clip3(-(1<<14)+1, (1<<14)-1, scaled)
/// }
/// ```
pub fn get_mv_projection(mv: [i16; 2], numerator: i32, denominator: i32) -> [i16; 2] {
use crate::inter_pred::MAX_FRAME_DISTANCE;
let clipped_denominator = core::cmp::min(MAX_FRAME_DISTANCE, denominator) as usize;
let clipped_numerator = numerator.clamp(-MAX_FRAME_DISTANCE, MAX_FRAME_DISTANCE);
let div = DIV_MULT[clipped_denominator] as i64;
let max_val = (1i64 << MV_IN_USE_BITS) - 1;
let min_val = -max_val;
let mut proj = [0i16; 2];
for i in 0..2 {
let scaled = round2_signed((mv[i] as i64) * (clipped_numerator as i64) * div, 14);
proj[i] = scaled.clamp(min_val, max_val) as i16;
}
proj
}
/// `get_block_position( x8, y8, dstSign, projMv )` per §7.9.4
/// (av1-spec p.215). Returns `Some((pos_y8, pos_x8))` when the
/// projected position lies inside both the motion-field grid and
/// the per-direction `MAX_OFFSET_*` window; `None` otherwise (the
/// spec's `posValid = 0` outcome).
///
/// The inner `project` helper (av1-spec p.216):
///
/// ```text
/// project(v8, delta, dstSign, max8, maxOff8) {
/// base8 = (v8 >> 3) << 3
/// if (delta >= 0) {
/// offset8 = delta >> (3 + 1 + MI_SIZE_LOG2)
/// } else {
/// offset8 = -((-delta) >> (3 + 1 + MI_SIZE_LOG2))
/// }
/// v8 += dstSign * offset8
/// if (v8 < 0 || v8 >= max8 ||
/// v8 < base8 - maxOff8 ||
/// v8 >= base8 + 8 + maxOff8) {
/// posValid = 0
/// }
/// return v8
/// }
/// ```
pub fn get_block_position(
x8: i32,
y8: i32,
dst_sign: i32,
proj_mv: [i16; 2],
mi_rows: u32,
mi_cols: u32,
) -> Option<(u32, u32)> {
// §7.9.4: PosY8 = project(y8, projMv[0], dstSign, MiRows >> 1, MAX_OFFSET_HEIGHT)
// PosX8 = project(x8, projMv[1], dstSign, MiCols >> 1, MAX_OFFSET_WIDTH)
let max_y = (mi_rows >> 1) as i32;
let max_x = (mi_cols >> 1) as i32;
let (pos_y, valid_y) = project_axis(y8, proj_mv[0] as i32, dst_sign, max_y, MAX_OFFSET_HEIGHT);
let (pos_x, valid_x) = project_axis(x8, proj_mv[1] as i32, dst_sign, max_x, MAX_OFFSET_WIDTH);
if valid_y && valid_x {
Some((pos_y as u32, pos_x as u32))
} else {
None
}
}
#[inline]
fn project_axis(v8_in: i32, delta: i32, dst_sign: i32, max8: i32, max_off8: i32) -> (i32, bool) {
let base8 = (v8_in >> 3) << 3;
let offset8 = if delta >= 0 {
delta >> (3 + 1 + MI_SIZE_LOG2)
} else {
-((-delta) >> (3 + 1 + MI_SIZE_LOG2))
};
let v8 = v8_in + dst_sign * offset8;
let valid = v8 >= 0 && v8 < max8 && v8 >= base8 - max_off8 && v8 < base8 + 8 + max_off8;
(v8, valid)
}
/// `setup_global_mv( refList )` per §7.10.2.1 (av1-spec p.219). Returns
/// the per-block global-motion `mv` after the §7.10.2.10 lower-precision
/// pass.
///
/// * `INTRA_FRAME` or `IDENTITY` ⇒ `[0, 0]` (no warp).
/// * `TRANSLATION` ⇒ `[gm_params[ref][0] >> (PREC - 3),
/// gm_params[ref][1] >> (PREC - 3)]`.
/// * Otherwise (`ROTZOOM` / `AFFINE`) ⇒ project the block centre
/// through the affine model.
#[allow(clippy::too_many_arguments)]
fn setup_global_mv(
mi_row: u32,
mi_col: u32,
sub_size: usize,
ref_frame: i32,
gm_type: &[i32; 8],
gm_params: &[[i32; 6]; 8],
allow_high_precision_mv: bool,
force_integer_mv: bool,
) -> [i32; 2] {
let bw = NUM_4X4_BLOCKS_WIDE[sub_size] as i32 * 4;
let bh = NUM_4X4_BLOCKS_HIGH[sub_size] as i32 * 4;
// §7.10.2.1 short-circuit on INTRA_FRAME (which the caller may
// legally pass for the slot-1 inter-intra arm) — returns identity
// zero MV with no further processing.
let mut mv: [i32; 2] = [0, 0];
let ref_idx = ref_frame as usize;
if ref_frame == 0 {
return lower_mv_precision(mv, allow_high_precision_mv, force_integer_mv);
}
if !(0..8).contains(&ref_idx) {
// Caller-bug guard; the find_mv_stack outer guard already
// rejects this, but keep the local check for defence in depth.
return lower_mv_precision(mv, allow_high_precision_mv, force_integer_mv);
}
let typ = gm_type[ref_idx];
if typ == GM_TYPE_IDENTITY {
return lower_mv_precision(mv, allow_high_precision_mv, force_integer_mv);
}
if typ == GM_TYPE_TRANSLATION {
let shift = WARPEDMODEL_PREC_BITS - 3;
// Use the spec's literal `gm_params[ref][i] >> shift` —
// Rust's `>>` on `i32` is arithmetic (sign-preserving), the
// semantics the spec relies on.
mv[0] = gm_params[ref_idx][0] >> shift;
mv[1] = gm_params[ref_idx][1] >> shift;
return lower_mv_precision(mv, allow_high_precision_mv, force_integer_mv);
}
// ROTZOOM / AFFINE projection.
let x = (mi_col as i32) * (MI_SIZE as i32) + bw / 2 - 1;
let y = (mi_row as i32) * (MI_SIZE as i32) + bh / 2 - 1;
// Use saturating widening — `gm_params` fit in i32, multiplied by
// x/y up to ~32768, can reach the i32 range. Use i64 intermediate.
let xc: i64 = ((gm_params[ref_idx][2] as i64) - (1i64 << WARPEDMODEL_PREC_BITS)) * (x as i64)
+ (gm_params[ref_idx][3] as i64) * (y as i64)
+ (gm_params[ref_idx][0] as i64);
let yc: i64 = (gm_params[ref_idx][4] as i64) * (x as i64)
+ ((gm_params[ref_idx][5] as i64) - (1i64 << WARPEDMODEL_PREC_BITS)) * (y as i64)
+ (gm_params[ref_idx][1] as i64);
if allow_high_precision_mv {
mv[0] = round2_signed(yc, WARPEDMODEL_PREC_BITS - 3) as i32;
mv[1] = round2_signed(xc, WARPEDMODEL_PREC_BITS - 3) as i32;
} else {
mv[0] = (round2_signed(yc, WARPEDMODEL_PREC_BITS - 2) as i32) * 2;
mv[1] = (round2_signed(xc, WARPEDMODEL_PREC_BITS - 2) as i32) * 2;
}
lower_mv_precision(mv, allow_high_precision_mv, force_integer_mv)
}
/// §4.7.2 `Round2Signed(x, n)` — symmetric arithmetic round for signed
/// integers, returning `(x + (1 << (n-1))) >> n` after sign-handling.
#[inline]
fn round2_signed(x: i64, n: u32) -> i64 {
if n == 0 {
return x;
}
let half: i64 = 1i64 << (n - 1);
if x >= 0 {
(x + half) >> n
} else {
// Mirror the spec's `Round2Signed` (negative half rounds away
// from zero, then toward zero via arithmetic shift). For
// negative x, compute -Round2(|x|, n).
-(((-x) + half) >> n)
}
}
/// `lower_mv_precision( candMv )` per §7.10.2.10 (av1-spec p.229).
/// Strips the least significant bit when high-precision MVs are
/// disallowed, and all three fractional bits when `force_integer_mv ==
/// 1`.
#[inline]
fn lower_mv_precision(
mut cand_mv: [i32; 2],
allow_high_precision_mv: bool,
force_integer_mv: bool,
) -> [i32; 2] {
if allow_high_precision_mv {
return cand_mv;
}
for c in &mut cand_mv {
if force_integer_mv {
let a = c.unsigned_abs();
let a_int = (a + 3) >> 3;
*c = if *c > 0 {
(a_int << 3) as i32
} else {
-((a_int << 3) as i32)
};
} else if (*c & 1) != 0 {
if *c > 0 {
*c -= 1;
} else {
*c += 1;
}
}
}
cand_mv
}
/// §5.11.53 `clamp_mv_row(mvec, border)` — clamps the vertical (row)
/// component of an MV to the frame's edge plus a `border` margin (in
/// 1/8 luma samples). Surfaced as a free function so the find_mv_stack
/// driver can call it without needing the `&self` borrow.
#[inline]
fn clamp_mv_row(mvec: i32, border: i32, mi_rows: i32, bh4: i32, mi_row: i32) -> i32 {
let mb_to_top_edge = -((mi_row * (MI_SIZE as i32)) * 8);
let mb_to_bottom_edge = ((mi_rows - bh4 - mi_row) * (MI_SIZE as i32)) * 8;
let lo = mb_to_top_edge - border;
let hi = mb_to_bottom_edge + border;
mvec.clamp(lo, hi)
}
/// §5.11.54 `clamp_mv_col(mvec, border)` — horizontal mirror of
/// [`clamp_mv_row`].
#[inline]
fn clamp_mv_col(mvec: i32, border: i32, mi_cols: i32, bw4: i32, mi_col: i32) -> i32 {
let mb_to_left_edge = -((mi_col * (MI_SIZE as i32)) * 8);
let mb_to_right_edge = ((mi_cols - bw4 - mi_col) * (MI_SIZE as i32)) * 8;
let lo = mb_to_left_edge - border;
let hi = mb_to_right_edge + border;
mvec.clamp(lo, hi)
}
/// `has_newmv(mode)` per §7.10.2.9 (av1-spec p.228). Returns whether
/// `mode` is one of the six NEWMV-bearing inter Y modes.
#[inline]
fn has_newmv(mode: u8) -> bool {
matches!(
mode,
MODE_NEWMV
| MODE_NEW_NEWMV
| MODE_NEAR_NEWMV
| MODE_NEW_NEARMV
| MODE_NEAREST_NEWMV
| MODE_NEW_NEARESTMV
)
}
/// `has_nearmv()` per §5.11.23 (av1-spec p.75 — the helper definition
/// immediately following the `inter_block_mode_info` body). Returns
/// whether `mode` is one of the four NEARMV-bearing inter Y modes —
/// the ones whose §5.11.23 RefMvIdx loop iterates from `idx = 1` to
/// `idx < 3` (i.e. the `idx ∈ {1, 2}` window where the §7.10.2 stack
/// already has slot 0 = NEAREST and slot 1+2 are NEAR-class
/// candidates).
///
/// Spec body:
///
/// ```text
/// has_nearmv( ) {
/// return (YMode == NEARMV || YMode == NEAR_NEARMV
/// || YMode == NEAR_NEWMV || YMode == NEW_NEARMV)
/// }
/// ```
#[inline]
fn has_nearmv(mode: u8) -> bool {
matches!(
mode,
MODE_NEARMV | MODE_NEAR_NEARMV | MODE_NEAR_NEWMV | MODE_NEW_NEARMV
)
}
/// `get_mode(refList)` per §5.11.30 (av1-spec p.81) — folds a YMode +
/// reference-list index into the per-list `compMode` used by §5.11.31
/// `assign_mv`. The returned ordinal is one of
/// `MODE_NEWMV` / `MODE_NEARESTMV` / `MODE_NEARMV` / `MODE_GLOBALMV`
/// (the four single-list compMode values).
///
/// Spec body (av1-spec p.81):
///
/// ```text
/// get_mode( refList ) {
/// if ( refList == 0 ) {
/// if ( YMode < NEAREST_NEARESTMV )
/// compMode = YMode
/// else if ( YMode == NEW_NEWMV || YMode == NEW_NEARESTMV || YMode == NEW_NEARMV )
/// compMode = NEWMV
/// else if ( YMode == NEAREST_NEARESTMV || YMode == NEAREST_NEWMV )
/// compMode = NEARESTMV
/// else if ( YMode == NEAR_NEARMV || YMode == NEAR_NEWMV )
/// compMode = NEARMV
/// else
/// compMode = GLOBALMV
/// } else {
/// if ( YMode == NEW_NEWMV || YMode == NEAREST_NEWMV || YMode == NEAR_NEWMV )
/// compMode = NEWMV
/// else if ( YMode == NEAREST_NEARESTMV || YMode == NEW_NEARESTMV )
/// compMode = NEARESTMV
/// else if ( YMode == NEAR_NEARMV || YMode == NEW_NEARMV )
/// compMode = NEARMV
/// else
/// compMode = GLOBALMV
/// }
/// return compMode
/// }
/// ```
///
/// The single-pred branch (`ref_list == 0` AND `YMode <
/// NEAREST_NEARESTMV = 18`) is the identity (the YMode IS the
/// compMode), matching the §6.10.22 inter-Y-mode ordering
/// `MODE_NEARESTMV..=MODE_NEWMV = 14..=17`.
#[inline]
pub fn get_mode(y_mode: u8, ref_list: usize) -> u8 {
if ref_list == 0 {
if y_mode < MODE_NEAREST_NEARESTMV {
return y_mode;
}
match y_mode {
MODE_NEW_NEWMV | MODE_NEW_NEARESTMV | MODE_NEW_NEARMV => MODE_NEWMV,
MODE_NEAREST_NEARESTMV | MODE_NEAREST_NEWMV => MODE_NEARESTMV,
MODE_NEAR_NEARMV | MODE_NEAR_NEWMV => MODE_NEARMV,
_ => MODE_GLOBALMV,
}
} else {
match y_mode {
MODE_NEW_NEWMV | MODE_NEAREST_NEWMV | MODE_NEAR_NEWMV => MODE_NEWMV,
MODE_NEAREST_NEARESTMV | MODE_NEW_NEARESTMV => MODE_NEARESTMV,
MODE_NEAR_NEARMV | MODE_NEW_NEARMV => MODE_NEARMV,
_ => MODE_GLOBALMV,
}
}
}
/// §7.10.2.11 sorting process. Stable modified-bubble-sort: descending
/// by `WeightStack[]`, with the `swap_stack` swap copying both
/// `WeightStack` and `RefStackMv[idx][list][comp]` for `list = 0..1 +
/// isCompound`. The §7.10.2.11 outer `while` halves the unsorted prefix
/// after each pass.
fn sort_stack_segment(state: &mut MvStackState, start: usize, end_in: usize, is_compound: bool) {
let mut end = end_in;
while end > start {
let mut new_end = start;
let mut idx = start + 1;
while idx < end {
if state.weight_stack[idx - 1] < state.weight_stack[idx] {
// swap_stack(idx - 1, idx)
state.weight_stack.swap(idx - 1, idx);
let list_count = if is_compound { 2 } else { 1 };
for list in 0..list_count {
let tmp = state.ref_stack_mv[idx - 1][list];
state.ref_stack_mv[idx - 1][list] = state.ref_stack_mv[idx][list];
state.ref_stack_mv[idx][list] = tmp;
}
new_end = idx;
}
idx += 1;
}
end = new_end;
}
}
/// `FindMvStackResult` — the §7.10.2 process's aggregate output,
/// surfaced by [`PartitionWalker::find_mv_stack`]. Carries every
/// quantity the §5.11.23 post-`find_mv_stack` readers consume:
/// `RefStackMv[idx][list][comp]`, `WeightStack[idx]`, `NumMvFound`,
/// `NewMvCount`, `GlobalMvs[list]`, `NewMvContext`, `RefMvContext`,
/// `ZeroMvContext`, and `DrlCtxStack[idx]`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FindMvStackResult {
/// `NumMvFound` — number of distinct candidates retained in the
/// stack. In `0..=MAX_REF_MV_STACK_SIZE`.
pub num_mv_found: u32,
/// `NewMvCount` — number of NEWMV-class candidates contributing to
/// the close-match neighbourhood (the §7.10.2 step-12-13 snapshot).
/// Drives the §7.10.2.14 `NewMvContext` derivation through
/// `numNew`.
pub new_mv_count: u32,
/// `RefStackMv[idx][list][comp]` — the deduplicated, sorted, and
/// clamped per-component motion vectors in 1/8-luma-sample units.
/// Slot `[list][1]` is undefined for `list = 1` on single-pred
/// calls (`is_compound == false`).
pub ref_stack_mv: [[[i32; 2]; 2]; MAX_REF_MV_STACK_SIZE],
/// `WeightStack[idx]` — the per-stack-slot weight after the
/// §7.10.2 step-15 `REF_CAT_LEVEL` bonus and §7.10.2.11 sort.
pub weight_stack: [u32; MAX_REF_MV_STACK_SIZE],
/// `GlobalMvs[list]` — the per-ref global-motion MV from §7.10.2.1
/// `setup_global_mv`. Consumed by the §5.11.23 GLOBALMV /
/// GLOBAL_GLOBALMV arms.
pub global_mvs: [[i32; 2]; 2],
/// `NewMvContext` per §7.10.2.14. In `0..NEW_MV_CONTEXTS = 0..6`.
pub new_mv_context: u32,
/// `RefMvContext` per §7.10.2.14. In `0..REF_MV_CONTEXTS = 0..6`.
pub ref_mv_context: u32,
/// `ZeroMvContext` per §7.10.2. In `0..ZERO_MV_CONTEXTS = 0..2`.
/// Always `0` on the spatial-only path (the temporal scan is the
/// only writer of non-zero `ZeroMvContext`).
pub zero_mv_context: u32,
/// `DrlCtxStack[idx]` per §7.10.2.14. In `0..DRL_MODE_CONTEXTS =
/// 0..3`.
pub drl_ctx_stack: [u32; MAX_REF_MV_STACK_SIZE],
/// `CloseMatches` per §7.10.2 (step 12) — `foundAboveMatch +
/// foundLeftMatch` snapshot from the immediate neighbourhood. In
/// `0..=2`.
pub close_matches: u32,
/// `TotalMatches` per §7.10.2 (step 32) — `foundAboveMatch +
/// foundLeftMatch` after the §7.10.2 outer-ring scans. In `0..=2`.
pub total_matches: u32,
/// `numNearest` per §7.10.2 (step 13) — snapshot of `NumMvFound`
/// before the outer-ring scans + extra-search.
pub num_nearest: u32,
/// `numNew` per §7.10.2 (step 14) — snapshot of `NewMvCount` before
/// the outer-ring scans + extra-search.
pub num_new: u32,
}
/// `palette_colors_y[]` reader per §5.11.46 (av1-spec p.97) — the Y
/// plane palette-entries syntax sub-block. Reached from the §5.11.46
/// luma arm when `YMode == DC_PRED && has_palette_y == 1`. The reader
/// composes:
///
/// 1. `cacheN = get_palette_cache( 0 )` (§5.11.49) — the merged +
/// deduplicated above + left luma palette in ascending order.
/// 2. The cache-coded indices loop:
/// `for ( i = 0; i < cacheN && idx < PaletteSizeY; i++ ) { ` …
/// `use_palette_color_cache_y L(1)`; if set, copy `PaletteCache[i]`
/// into `palette_colors_y[idx++]`.
/// 3. The first "new" entry (if `idx < PaletteSizeY`) is an
/// `L(BitDepth)` literal.
/// 4. The optional `L(2) palette_num_extra_bits_y` (when at least one
/// more entry remains).
/// 5. The delta loop — `L(paletteBits) palette_delta_y`, incremented
/// by 1, then setting `palette_colors_y[idx]` to
/// `Clip1(palette_colors_y[idx - 1] + palette_delta_y)` and
/// refining `paletteBits` via the spec's `CeilLog2` of the residual
/// range.
/// 6. `sort( palette_colors_y, 0, PaletteSizeY - 1 )` (ascending) — the
/// Y plane always sorts the final array per §5.11.46 (cache-coded
/// entries are extracted from an already-sorted cache, but the new
/// entries can interleave with them — the merge requires a sort).
///
/// Returns a fixed-size `[u16; PALETTE_COLORS]` with the first
/// `palette_size_y` slots carrying the sorted palette entries; the
/// remaining slots are 0.
///
/// Reads neighbour state via `walker.get_palette_cache(0, mi_row,
/// mi_col, …)` — the caller's walker must have the §5.11.46 luma arm
/// of any preceding decoded blocks committed into its `PaletteSizes` /
/// `PaletteColors` grids.
#[allow(clippy::too_many_arguments)]
pub(crate) fn read_palette_entries_y(
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
bit_depth: u8,
palette_size_y: usize,
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
) -> Result<[u16; PALETTE_COLORS], crate::Error> {
debug_assert!(
matches!(bit_depth, 8 | 10 | 12),
"§5.5.2 bit_depth must be 8 / 10 / 12"
);
debug_assert!(
(2..=PALETTE_COLORS).contains(&palette_size_y),
"§5.11.46 PaletteSizeY must be in 2..=PALETTE_COLORS"
);
let bd = bit_depth as u32;
let max_val: u32 = 1u32 << bd;
let mut palette_colors_y = [0u16; PALETTE_COLORS];
// §5.11.49 cache.
let mut cache_buf = [0u16; 2 * PALETTE_COLORS];
let cache_n = walker.get_palette_cache(0, mi_row, mi_col, &mut cache_buf);
// §5.11.46 cache-coded indices loop.
let mut idx: usize = 0;
{
let mut i = 0usize;
while i < cache_n && idx < palette_size_y {
let use_cache = decoder.read_literal(1)?;
if use_cache != 0 {
palette_colors_y[idx] = cache_buf[i];
idx += 1;
}
i += 1;
}
}
// §5.11.46 first "new" entry as L(BitDepth) literal.
if idx < palette_size_y {
let v = decoder.read_literal(bd)?;
palette_colors_y[idx] = v as u16;
idx += 1;
}
// §5.11.46 paletteBits derivation (only when entries remain).
let mut palette_bits: u32 = 0;
if idx < palette_size_y {
// §5.11.46: `minBits = BitDepth - 3`. The §3 bit-depth
// enumeration starts at 8, so `minBits >= 5 > 0`.
let min_bits = bd - 3;
let extra = decoder.read_literal(2)?;
palette_bits = min_bits + extra;
}
// §5.11.46 delta loop — `palette_delta_y++` then Clip1(prev +
// delta), `range = (1 << BitDepth) - palette_colors_y[idx] - 1`,
// `paletteBits = Min(paletteBits, CeilLog2(range))`.
while idx < palette_size_y {
let delta_raw = decoder.read_literal(palette_bits)?;
let delta = delta_raw + 1;
let prev = palette_colors_y[idx - 1] as u32;
let v = clip1_to_bit_depth(prev + delta, bd);
palette_colors_y[idx] = v as u16;
// §5.11.46 paletteBits refinement: `range = (1 << BitDepth) -
// palette_colors_y[idx] - 1`. If range becomes 0 the next
// iteration's L(paletteBits) read becomes L(0) (zero bits), at
// which point any remaining `delta_raw == 0` plus the `+ 1`
// brings the next color to `v + 1` after Clip1 — but the spec
// gates this through CeilLog2 (defined to return 0 on 0).
let v_u32 = v;
let range = max_val.saturating_sub(v_u32).saturating_sub(1);
palette_bits = palette_bits.min(ceil_log2_av1(range));
idx += 1;
}
// §5.11.46 trailing sort over the populated entries (ascending).
palette_colors_y[..palette_size_y].sort_unstable();
Ok(palette_colors_y)
}
/// `palette_colors_u[]` + `palette_colors_v[]` reader per §5.11.46
/// (av1-spec p.97-98) — the UV-plane palette-entries syntax sub-block.
/// Reached from the §5.11.46 chroma arm when `HasChroma && UVMode ==
/// DC_PRED && has_palette_uv == 1`. Both U and V planes share the same
/// `PaletteSizeUV` (the §5.11.46 spec body reads it once).
///
/// The U plane mirrors the §5.11.46 Y reader minus the
/// `palette_delta_y++` step (the spec literally writes
/// `palette_colors_u[idx] = Clip1(palette_colors_u[idx - 1] +
/// palette_delta_u)` without the `++`) and minus the `- 1` on the
/// range derivation (`range = (1 << BitDepth) - palette_colors_u[idx]`
/// vs Y's `… - 1`).
///
/// The V plane has a distinct two-arm structure gated on
/// `delta_encode_palette_colors_v L(1)`:
///
/// * On `delta_encode_palette_colors_v == 1`:
/// - `minBits = BitDepth - 4`
/// - `maxVal = 1 << BitDepth`
/// - `L(2) palette_num_extra_bits_v`; `paletteBits = minBits + extra`
/// - `L(BitDepth) palette_colors_v[ 0 ]`
/// - For `idx = 1..PaletteSizeUV`: `L(paletteBits) palette_delta_v`;
/// on non-zero, read `L(1) palette_delta_sign_bit_v` and negate;
/// `val = palette_colors_v[idx - 1] + palette_delta_v`; modular
/// wrap to `[0, maxVal)`; `palette_colors_v[idx] = Clip1(val)`.
/// * On `delta_encode_palette_colors_v == 0`:
/// - `for idx = 0..PaletteSizeUV: palette_colors_v[idx] L(BitDepth)`
///
/// The V plane is **not** sorted after decode — the §5.11.46 spec body
/// omits the trailing `sort()` call that Y and U apply.
///
/// Returns a `(u, v)` tuple, each a `[u16; PALETTE_COLORS]` with the
/// first `palette_size_uv` slots carrying the decoded entries.
#[allow(clippy::too_many_arguments)]
pub(crate) fn read_palette_entries_uv(
decoder: &mut crate::symbol_decoder::SymbolDecoder<'_>,
bit_depth: u8,
palette_size_uv: usize,
walker: &PartitionWalker,
mi_row: u32,
mi_col: u32,
) -> Result<([u16; PALETTE_COLORS], [u16; PALETTE_COLORS]), crate::Error> {
debug_assert!(
matches!(bit_depth, 8 | 10 | 12),
"§5.5.2 bit_depth must be 8 / 10 / 12"
);
debug_assert!(
(2..=PALETTE_COLORS).contains(&palette_size_uv),
"§5.11.46 PaletteSizeUV must be in 2..=PALETTE_COLORS"
);
let bd = bit_depth as u32;
let max_val: u32 = 1u32 << bd;
// -------- U plane --------
let mut palette_colors_u = [0u16; PALETTE_COLORS];
let mut cache_buf = [0u16; 2 * PALETTE_COLORS];
let cache_n = walker.get_palette_cache(1, mi_row, mi_col, &mut cache_buf);
let mut idx: usize = 0;
{
let mut i = 0usize;
while i < cache_n && idx < palette_size_uv {
let use_cache = decoder.read_literal(1)?;
if use_cache != 0 {
palette_colors_u[idx] = cache_buf[i];
idx += 1;
}
i += 1;
}
}
if idx < palette_size_uv {
let v = decoder.read_literal(bd)?;
palette_colors_u[idx] = v as u16;
idx += 1;
}
let mut palette_bits: u32 = 0;
if idx < palette_size_uv {
let min_bits = bd - 3;
let extra = decoder.read_literal(2)?;
palette_bits = min_bits + extra;
}
// §5.11.46 U-plane delta loop. Note the spec differences vs Y:
// - NO `palette_delta_u++` (Y has `palette_delta_y++` before
// adding to the previous entry).
// - `range = (1 << BitDepth) - palette_colors_u[idx]` (Y has
// `… - 1` at the end).
while idx < palette_size_uv {
let delta = decoder.read_literal(palette_bits)?;
let prev = palette_colors_u[idx - 1] as u32;
let v = clip1_to_bit_depth(prev + delta, bd);
palette_colors_u[idx] = v as u16;
let range = max_val.saturating_sub(v);
palette_bits = palette_bits.min(ceil_log2_av1(range));
idx += 1;
}
palette_colors_u[..palette_size_uv].sort_unstable();
// -------- V plane --------
let mut palette_colors_v = [0u16; PALETTE_COLORS];
let delta_encode_v = decoder.read_literal(1)?;
if delta_encode_v != 0 {
// §5.11.46 V-plane delta-encoded arm.
let min_bits_v = bd - 4;
let extra_v = decoder.read_literal(2)?;
let palette_bits_v = min_bits_v + extra_v;
let first = decoder.read_literal(bd)?;
palette_colors_v[0] = first as u16;
for i in 1..palette_size_uv {
let raw = decoder.read_literal(palette_bits_v)?;
let signed_delta: i32 = if raw != 0 {
let sign_bit = decoder.read_literal(1)?;
if sign_bit != 0 {
-(raw as i32)
} else {
raw as i32
}
} else {
0
};
let prev_v = palette_colors_v[i - 1] as i32;
let mut val = prev_v + signed_delta;
// §5.11.46 V-plane modular wrap into [0, maxVal).
if val < 0 {
val += max_val as i32;
}
if val >= max_val as i32 {
val -= max_val as i32;
}
// §5.11.46: `palette_colors_v[idx] = Clip1(val)`. After the
// wrap `val` is in `[0, maxVal)` so Clip1 is the identity;
// we apply it anyway to match the spec literal form.
let clipped = clip1_to_bit_depth(val as u32, bd);
palette_colors_v[i] = clipped as u16;
}
// The §5.11.46 spec body does NOT sort the V plane after the
// delta-encoded read — preserve source order.
} else {
// §5.11.46 V-plane direct-literal arm.
for slot in palette_colors_v.iter_mut().take(palette_size_uv) {
let v = decoder.read_literal(bd)?;
*slot = v as u16;
}
}
Ok((palette_colors_u, palette_colors_v))
}
/// `Clip1( x )` per §4.7 — clamp `x` to `[0, (1 << BitDepth) - 1]`.
#[inline]
pub(crate) fn clip1_to_bit_depth(x: u32, bit_depth: u32) -> u32 {
let max_val: u32 = (1u32 << bit_depth) - 1;
if x > max_val {
max_val
} else {
x
}
}
/// `CeilLog2( x )` per §4.7.
///
/// Defined to return 0 when `x == 0` or `x == 1`. Otherwise the
/// smallest integer `i` such that `2.pow(i) >= x` (i.e. the number of
/// bits needed to code a value in `0..x`).
#[inline]
pub(crate) fn ceil_log2_av1(x: u32) -> u32 {
if x < 2 {
return 0;
}
// x.next_power_of_two() returns the smallest 2^i >= x; trailing_zeros
// of that is i.
x.next_power_of_two().trailing_zeros()
}
/// Result of [`PartitionWalker::decode_intra_frame_mode_info_prefix`]
/// — the per-block state §5.11.7 produces through the
/// `read_delta_lf( )` line. Carries the outputs of the composed leaf
/// methods (`skip`, `skip_mode`, `segment_id`, `Lossless`, `cdef_idx`)
/// plus the running quantiser-index / loop-filter-delta accumulators
/// the walker updated in place. The §5.11.7 trailing assignments
/// (`RefFrame[ 0 ] = INTRA_FRAME`, `RefFrame[ 1 ] = NONE`) are
/// surfaced as the `ref_frame` field.
///
/// Field semantics align one-to-one with §5.11.7 / §5.11.8 / §5.11.11
/// / §5.11.56 / §5.11.12 / §5.11.13:
///
/// * `skip` — §5.11.11 / §5.11.5 `Skips[r][c]`. `0` or `1`.
/// * `skip_mode` — §5.11.7 line 4 fixed at `0` for the intra-frame
/// walk (§5.11.10 short-circuits on `!skip_mode_present`).
/// * `segment_id` — §5.11.8 / §5.11.9 `SegmentIds[r][c]`. In
/// `0..MAX_SEGMENTS = 0..8`.
/// * `lossless` — §5.11.8 `Lossless = LosslessArray[ segment_id ]`.
/// * `cdef_idx` — §5.11.56 / §5.11.55 `cdef_idx[r][c]`. `-1` when the
/// §5.11.56 short-circuit fires (skip / coded-lossless /
/// !enable_cdef / allow_intrabc) and the 64×64 anchor has not been
/// stamped yet by an earlier leaf.
/// * `current_q_index` — §5.11.12 `CurrentQIndex` after the call.
/// * `current_delta_lf` — §5.11.13 `DeltaLF[ .. ]` row after the call.
/// * `ref_frame` — §5.11.7 `RefFrame[ 0..2 ]` = `[INTRA_FRAME, NONE]`
/// = `[0, -1]`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IntraFrameModeInfoPrefix {
/// `Skips[r][c]` post-§5.11.11. `0` or `1`.
pub skip: u8,
/// `SkipModes[r][c]` post-§5.11.10. Always `0` for the
/// intra-frame walk (no `decode_skip_mode` call from this
/// dispatcher).
pub skip_mode: u8,
/// `SegmentIds[r][c]` post-§5.11.9 (whichever §5.11.7 arm fired).
/// In `0..MAX_SEGMENTS = 0..8`.
pub segment_id: u8,
/// `Lossless = LosslessArray[ segment_id ]` per §5.11.8.
pub lossless: bool,
/// `cdef_idx[r][c]` post-§5.11.56. `-1` when the §5.11.56
/// short-circuit fired and no anchor was written.
pub cdef_idx: i8,
/// `CurrentQIndex` post-§5.11.12 (running accumulator).
pub current_q_index: i32,
/// `DeltaLF[ .. ]` row post-§5.11.13 (running accumulator).
pub current_delta_lf: [i32; FRAME_LF_COUNT],
/// §5.11.7 `RefFrame[ 0..2 ]` = `[INTRA_FRAME, NONE]` = `[0, -1]`.
/// Fixed by spec — intra-frame mode info always sets these to
/// `INTRA_FRAME` / `NONE` regardless of the `use_intrabc`
/// follow-on element.
pub ref_frame: [i8; 2],
}
/// `DecodedBlock` — the per-block derived state the §5.11.5
/// [`PartitionWalker::decode_block_syntax`] walker produces on the
/// implemented prologue + §5.11.6 `mode_info()` (intra arm) path.
///
/// Combines:
///
/// * The §5.11.5 prologue derivations: `MiRow` / `MiCol` / `MiSize` /
/// `bw4` / `bh4` / `HasChroma` / `AvailU` / `AvailL` /
/// `AvailUChroma` / `AvailLChroma`.
/// * The §5.11.7 [`IntraFrameModeInfoPrefix`] fields (`skip` /
/// `skip_mode` / `segment_id` / `lossless` / `cdef_idx` /
/// `current_q_index` / `current_delta_lf` / `ref_frame`).
/// * The §5.11.7 mode-info pass results: `use_intrabc`, `is_inter`,
/// `YMode`, `IsCompound`.
///
/// Fields are spec-shaped names that match the av1-spec p.63-64
/// `decode_block` body variables one-to-one. Stamped over the block's
/// `bw4 * bh4` footprint of the walker's grid arrays by each composed
/// leaf as it fires.
///
/// Out of scope (surfaced through dedicated readouts rather than
/// this aggregate): `compute_prediction()` outputs (prediction
/// samples), `residual()` outputs (transform coefficients +
/// reconstructed samples), the inter arm's `CompGroupIdx` /
/// `CompoundIdx` / interpolation-filter detail (see
/// [`DecodedInterBlockModeInfo`]).
///
/// The §5.11.49 `palette_tokens()` per-sample `ColorMap{Y,UV}`
/// contents are surfaced here (r325) through [`Self::color_map_y`] /
/// [`Self::color_map_uv`] so a stream consumer that maintains
/// `CurrFrame[plane]` buffers can drive the §7.11.4 palette
/// prediction process; the §5.11.5 walker itself tracks no per-plane
/// sample buffers, so it surfaces the decoded maps rather than
/// reconstructing inline.
///
/// Note: this struct is no longer `Copy` (r325) — the surfaced
/// palette maps are heap-allocated [`DecodedPaletteMap`] aggregates.
/// It remains `Clone`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedBlock {
/// §5.11.5 `MiRow = r` — mi-unit row of the block.
pub mi_row: u32,
/// §5.11.5 `MiCol = c` — mi-unit column of the block.
pub mi_col: u32,
/// §5.11.5 `MiSize = subSize` — the BLOCK_* ordinal selected by
/// the §5.11.4 partition tree for this leaf, in `0..BLOCK_SIZES`.
pub mi_size: usize,
/// §5.11.5 `bw4 = Num_4x4_Blocks_Wide[ MiSize ]`.
pub bw4: u32,
/// §5.11.5 `bh4 = Num_4x4_Blocks_High[ MiSize ]`.
pub bh4: u32,
/// §5.11.5 `HasChroma` — true ⇔ the block emits chroma samples.
/// Three-arm derivation: small-sample sub-sampled edge cases
/// force `false`; otherwise `num_planes > 1`.
pub has_chroma: bool,
/// §5.11.5 `AvailU = is_inside( r - 1, c )` — true ⇔ the block's
/// above neighbour is inside the current tile.
pub avail_u: bool,
/// §5.11.5 `AvailL = is_inside( r, c - 1 )` — true ⇔ the block's
/// left neighbour is inside the current tile.
pub avail_l: bool,
/// §5.11.5 `AvailUChroma` — `AvailU` plus the chroma fix-up arm
/// (`is_inside( r - 2, c )` when `subsampling_y && bh4 == 1`).
/// `false` when `!HasChroma`.
pub avail_u_chroma: bool,
/// §5.11.5 `AvailLChroma` — `AvailL` plus the chroma fix-up arm
/// (`is_inside( r, c - 2 )` when `subsampling_x && bw4 == 1`).
/// `false` when `!HasChroma`.
pub avail_l_chroma: bool,
/// §5.11.5 / §5.11.11 `Skips[r][c]`. `0` or `1`.
pub skip: u8,
/// §5.11.5 / §5.11.10 `SkipModes[r][c]`. Always `0` for the
/// intra-frame walk.
pub skip_mode: u8,
/// §5.11.5 / §5.11.9 `SegmentIds[r][c]`. In `0..MAX_SEGMENTS`.
pub segment_id: u8,
/// §5.11.8 `Lossless = LosslessArray[ segment_id ]`.
pub lossless: bool,
/// §5.11.56 `cdef_idx[r][c]`. `-1` on the short-circuit fall-through.
pub cdef_idx: i8,
/// §5.11.12 `CurrentQIndex` post-call (running accumulator).
pub current_q_index: i32,
/// §5.11.13 `DeltaLF[..]` row post-call (running accumulator).
pub current_delta_lf: [i32; FRAME_LF_COUNT],
/// §5.11.7 `RefFrame[0..2]` after the intra arm =
/// `[INTRA_FRAME, NONE]` = `[0, -1]`.
pub ref_frame: [i8; 2],
/// §5.11.7 `use_intrabc`. `0` on the `allow_intrabc = false`
/// fall-through and the `else` arm; `1` on the intra-block-copy
/// arm (whose §7.10.2 `find_mv_stack( 0 )` + §5.11.26
/// `assign_mv( 0 )` body runs to completion — see [`Self::mv`]).
pub use_intrabc: u8,
/// §5.11.5 / §5.11.7 `is_inter`. `0` on the `else` arm, `1` on
/// the `use_intrabc == 1` arm. The walker stamps this into
/// `IsInters[r+y][c+x]` for the implemented mode-info path.
pub is_inter: u8,
/// §5.11.7 `YMode = intra_frame_y_mode` (or `DC_PRED` on the
/// `use_intrabc == 1` arm).
pub y_mode: u8,
/// §5.11.7 / §5.11.22 `UVMode`. `Some(uv_mode)` when the
/// `HasChroma` arm of the `else` body read the element;
/// `Some(DC_PRED)` on the `use_intrabc == 1` arm (the §5.11.7
/// fixed assignment); `None` when `!HasChroma` on the `else`
/// arm and on the §5.11.6 inter arm (no chroma intra mode).
pub uv_mode: Option<u8>,
/// §5.11.42 `AngleDeltaY` from `intra_angle_info_y( )`. `0` on
/// the §5.11.42 short-circuit, the intrabc arm and the inter arm.
pub angle_delta_y: i8,
/// §5.11.43 `AngleDeltaUV` from `intra_angle_info_uv( )`. `None`
/// when the `HasChroma` arm did not run.
pub angle_delta_uv: Option<i8>,
/// §5.11.45 `CflAlphaU`. `None` unless `UVMode == UV_CFL_PRED`.
pub cfl_alpha_u: Option<i8>,
/// §5.11.45 `CflAlphaV`. `None` unless `UVMode == UV_CFL_PRED`.
pub cfl_alpha_v: Option<i8>,
/// §5.11.46 `PaletteSizeY`. `0` when no luma palette was coded
/// (the §5.11.22 line-11 initialiser); `2..=PALETTE_COLORS`
/// otherwise. Non-zero implies the §5.11.49 `palette_tokens()`
/// luma walk consumed its `color_index_map_y` NS() +
/// `palette_color_idx_y` S() reads.
pub palette_size_y: u8,
/// §5.11.46 `PaletteSizeUV` — chroma twin of
/// [`Self::palette_size_y`].
pub palette_size_uv: u8,
/// §5.11.24 `use_filter_intra`. `None` when the §5.11.24 outer
/// gate was closed (the spec body's `use_filter_intra = 0`
/// initialiser).
pub use_filter_intra: Option<u8>,
/// §5.11.24 `filter_intra_mode`. `None` unless
/// `use_filter_intra == 1`.
pub filter_intra_mode: Option<u8>,
/// §5.11.26 / §5.11.31 `Mv[ 0..2 ]` (`[ row, col ]` per list,
/// 1/8-luma-sample units). `[[0, 0], [0, 0]]` on the intra
/// `else` arm (no MV is coded); list 0 carries the
/// intra-block-copy block vector on the `use_intrabc == 1` arm;
/// both lists are populated per the §5.11.23 aggregate on the
/// inter arm.
pub mv: [[i32; 2]; 2],
/// §5.11.5 `IsCompound = RefFrame[1] > INTRA_FRAME`. Always
/// `false` on the intra arm (where `RefFrame[1] = NONE = -1 <
/// INTRA_FRAME = 0`).
pub is_compound: bool,
/// §5.11.33 `IsInterIntra = ( is_inter && RefFrame[ 1 ] ==
/// INTRA_FRAME )` (av1-spec p.82 line 5140), captured from the
/// per-block [`ComputePredictionReadout`] the §5.11.5 walker
/// runs at the `compute_prediction()` call site. Always `false`
/// on the §5.11.7 intra arm (`RefFrame[1] = NONE = -1 ≠
/// INTRA_FRAME = 0`) and on the §5.11.6 inter arm's
/// non-inter-intra blocks; `true` exactly when the §5.11.28
/// `interintra` arm stamped `RefFrame[1] = INTRA_FRAME` over the
/// block. A stream consumer that maintains `CurrFrame[plane]`
/// buffers routes `true` blocks through the shared
/// [`crate::reconstruct_inter_intra_block`] /
/// [`crate::reconstruct_inter_intra_from_dispatch`] driver; the
/// §5.11.5 walker itself tracks no per-plane sample buffers so it
/// surfaces the flag rather than reconstructing inline.
pub is_inter_intra: bool,
/// §5.11.5 / §5.11.16 `TxSize`. On the implemented intra arm the
/// value is `read_tx_size`'s return: `TX_4X4` when `Lossless`,
/// otherwise `maxRectTxSize` further split `tx_depth` times via
/// [`SPLIT_TX_SIZE`] when the frame's `TxMode == TX_MODE_SELECT`,
/// otherwise `maxRectTxSize`. The §5.11.5 footer also stamps this
/// into the walker's `TxSizes[]` and `InterTxSizes[]` grids over
/// the block's `bh4 * bw4` footprint.
pub tx_size: u8,
/// §5.11.49 `ColorMapY[ ][ ]` — the decoded luma palette colour
/// index map. `Some(map)` exactly when [`Self::palette_size_y`] is
/// non-zero (a luma palette block); `None` otherwise. The map's
/// `block_w × block_h` row-major contents are the §7.11.4 luma
/// palette-prediction indices (each in `0..PaletteSizeY`),
/// including the §5.11.49 bottom-row / right-column border-fill
/// applied past the on-screen region. See [`DecodedPaletteMap`].
pub color_map_y: Option<DecodedPaletteMap>,
/// §5.11.49 `ColorMapUV[ ][ ]` — the decoded chroma palette colour
/// index map. `Some(map)` exactly when [`Self::palette_size_uv`]
/// is non-zero; `None` otherwise. Dimensions are the chroma-plane
/// (post-subsampling, post-`<4`-bump) `block_w × block_h` per
/// §5.11.49. See [`DecodedPaletteMap`].
pub color_map_uv: Option<DecodedPaletteMap>,
}
/// §5.11.49 `ColorMap{Y,UV}[ ][ ]` — a decoded palette colour-index
/// map surfaced on [`DecodedBlock`] (r325).
///
/// The map is the per-sample palette colour index the §5.11.49
/// `palette_tokens()` reader produced: `data[row * stride + col]` is
/// the index for sample `(row, col)` of the plane's palette block.
/// Indices are in `0..palette_size` and select into the block's
/// §5.11.46 `PaletteColors{Y,UV}[ ]` table (decoded separately and
/// not carried here).
///
/// Geometry mirrors the [`PaletteTokensArgs`] the reader consumed:
///
/// * `block_w` / `block_h` — the full (post-subsampling,
/// post-`<4`-bump) block extent; `data.len() == block_w * block_h`
/// and `stride == block_w`.
/// * `onscreen_w` / `onscreen_h` — the sub-region that received the
/// §5.11.49 anti-diagonal `palette_color_idx` S() walk. Samples in
/// `[onscreen_h..block_h]` rows and `[onscreen_w..block_w]` columns
/// carry the spec's border-fill (the last on-screen value
/// replicated rightward / downward).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedPaletteMap {
/// Row-major colour-index buffer, length `block_w * block_h`.
pub data: Vec<u8>,
/// Row stride of [`Self::data`]; equal to [`Self::block_w`].
pub stride: usize,
/// Full block width (post-subsampling, post-`<4`-bump).
pub block_w: usize,
/// Full block height (post-subsampling, post-`<4`-bump).
pub block_h: usize,
/// On-screen sub-region width that received decoded S() reads.
pub onscreen_w: usize,
/// On-screen sub-region height that received decoded S() reads.
pub onscreen_h: usize,
}
/// §5.11.18 per-block aggregate surfaced by
/// [`PartitionWalker::decode_inter_frame_mode_info`]. Carries every
/// value the §5.11.18 spec body derives or reads before the
/// `if ( is_inter )` terminal dispatch into §5.11.22 / §5.11.23.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodedInterFrameModeInfo {
/// §5.11.18 caller-passed mi-unit row of the block.
pub mi_row: u32,
/// §5.11.18 caller-passed mi-unit column of the block.
pub mi_col: u32,
/// §5.11.18 caller-passed `MiSize = subSize` ordinal.
pub mi_size: usize,
/// §5.11.18 line 1: `use_intrabc = 0`. Always 0 on the inter arm.
pub use_intrabc: u8,
/// §5.11.5 / §5.11.18 `AvailU = is_inside( MiRow - 1, MiCol )`.
pub avail_u: bool,
/// §5.11.5 / §5.11.18 `AvailL = is_inside( MiRow, MiCol - 1 )`.
pub avail_l: bool,
/// §5.11.18 `LeftRefFrame[0..2]`. Currently `[INTRA_FRAME, NONE]`
/// = `[0, -1]` since the walker doesn't yet track
/// `RefFrames[][][..]`; entries become the conformant
/// neighbour-read values once the §5.11.23 readers land and the
/// walker grows a `ref_frames[][][..]` grid.
pub left_ref_frame: [i32; 2],
/// §5.11.18 `AboveRefFrame[0..2]`. Currently `[INTRA_FRAME, NONE]`.
pub above_ref_frame: [i32; 2],
/// §5.11.18 `LeftIntra = LeftRefFrame[0] <= INTRA_FRAME`.
pub left_intra: bool,
/// §5.11.18 `AboveIntra = AboveRefFrame[0] <= INTRA_FRAME`.
pub above_intra: bool,
/// §5.11.18 `LeftSingle = LeftRefFrame[1] <= INTRA_FRAME`.
pub left_single: bool,
/// §5.11.18 `AboveSingle = AboveRefFrame[1] <= INTRA_FRAME`.
pub above_single: bool,
/// §5.11.18 line 10 + lines 13-14 result. `1` if `skip_mode == 1`
/// or §5.11.11 `read_skip` returned `1`; `0` otherwise.
pub skip: u8,
/// §5.11.18 line 12 result. `0` if the §5.11.10
/// short-circuit fired; `0` or `1` if `S()` was read.
pub skip_mode: u8,
/// §5.11.18 lines 11 + 15-16 result. `0..=last_active_seg_id`.
pub segment_id: u8,
/// §5.11.18 line 17: `Lossless = LosslessArray[ segment_id ]`.
pub lossless: bool,
/// §5.11.18 line 18 result. `-1` on the §5.11.56 short-circuit.
pub cdef_idx: i8,
/// §5.11.18 line 19 result — `CurrentQIndex` post-call.
pub current_q_index: i32,
/// §5.11.18 line 20 result — `DeltaLF[..]` row post-call.
pub current_delta_lf: [i32; FRAME_LF_COUNT],
/// §5.11.18 line 22 result. `0` (intra) or `1` (inter).
pub is_inter: u8,
/// §5.11.23 inter-arm body output (r190 wire-up). `Some(_)` when
/// the §5.11.18 `if ( is_inter )` arm fires and the §5.11.23
/// inter-block cascade (`read_ref_frames` / `find_mv_stack` /
/// YMode dispatch / `assign_mv` / `read_motion_mode` /
/// `read_interintra_mode` / `read_compound_type` /
/// `read_interpolation_filter`) completes. `None` on the
/// `is_inter == 0` (intra) arm or when the inter cascade returned
/// before completing. Prior to r190 the dispatcher returned
/// [`crate::Error::InterBlockModeInfoUnsupported`] on the `Ok(_)`
/// arm; r190 lifts that and surfaces the inner aggregate here so
/// the §5.11.5 [`PartitionWalker::decode_block_syntax`] walker can
/// thread the MV / ref-frame / interp-filter into
/// `compute_prediction` + `residual`.
pub inter_block: Option<DecodedInterBlockModeInfo>,
}
/// `InterFrameContext` — r190 bundle of the per-frame / per-segment
/// scalars that [`PartitionWalker::decode_block_syntax`] passes through
/// to [`PartitionWalker::decode_inter_frame_mode_info`] when wiring the
/// §5.11.18 inter arm. Each field tracks a single §5.9 / §5.5.2 /
/// §5.11.x derivation the §5.11.18 dispatcher consumes.
///
/// Callers staying on the §5.11.6 intra-only path pass `None` to
/// [`PartitionWalker::decode_block_syntax`] / [`PartitionWalker::decode_partition_syntax`];
/// the walker then preserves its pre-r190 short-circuit
/// (`Err(crate::Error::DecodeBlockInterFrameUnsupported)`) on
/// `frame_is_intra == false`. Callers wanting the §5.11.18 inter arm
/// to run (and thread MV / ref-frame state into
/// [`PartitionWalker::compute_prediction`] +
/// [`PartitionWalker::residual`]) pass `Some(&ctx)` with the scalars
/// derived from the §5.9 frame header and the §5.5.2 sequence header.
///
/// All fields mirror the
/// [`PartitionWalker::decode_inter_frame_mode_info`] / [`PartitionWalker::decode_inter_block_mode_info`]
/// argument shape one-to-one so callers can construct the context
/// once per frame and reuse it across every per-block dispatch.
#[derive(Debug, Clone)]
pub struct InterFrameContext<'a> {
// ---- §5.9.14 / §6.10 segmentation state ----
/// `segmentation_update_map` (§5.9.14).
pub segmentation_update_map: bool,
/// `segmentation_temporal_update` (§5.9.14).
pub segmentation_temporal_update: bool,
/// §5.9.14 `FeatureEnabled[ segmentId ][ feature ]` — the walker
/// derives every per-block `seg_feature_active( feature )` gate
/// (`segmentation_enabled && FeatureEnabled[ segment_id ][ f ]`,
/// §5.11.14) from this table at the block's own `segment_id`,
/// which is only known mid-§5.11.18 (the pre-skip / post-skip
/// `inter_segment_id` reads). r394: replaces the frame-scope
/// pre-collapsed booleans, which were wrong whenever two segments
/// carried different feature sets.
pub seg_feature_active: [[bool; crate::uncompressed_header_tail::SEG_LVL_MAX]; MAX_SEGMENTS],
/// §5.9.14 `FeatureData[ segmentId ][ feature ]`.
pub seg_feature_data: [[i16; crate::uncompressed_header_tail::SEG_LVL_MAX]; MAX_SEGMENTS],
/// §5.9.2 `PrevSegmentIds[ row ][ col ]` (row-major over the
/// current frame's `MiRows × MiCols` grid) — the §5.11.21
/// `get_segment_id()` prediction source, loaded by
/// `load_previous_segment_ids()` from the primary reference (or
/// all-zero after `setup_past_independence()`). `None` ⇒ all-zero.
pub prev_segment_ids: Option<&'a [i32]>,
/// `skip_mode_present` (§5.9.22).
pub skip_mode_present: bool,
/// `SkipModeFrame[ 0..2 ]` per §5.9.22.
pub skip_mode_frame: [i32; 2],
/// `reference_select` / `frame_reference_mode` (§5.9.23).
pub reference_select: bool,
// ---- §7.10 / §5.9.24 global-motion + MV-stack state ----
/// `GmType[ refFrame ]` per §5.9.24, indexed in `0..NUM_REF_FRAMES = 0..8`.
pub gm_type: [i32; 8],
/// `gm_params[ refFrame ][ idx ]` per §5.9.24.
pub gm_params: [[i32; 6]; 8],
/// `RefFrameSignBias[ refFrame ]` per §7.8.
pub ref_frame_sign_bias: [i32; 8],
/// `allow_high_precision_mv` (§5.9.2).
pub allow_high_precision_mv: bool,
/// `force_integer_mv` (§5.9.2).
pub force_integer_mv: bool,
/// `use_ref_frame_mvs` (§5.9.2).
pub use_ref_frame_mvs: bool,
// ---- §5.11.27 motion-mode state ----
/// `is_motion_mode_switchable` (§5.9.2).
pub is_motion_mode_switchable: bool,
/// `allow_warped_motion` (§5.9.2).
pub allow_warped_motion: bool,
/// Per-`refFrame in LAST_FRAME..=ALTREF_FRAME = 1..=7` `is_scaled(refFrame)`
/// outcome per §5.11.27.
pub is_scaled_per_ref: [bool; 7],
// ---- §5.11.28 / §5.11.29 compound-blend state ----
/// `enable_interintra_compound` (§5.5.2).
pub enable_interintra_compound: bool,
/// `enable_masked_compound` (§5.5.2).
pub enable_masked_compound: bool,
/// `enable_jnt_comp` (§5.5.2).
pub enable_jnt_comp: bool,
/// §5.9.2 `OrderHint` / `OrderHints[]` frame-header scalars. The
/// §8.3.2 `compound_idx` ctx seed (`fwd == bck` over the block's
/// own `RefFrame[0]` / `RefFrame[1]` relative distances) is
/// derived per block from this at the §5.11.29 read; the
/// §7.11.3.15 DISTANCE blend weights read the same table through
/// [`InterWalkPixels::order_hints`].
pub order_hints: FrameInterOrderHints,
// ---- §5.11.x interpolation-filter state ----
/// `interpolation_filter` (§5.9.10) — one of `EIGHTTAP..=SWITCHABLE = 0..=4`.
pub interpolation_filter: u8,
/// `enable_dual_filter` (§5.5.2).
pub enable_dual_filter: bool,
// ---- r205 §7.10.2.5 / §7.10.2.6 motion-field state ----
/// `MotionFieldMvs[ref][y8][x8]` populated by §7.9
/// `motion_field_estimation`. The grid is consulted by §7.10.2.5
/// temporal scan when `use_ref_frame_mvs == true`; pass an
/// all-`MFMV_INVALID` grid via
/// [`MotionFieldMvs::new_invalid`] for callers that haven't yet
/// run §7.9 (the temporal-scan body short-circuits on the
/// sentinel).
pub motion_field_mvs: &'a MotionFieldMvs,
// ---- in-walk §5.11.33 `predict_inter` pixel state ----
/// §7.11.3 reference-pixel state for the in-walk §5.11.33
/// `compute_prediction()`. `Some(_)` ⇒ the §5.11.5 inter arm
/// motion-compensates every inter leaf into `CurrFrame[ plane ]`
/// **during** the walk (the spec's per-block `compute_prediction()`
/// → `residual()` ordering, so a later intra block's §7.11.2
/// neighbour reads observe reconstructed inter pixels and the
/// §7.12.3 step-3 residual merge lands on the leaf's prediction).
/// `None` ⇒ the historical syntax-only walk (callers reconstruct
/// post-walk via the frame-scope bridge, valid only when no intra
/// leaf reads an inter neighbour and no inter leaf codes a
/// residual).
pub pixels: Option<&'a InterWalkPixels<'a>>,
}
/// §7.11.3 reference-pixel state for the in-walk §5.11.33
/// `compute_prediction()` — the decoded-picture-buffer view the
/// §5.11.5 walker motion-compensates against. Carried on
/// [`InterFrameContext::pixels`].
#[derive(Debug)]
pub struct InterWalkPixels<'a> {
/// §6.8.2 `ref_frame_idx[ 0..REFS_PER_FRAME ]` — the slot index
/// each of the seven inter references (`LAST_FRAME - LAST_FRAME =
/// 0` .. `ALTREF_FRAME - LAST_FRAME = 6`) resolves to.
pub ref_frame_idx: [u8; 7],
/// §5.5.2 `BitDepth` (8 / 10 / 12).
pub bit_depth: u8,
/// Per-decoded-plane reference specs — each carries the plane
/// ordinal, its subsampling, the per-slot §7.11.3.3 `FrameStore`
/// slices, and the current frame's plane-space dimensions.
pub plane_refs: &'a [crate::PlaneRefSpec<'a>],
/// §7.11.3.15 order-hint context (COMPOUND_DISTANCE weights).
pub order_hints: FrameInterOrderHints,
}
impl<'a> InterFrameContext<'a> {
/// Identity-default `InterFrameContext` — every flag off, segment
/// id `0`, `gm_params[ref][2] = gm_params[ref][5] = 1 << WARPEDMODEL_PREC_BITS`
/// (the §5.9.24 identity-warp defaults), zero MV / no skip-mode
/// frame, `EIGHTTAP` interp filter. Useful for tests + single-ref
/// non-segmented + non-compound P-frame fixtures that don't yet
/// exercise the segmentation / global-motion / compound paths.
///
/// The `gm_params` slot pattern matches
/// [`PartitionWalker::decode_inter_frame_mode_info`]'s
/// "pass identity-default for callers that never run the inter arm"
/// guidance — `gm_params[ref][2] = gm_params[ref][5] = (1 <<
/// WARPEDMODEL_PREC_BITS) = 1 << 16 = 65536`.
pub fn identity_default(motion_field_mvs: &'a MotionFieldMvs) -> Self {
let mut gm_params = [[0i32; 6]; 8];
// §5.9.24 identity-warp defaults: slot 2 / slot 5 hold
// `1 << WARPEDMODEL_PREC_BITS = 1 << 16`. The other slots
// (0 / 1 / 3 / 4) hold zero for an identity affine.
let identity_scale: i32 = 1 << 16;
for row in gm_params.iter_mut() {
row[2] = identity_scale;
row[5] = identity_scale;
}
Self {
segmentation_update_map: false,
segmentation_temporal_update: false,
seg_feature_active: [[false; crate::uncompressed_header_tail::SEG_LVL_MAX];
MAX_SEGMENTS],
seg_feature_data: [[0; crate::uncompressed_header_tail::SEG_LVL_MAX]; MAX_SEGMENTS],
prev_segment_ids: None,
skip_mode_present: false,
skip_mode_frame: [0, -1],
reference_select: false,
gm_type: [GM_TYPE_IDENTITY; 8],
gm_params,
ref_frame_sign_bias: [0; 8],
allow_high_precision_mv: false,
force_integer_mv: false,
use_ref_frame_mvs: false,
is_motion_mode_switchable: false,
allow_warped_motion: false,
is_scaled_per_ref: [false; 7],
enable_interintra_compound: false,
enable_masked_compound: false,
enable_jnt_comp: false,
order_hints: FrameInterOrderHints::IDENTITY,
interpolation_filter: crate::inter_pred::EIGHTTAP,
enable_dual_filter: false,
motion_field_mvs,
pixels: None,
}
}
}
/// §5.11.22 per-block aggregate surfaced by
/// [`PartitionWalker::decode_intra_block_mode_info`]. Carries every
/// value the §5.11.22 spec body derives or reads in syntax order. The
/// optional fields (`uv_mode`, `cfl_alpha_*`, `angle_delta_uv`,
/// `palette_size_*`, `has_palette_*`, `use_filter_intra`,
/// `filter_intra_mode`) are `None` on the spec's "not read" arms — the
/// caller can rebuild §5.11.22's locally-derived `UVMode = uv_mode` /
/// `PaletteSizeY` / `PaletteSizeUV` / `use_filter_intra = 0`
/// assignments from these fields directly (matching the §5.11.22 spec
/// body's initial-zero / `if`-guarded write sites).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodedIntraBlockModeInfo {
/// §5.11.22 caller-passed mi-unit row of the block.
pub mi_row: u32,
/// §5.11.22 caller-passed mi-unit column of the block.
pub mi_col: u32,
/// §5.11.22 caller-passed `MiSize` ordinal.
pub mi_size: usize,
/// §5.11.22 line 1: `RefFrame[ 0 ] = INTRA_FRAME`, `RefFrame[ 1 ] =
/// NONE`. Always `[0, -1]` on this arm per §6.10.1 / §3.
pub ref_frame: [i32; 2],
/// §5.11.22 line 2: `y_mode` (= `YMode`). In `0..INTRA_MODES = 13`
/// per §3 (the §3 intra-mode enumeration: `DC_PRED = 0` through
/// `PAETH_PRED = 12`).
pub y_mode: u8,
/// §5.11.42 `intra_angle_info_y` result: `AngleDeltaY = angle_delta_y -
/// MAX_ANGLE_DELTA`, in `-MAX_ANGLE_DELTA..=MAX_ANGLE_DELTA` for a
/// directional `YMode`. `0` on the §5.11.42 short-circuit (`MiSize <
/// BLOCK_8X8` or `!is_directional_mode(YMode)`).
pub angle_delta_y: i8,
/// §5.11.22 line 4: `uv_mode` (= `UVMode`). `None` when
/// `!HasChroma` (the §5.11.22 spec body wraps the `uv_mode` read in
/// `if ( HasChroma )`). When present, in `0..UV_INTRA_MODES_CFL_ALLOWED
/// = 14` per §3 (the §3 chroma-mode enumeration, with `UV_CFL_PRED =
/// 13` reachable only on the `cfl_allowed` branch).
pub uv_mode: Option<u8>,
/// §5.11.45 `read_cfl_alphas` result for the U plane. `None` when
/// `UVMode != UV_CFL_PRED` or `!HasChroma`. When the §5.11.45
/// `signU == CFL_SIGN_ZERO` arm fires, the field is `Some(0)` (the
/// spec body sets `CflAlphaU = 0`); otherwise `Some(±(1 +
/// cfl_alpha_u))` per §5.11.45.
pub cfl_alpha_u: Option<i8>,
/// §5.11.45 `read_cfl_alphas` result for the V plane. Mirrors
/// [`Self::cfl_alpha_u`].
pub cfl_alpha_v: Option<i8>,
/// §5.11.43 `intra_angle_info_uv` result. `None` when `!HasChroma`
/// or the §5.11.43 short-circuit fired. Otherwise `AngleDeltaUV =
/// angle_delta_uv - MAX_ANGLE_DELTA`, in
/// `-MAX_ANGLE_DELTA..=MAX_ANGLE_DELTA`.
pub angle_delta_uv: Option<i8>,
/// §5.11.46 `has_palette_y`. `None` when the §5.11.46 spec body's
/// `YMode == DC_PRED` gate is false (or the §5.11.22 outer gate
/// rejects the block); otherwise `Some(0)` / `Some(1)`.
pub has_palette_y: Option<u8>,
/// §5.11.46 `PaletteSizeY = palette_size_y_minus_2 + 2`. `None`
/// when `has_palette_y` was not read or was `0` (the §5.11.46 body
/// then leaves `PaletteSizeY = 0` from §5.11.22 line 11). When
/// present, in `2..=PALETTE_COLORS = 8`.
pub palette_size_y: Option<u8>,
/// §5.11.46 `palette_colors_y[]` — the sorted ascending palette
/// entries for the Y plane. `None` when `has_palette_y` was not
/// read or was `0`. When present, the first `palette_size_y.unwrap()`
/// slots carry the decoded entries (each in `0..(1 << BitDepth)`);
/// remaining slots are zeroed. The §5.11.46 spec body sorts the
/// final array in ascending order; this field carries the sorted
/// result.
pub palette_colors_y: Option<[u16; PALETTE_COLORS]>,
/// §5.11.46 `has_palette_uv`. `None` when the §5.11.46 spec body's
/// `HasChroma && UVMode == DC_PRED` gate is false; otherwise
/// `Some(0)` / `Some(1)`.
pub has_palette_uv: Option<u8>,
/// §5.11.46 `PaletteSizeUV = palette_size_uv_minus_2 + 2`. `None`
/// when `has_palette_uv` was not read or was `0`. When present, in
/// `2..=PALETTE_COLORS = 8`.
pub palette_size_uv: Option<u8>,
/// §5.11.46 `palette_colors_u[]` — the sorted ascending palette
/// entries for the U chroma plane. `None` when `has_palette_uv`
/// was not read or was `0`. When present, the first
/// `palette_size_uv.unwrap()` slots carry the decoded entries
/// (each in `0..(1 << BitDepth)`); remaining slots are zeroed. The
/// §5.11.46 U-plane spec body sorts the array in ascending order;
/// this field carries the sorted result.
pub palette_colors_u: Option<[u16; PALETTE_COLORS]>,
/// §5.11.46 `palette_colors_v[]` — the palette entries for the V
/// chroma plane. `None` when `has_palette_uv` was not read or was
/// `0`. When present, the first `palette_size_uv.unwrap()` slots
/// carry the decoded entries (each in `0..(1 << BitDepth)`);
/// remaining slots are zeroed. **Not sorted.** The §5.11.46 V-plane
/// spec body omits the final `sort()` step the Y and U planes
/// apply; the V plane carries either the `delta_encode_palette_colors_v
/// == 1` signed-delta result (with `Clip1` + modular wrap on
/// `(prev + signed_delta) % maxVal`) or the `== 0` direct
/// L(BitDepth) literals (in source order).
pub palette_colors_v: Option<[u16; PALETTE_COLORS]>,
/// §5.11.24 `use_filter_intra` flag. `None` when the §5.11.24
/// outer gate (`enable_filter_intra && YMode == DC_PRED &&
/// PaletteSizeY == 0 && Max(Block_Width, Block_Height) <= 32`) is
/// false (the spec body's first line sets `use_filter_intra = 0`).
/// When present, in `{0, 1}`.
pub use_filter_intra: Option<u8>,
/// §5.11.24 `filter_intra_mode`. `None` when `use_filter_intra ==
/// 0` (the spec body only reads the mode on the inner `if (
/// use_filter_intra )` arm). When present, in
/// `0..INTRA_FILTER_MODES = 5`.
pub filter_intra_mode: Option<u8>,
}
/// §5.11.23 per-block aggregate surfaced by
/// [`PartitionWalker::decode_inter_block_mode_info`]. Carries the
/// §5.11.23 prologue + §7.10.2 stack-summary + YMode + RefMvIdx + the
/// §5.11.31 `assign_mv` outcome + the full §5.11.23 tail
/// (`read_interintra_mode` per §5.11.28, `read_motion_mode` per
/// §5.11.27, `read_compound_type` per §5.11.29, and the §5.11.x
/// interpolation-filter loop — consumed in that spec order as of
/// r277). Every field below is populated on the `Ok` path.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodedInterBlockModeInfo {
/// §5.11.23 caller-passed mi-unit row of the block.
pub mi_row: u32,
/// §5.11.23 caller-passed mi-unit column of the block.
pub mi_col: u32,
/// §5.11.23 caller-passed `MiSize` ordinal.
pub mi_size: usize,
/// §5.11.25 `read_ref_frames( )` output, post the §5.11.28
/// `RefFrame[ 1 ] = INTRA_FRAME` override (r277). Slot 0 in
/// `INTRA_FRAME = 0..=ALTREF_FRAME = 7`; slot 1 in
/// `NONE = -1 | INTRA_FRAME = 0 | LAST_FRAME = 1..=ALTREF_FRAME = 7`.
/// `(ref_frame[0], ref_frame[1])` pairs are constrained by §5.11.25
/// — single-prediction always has slot 1 = `NONE`; compound
/// prediction has both slots in `1..=7`. `slot 1 = INTRA_FRAME`
/// appears exactly when the §5.11.28 `interintra` arm fired (the
/// §5.11.25 reader itself never produces it); the same override is
/// stamped onto the walker's `RefFrames` grid.
pub ref_frame: [i32; 2],
/// §5.11.23 line 4: `isCompound = RefFrame[ 1 ] > INTRA_FRAME`.
/// True ⇔ the block uses compound prediction (slot 1 in
/// `LAST_FRAME = 1..=ALTREF_FRAME = 7`). False ⇔ slot 1 is
/// `NONE = -1` (or, in a hypothetical post-r170 inter-intra arm,
/// `INTRA_FRAME = 0`; the §5.11.25 reader doesn't currently
/// produce slot-1-INTRA so the latter case is unreachable on this
/// path).
pub is_compound: bool,
/// §5.11.23 YMode after the four-arm dispatch (av1-spec p.74). In
/// `MODE_NEARESTMV..=MODE_NEW_NEWMV = 14..=25`. The §5.11.23 spec
/// body resolves YMode to one of the inter Y-mode ordinals from
/// §6.10.22 — see [`MODE_NEAREST_NEARESTMV`] etc.
pub y_mode: u8,
/// §5.11.23 `RefMvIdx` after the per-mode drl_mode loops. In
/// `0..=2` (§7.10.2 caps `NumMvFound` at `MAX_REF_MV_STACK_SIZE =
/// 8`; the §5.11.23 drl_mode loops cap idx at `2`). On the
/// short-circuit YMode arms (NEAREST_NEARESTMV / GLOBALMV /
/// NEARMV / NEARESTMV / GLOBAL_GLOBALMV / NEAREST_NEWMV /
/// NEW_NEARESTMV) the loops do not run and `ref_mv_idx = 0`.
pub ref_mv_idx: u32,
/// §7.10.2 `NumMvFound` snapshot at the point the §5.11.23
/// post-find_mv_stack cascade fires. In `0..=MAX_REF_MV_STACK_SIZE
/// = 0..=8`.
pub num_mv_found: u32,
/// §7.10.2.14 `NewMvContext` snapshot consumed by the §5.11.23
/// `new_mv` selection (Arm 4). In `0..NEW_MV_CONTEXTS = 0..6`.
pub new_mv_context: u32,
/// §7.10.2.14 `RefMvContext` snapshot consumed by the §5.11.23
/// `ref_mv` selection (Arm 4) AND by [`compound_mode_ctx`] (Arm 3).
/// In `0..REF_MV_CONTEXTS = 0..6`.
pub ref_mv_context: u32,
/// §7.10.2 `ZeroMvContext` snapshot consumed by the §5.11.23
/// `zero_mv` selection (Arm 4). In `0..ZERO_MV_CONTEXTS = 0..2`.
/// Always `0` on the r172 spatial-only path (the temporal scan is
/// the only writer of non-zero `ZeroMvContext`).
pub zero_mv_context: u32,
/// `Mv[ ref ][ comp ]` — the §5.11.31 `assign_mv()` output. Each
/// per-list pair holds the row component in slot `0` and the
/// column component in slot `1`, both in 1/8-luma-sample units
/// (`MV_BORDER`-clamped per §6.10.25). For single-pred blocks
/// (`is_compound == false`) only `mv[0]` is meaningful; `mv[1]`
/// stays at its default `[0, 0]`. For compound blocks both pairs
/// are written.
///
/// Each pair is either:
/// * `GlobalMvs[i]` when `get_mode(i) == MODE_GLOBALMV`
/// (`GLOBAL_GLOBALMV` arm) — no MV bits read,
/// * `RefStackMv[ pos ][ i ]` for the NEAREST / NEAR cases with
/// `pos = ( compMode == NEARESTMV ) ? 0 : RefMvIdx` (forced to
/// `0` when `compMode == NEWMV && NumMvFound <= 1`) — no MV bits
/// read,
/// * `PredMv[i] + diffMv` when `get_mode(i) == MODE_NEWMV`, where
/// `diffMv` is the §5.11.31 `read_mv()` output (§5.11.32
/// `read_mv_component` reads per axis gated on `mv_joint`).
pub mv: [[i32; 2]; 2],
/// §5.11.27 `motion_mode` outcome. One of
/// [`MOTION_MODE_SIMPLE`] / [`MOTION_MODE_OBMC`] /
/// [`MOTION_MODE_WARPED_CAUSAL`]. On the §5.11.27 short-circuit
/// arms (skip_mode / `!is_motion_mode_switchable` / small block /
/// GLOBALMV+non-TRANSLATION gm_type / compound / inter-intra / no
/// overlappable candidates) the value is `SIMPLE` and no S() is
/// read. On the `use_obmc` arm the value is `SIMPLE` or `OBMC`. On
/// the `motion_mode` arm the value is any of the three.
pub motion_mode: u8,
/// §7.10.4 `NumSamples` snapshot returned by
/// [`PartitionWalker::find_warp_samples`] at the §5.11.27 cascade
/// site. Always `0` on the short-circuit arms (the call is skipped
/// when the §5.11.27 prologue gates fire). In `0..=8` (the
/// §3 `LEAST_SQUARES_SAMPLES_MAX` cap). The §5.11.27 `use_obmc` arm
/// fires when this is `0`; the `motion_mode` arm consumes it via
/// the §7.11 warp-affine projection (downstream arc).
pub num_warp_samples: u32,
/// §5.11.28 `read_interintra_mode( )` outcome. Carries the
/// `interintra` flag and — when `interintra == 1` — the
/// `interintra_mode`, `wedge_interintra`, and (when
/// `wedge_interintra == 1`) `wedge_index` values. On the closed
/// outer gate (skip_mode / !enable_interintra_compound /
/// isCompound / outside the `BLOCK_8X8..=BLOCK_32X32` band) the
/// `interintra` flag is `0` and the per-symbol fields are `None`.
pub interintra: InterIntraReadout,
/// §5.11.29 `read_compound_type( )` outcome. Carries the
/// `comp_group_idx` / `compound_idx` (defaults to `0` / `1` on
/// short-circuit), the resolved `compound_type` ordinal, and the
/// optional `wedge_index` / `wedge_sign` (when `compound_type ==
/// COMPOUND_WEDGE`) or `mask_type` (when `compound_type ==
/// COMPOUND_DIFFWTD`).
pub compound_type: CompoundTypeReadout,
/// §5.11.x `read_interpolation_filter( )` outcome — the per-
/// direction `interp_filter[ 0..2 ]` ordinals plus a
/// `read_from_bitstream[ 0..2 ]` flag. See
/// [`InterpolationFilterReadout`].
pub interp_filter: InterpolationFilterReadout,
}
/// §5.11.28 `read_interintra_mode( isCompound )` per-block outcome —
/// the `interintra` flag together with the optional per-symbol values
/// the inner `if ( interintra )` arm reads (`interintra_mode`,
/// `wedge_interintra`, `wedge_index`). When `interintra == 0` (either
/// the outer gate is closed or the `interintra` S() returned 0) every
/// per-symbol field is `None`. When `interintra == 1` the
/// `interintra_mode` and `wedge_interintra` fields are always
/// populated; `wedge_index` is populated only when
/// `wedge_interintra == 1`.
///
/// The spec body also imperatively writes `RefFrame[ 1 ] = INTRA_FRAME`
/// and `AngleDeltaY = AngleDeltaUV = use_filter_intra = 0` on the
/// inner arm; the [`PartitionWalker::read_interintra_mode`] helper
/// returns this struct so the caller can apply those overrides (the
/// §5.11.23 dispatcher does so on the walker grids).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterIntraReadout {
/// §5.11.28 `interintra` flag. `0` ⇔ the outer gate closed OR the
/// `interintra` S() returned 0. `1` ⇔ the full inner arm fired.
pub interintra: u8,
/// §5.11.28 `interintra_mode` — in `0..INTERINTRA_MODES = 0..4`
/// (one of `II_DC_PRED` / `II_V_PRED` / `II_H_PRED` /
/// `II_SMOOTH_PRED`). `Some(_)` ⇔ `interintra == 1`.
pub interintra_mode: Option<u8>,
/// §5.11.28 `wedge_interintra` flag. `Some(0)` or `Some(1)` ⇔
/// `interintra == 1`; `None` otherwise.
pub wedge_interintra: Option<u8>,
/// §5.11.28 `wedge_index` — in `0..WEDGE_TYPES = 0..16`.
/// `Some(_)` ⇔ `interintra == 1 && wedge_interintra == 1`; `None`
/// otherwise.
pub wedge_index: Option<u8>,
}
/// §5.11.29 `read_compound_type( isCompound )` per-block outcome —
/// the §5.11.29 imperatives (`comp_group_idx` / `compound_idx` /
/// `compound_type` plus the optional sub-branch values
/// `wedge_index` / `wedge_sign` / `mask_type`).
///
/// `comp_group_idx` and `compound_idx` carry the §5.11.29 pre-set
/// defaults (`0` and `1`) on every code path; the inner arms overwrite
/// them on the `isCompound && enable_masked_compound` and
/// `isCompound && comp_group_idx == 0 && enable_jnt_comp` arms
/// respectively.
///
/// `compound_type` is always populated and carries one of the §6.10.24
/// ordinals ([`COMPOUND_WEDGE`] / [`COMPOUND_DIFFWTD`] /
/// [`COMPOUND_AVERAGE`] / [`COMPOUND_INTRA`] / [`COMPOUND_DISTANCE`]).
///
/// `wedge_index` / `wedge_sign` are `Some(_)` ⇔ `compound_type ==
/// COMPOUND_WEDGE` (the §5.11.29 wedge sub-branch fired). `mask_type`
/// is `Some(_)` ⇔ `compound_type == COMPOUND_DIFFWTD` (the §5.11.29
/// diffwtd sub-branch fired).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompoundTypeReadout {
/// §5.11.29 `comp_group_idx`. `0` ⇔ the `enable_masked_compound`
/// arm closed or its S() returned `0`; `1` ⇔ the S() returned `1`.
/// Also `0` on the `skip_mode` short-circuit and the
/// `!isCompound` else-arm (the §5.11.29 line-1 pre-set value).
pub comp_group_idx: u8,
/// §5.11.29 `compound_idx`. `1` ⇔ the `enable_jnt_comp` arm
/// closed or its S() returned `1`; `0` ⇔ the S() returned `0`.
/// Also `1` on the `skip_mode` short-circuit and the
/// `!isCompound` else-arm (the §5.11.29 line-2 pre-set value).
pub compound_idx: u8,
/// §5.11.29 `compound_type` — one of [`COMPOUND_WEDGE`] /
/// [`COMPOUND_DIFFWTD`] / [`COMPOUND_AVERAGE`] / [`COMPOUND_INTRA`]
/// / [`COMPOUND_DISTANCE`] per §6.10.24.
pub compound_type: u8,
/// §5.11.29 `wedge_index` — in `0..WEDGE_TYPES = 0..16`.
/// `Some(_)` ⇔ `compound_type == COMPOUND_WEDGE`.
pub wedge_index: Option<u8>,
/// §5.11.29 `wedge_sign` — binary `L(1)`. `Some(_)` ⇔
/// `compound_type == COMPOUND_WEDGE`.
pub wedge_sign: Option<u8>,
/// §5.11.29 `mask_type` — binary `L(1)`, one of [`UNIFORM_45`] /
/// [`UNIFORM_45_INV`] per §6.10.24. `Some(_)` ⇔ `compound_type ==
/// COMPOUND_DIFFWTD`.
pub mask_type: Option<u8>,
}
/// §5.11.x `read_interpolation_filter` per-block outcome — the two
/// per-direction `interp_filter` ordinals plus a flag indicating
/// whether each was read from the bitstream (`true`) or forced from
/// frame-header / fallback state (`false`).
///
/// Slot `[0]` carries the horizontal direction (`dir == 0`); slot
/// `[1]` the vertical (`dir == 1`). Each ordinal is one of
/// [`EIGHTTAP`] / [`EIGHTTAP_SMOOTH`] / [`EIGHTTAP_SHARP`] /
/// [`BILINEAR`] per §6.8.9.
///
/// The §5.11.x reader fills the slots as follows:
///
/// * `interpolation_filter != SWITCHABLE` (frame-level selection) ⇒
/// both slots = `interpolation_filter`, both `read_from_bitstream =
/// false`.
/// * `interpolation_filter == SWITCHABLE && !needs_interp_filter` ⇒
/// slot = `EIGHTTAP`, `read_from_bitstream = false`.
/// * `interpolation_filter == SWITCHABLE && needs_interp_filter` ⇒
/// slot = S() over `TileInterpFilterCdf[ ctx ]`,
/// `read_from_bitstream = true`. When `enable_dual_filter == 0`
/// slot `[1]` mirrors slot `[0]` and its `read_from_bitstream`
/// flag is forced to `false` (the spec body sets `interp_filter[ 1
/// ] = interp_filter[ 0 ]` with no separate read).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InterpolationFilterReadout {
/// Per-direction `interp_filter[ dir ]` ordinal. `[0]` = horizontal,
/// `[1]` = vertical. Each in `0..=BILINEAR = 0..=3`.
pub interp_filter: [u8; 2],
/// Per-direction "this slot was read from the bitstream" flag.
/// `false` on the §5.11.x fallback / forced paths; `true` on the
/// `interpolation_filter == SWITCHABLE && needs_interp_filter`
/// S() path. Useful for caller-side bit-accounting tests.
pub read_from_bitstream: [bool; 2],
}
/// §5.11.39 `coeffs( plane, startX, startY, txSz )` per-TU outcome —
/// the four scalars the §5.11.39 body produces beyond the `Quant[ pos ]`
/// magnitude array itself, plus the `all_zero` short-circuit indicator.
///
/// The reader that returns this aggregate
/// ([`PartitionWalker::coefficients`]) writes the §5.11.39 `Quant[ pos ]`
/// signed levels into the caller's output buffer (one cell per `pos` in
/// `0..segEob`, with `segEob` derived from `txSz`); the aggregate carries
/// the §5.11.39 scalar outputs the §5.11.34 caller needs to drive the
/// surrounding §5.11.42 `reset_block_context` / §8.3.2 `culLevel` /
/// §8.3.2 `dcCategory` context updates.
///
/// On the `all_zero == 1` short-circuit (the §5.11.39 line-5 "all
/// coefficients are zero" arm) `eob == 0`, `cul_level == 0`,
/// `dc_category == 0`, and the output buffer is left as the spec's
/// pre-`all_zero` zero-fill (`Quant[c] = 0` for `c in 0..segEob`).
///
/// `eob` is the §5.11.39 `eob` return value (`0` when `all_zero`,
/// otherwise in `1..=segEob` per the §5.11.39 `eob_pt_*` / `eob_extra`
/// derivation); `cul_level` is the §5.11.39 `Min( 63, culLevel )` clamp;
/// `dc_category` is the §5.11.39 `dcCategory ∈ {0, 1, 2}` (0 ⇔ no DC,
/// 1 ⇔ negative DC, 2 ⇔ positive DC) the §8.3.2 `dc_sign` selector
/// stamps into `AboveDcContext[ plane ][ x4 + i ]` /
/// `LeftDcContext[ plane ][ y4 + i ]`.
///
/// `all_zero` is `true` when the §5.11.39 line-3 `all_zero` S() read
/// returned `1` (the early-out path); the §5.11.34 caller uses this to
/// skip the `reconstruct()` invocation per the §5.11.35
/// `transform_block` line `if ( eob > 0 ) reconstruct(...)`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CoefficientsReadout {
/// `all_zero` — the §5.11.39 line-3 read. `true` ⇔ the TU is entirely
/// zero (no further reads). `false` ⇔ the §5.11.39 EOB / coefficient
/// cascade fired.
pub all_zero: bool,
/// §5.11.39 `eob` — the position past the last non-zero coefficient.
/// `0` ⇔ `all_zero == true`. Otherwise in `1..=segEob`.
pub eob: u32,
/// §5.11.39 `culLevel` after the `Min( 63, culLevel )` clamp — the
/// neighbour-magnitude context the §8.3.2 `all_zero` /
/// `get_coeff_base_ctx` selectors consume on neighbour TUs via
/// `AboveLevelContext[ plane ][ x4 + i ]` /
/// `LeftLevelContext[ plane ][ y4 + i ]`.
pub cul_level: u8,
/// §5.11.39 `dcCategory` — `0` ⇔ no DC coefficient was coded, `1` ⇔
/// negative DC, `2` ⇔ positive DC. The §8.3.2 `dc_sign` selector
/// consumes this on neighbour TUs via `AboveDcContext[ plane ][ x4 +
/// i ]` / `LeftDcContext[ plane ][ y4 + i ]`.
pub dc_category: u8,
}
/// §5.11.34 / §5.11.35 / §5.11.36 per-TU enumeration — one entry per
/// `transform_block( plane, baseX, baseY, txSz, x, y )` invocation the
/// §5.11.34 `residual()` body would emit.
///
/// The §5.11.34 outer dispatch walks the
/// `widthChunks` / `heightChunks` chunk grid (1 chunk per 64×64
/// `MiSize` region per axis), then per-plane: on the inter-luma path it
/// recurses through §5.11.36 `transform_tree(baseX, baseY, w, h)` to
/// the per-TU `transform_block(0, startX, startY, txSz, 0, 0)` leaves;
/// on every other path (intra or chroma) it directly iterates the
/// per-plane block with `stepX` / `stepY` per the §5.11.35 prologue.
///
/// Every leaf is the `transform_block(plane, startX, startY, txSz, x, y)`
/// call site — the per-TU bitstream-read kernel that wraps
/// §5.11.35's predict / coeffs / reconstruct sub-routines.
///
/// The §5.11.34 dispatcher in this crate records every emitted leaf in
/// [`ResidualReadout::tasks`] and, on the `!skip` path, invokes
/// [`PartitionWalker::coefficients`] per leaf to drive the bitstream
/// read; the per-TU `coefficients()` outcome is stored alongside in
/// [`Self::coeffs`]. The `predict_intra` / `predict_palette` /
/// `predict_chroma_from_luma` / `reconstruct` sub-routines that
/// §5.11.35 also calls remain split-off (see [`ResidualReadout`]
/// docs for the explicit split list).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResidualTuTask {
/// `plane` — `0` (luma), `1` (Cb), `2` (Cr).
pub plane: u8,
/// `startX = baseX + 4 * x` per §5.11.35 line 1 — top-left luma-
/// sample x offset of the per-TU prediction / residual region
/// (post-subsampling for `plane > 0`).
pub start_x: u32,
/// `startY = baseY + 4 * y` per §5.11.35 line 2 — top-left luma-
/// sample y offset companion to [`Self::start_x`].
pub start_y: u32,
/// `txSz` — the per-TU `TX_SIZES_ALL` ordinal the §5.11.34 outer
/// dispatch routed to this leaf (post-`get_tx_size( plane,
/// TxSize )` for the chroma path; post-`find_tx_size( w, h )` for
/// the inter-luma `transform_tree` recursion).
pub tx_size: u8,
/// `chunkX` of the §5.11.34 outer loop — `0..widthChunks`. Records
/// the chunk this leaf belongs to. `0` for every block whose
/// `MiSize` is below the 64-luma chunk threshold.
pub chunk_x: u8,
/// `chunkY` of the §5.11.34 outer loop — `0..heightChunks`. `0`
/// for every block whose `MiSize` is below the 64-luma chunk
/// threshold.
pub chunk_y: u8,
/// `true` ⇔ this leaf was emitted by §5.11.34's inter-luma
/// `transform_tree(baseX, baseY, num4x4W * 4, num4x4H * 4)` recursion
/// (per the `is_inter && !Lossless && !plane` arm). `false` ⇔ leaf
/// emitted by the per-plane intra / chroma `for y, for x` direct
/// iteration.
pub from_transform_tree: bool,
/// `PlaneTxType` per §5.11.40 `compute_tx_type( plane, txSz, x4, y4 )`
/// — the per-TU transform type the §7.12.3 step-3 frame-buffer
/// merge consults for the `flipUD` / `flipLR` destination remap.
/// One of the 16 §3 `TxType` ordinals (`DCT_DCT..H_FLIPADST`).
/// `DCT_DCT` on the `skip == true` path (no transform fired, the
/// step-3 merge for the TU is gated to the prediction-only write).
pub plane_tx_type: u8,
}
/// §5.11.34 `residual()` post-walk outcome — the per-block list of
/// per-TU tasks the §5.11.34 outer dispatch emitted, plus the
/// matching per-TU `coefficients()` readouts.
///
/// ## What this readout covers
///
/// * §5.11.34 outer `widthChunks` / `heightChunks` loops with the
/// `Block_Width[MiSize] >> 6` / `Block_Height[MiSize] >> 6`
/// threshold (1 chunk per 64-luma stride per axis; clamped to
/// `Max(1, _)`).
/// * §5.11.34 `miSizeChunk = ( widthChunks > 1 || heightChunks > 1 ) ?
/// BLOCK_64X64 : MiSize` derivation.
/// * §5.11.34 per-plane `for ( plane = 0; plane < 1 + HasChroma * 2;
/// plane++ )` loop with §5.11.37 `get_tx_size(plane, TxSize)` and
/// §5.11.38 `get_plane_residual_size(miSizeChunk, plane)`.
/// * §5.11.34 inter-luma `transform_tree(baseX, baseY, num4x4W * 4,
/// num4x4H * 4)` recursion (per the `is_inter && !Lossless && !plane`
/// arm) via [`PartitionWalker::residual`]'s internal
/// `transform_tree` helper.
/// * §5.11.36 `transform_tree( startX, startY, w, h )` recursion —
/// `w <= lumaW && h <= lumaH` leaf-emit, `w > h` / `w < h` /
/// `w == h` split arms — operating against the walker's
/// `InterTxSizes[ row ][ col ]` grid.
/// * §5.11.34 non-inter / chroma direct iteration via the
/// `for ( y = 0; y < num4x4H; y += stepY ) for ( x = 0; x < num4x4W;
/// x += stepX ) transform_block(plane, ...)` loop with the
/// `stepX = Tx_Width[txSz] >> 2` / `stepY = Tx_Height[txSz] >> 2`
/// step.
/// * §5.11.35 `transform_block` early-return on `startX >= maxX ||
/// startY >= maxY`.
/// * §5.11.35 `if ( !skip ) eob = coeffs( plane, startX, startY, txSz )`
/// per-leaf call via [`PartitionWalker::coefficients`], with the
/// per-TU outcome captured in [`Self::coeffs`].
///
/// ## Explicit split-off list (not implemented in this round)
///
/// The following §5.11.34 / §5.11.35 sub-routines are deferred to
/// subsequent arcs; the dispatcher records the per-TU task but does
/// not invoke them:
///
/// * §5.11.35 `predict_intra(plane, startX, startY, ...)` /
/// `predict_palette(...)` / `predict_chroma_from_luma(...)` (the
/// §7.11.2.x sample-generation kernels). The standalone
/// [`predict_intra_dc_pred`] leaf landed in r180; the remaining
/// §7.11.2.x bodies and the `CurrFrame[plane][y][x]` sample-buffer
/// wiring are split-off.
/// * §5.11.35 `reconstruct(plane, startX, startY, txSz)` — the full
/// §7.12.3 step-1 / §7.13 step-2 / §7.12.3 step-3 chain is now
/// wired through the dispatcher. r182 wires §7.13 inverse
/// transform via [`crate::transform::inverse_transform_2d`]; r183
/// wires §7.12.3 step-1 dequantization via [`dequantize_step1`];
/// r185 wires §7.12.3 step-3 frame-buffer merge (a private
/// `PartitionWalker` helper) writing into the walker's per-plane
/// [`PartitionWalker::curr_frame`] sample buffer (with `flipLR`
/// / `flipUD` destination remapping for the FLIPADST family and
/// a `Clip1` envelope per `BitDepth`). The per-TU `Residual[][]`
/// continues to be captured on [`Self::residuals`] for caller
/// inspection. r204 wires the §9.5.3
/// `Quantizer_Matrix[15][2][QM_TOTAL_SIZE]` table from
/// [`crate::qmatrix`]; the `using_qmatrix && plane_tx_type <
/// IDTX && seg_qm_level < 15` arm of step-1 now evaluates
/// `q2 = Round2( q * Quantizer_Matrix[ ... ], 5 )` per §7.12.3.
/// * §5.11.35 `LoopfilterTxSizes[ plane ][ row ][ col ] = txSz` and
/// `BlockDecoded[ plane ][ row ][ col ] = 1` per-TU stamps. The
/// underlying walker grids are not yet allocated; the dispatcher
/// no-ops on the stamp lines.
/// * §5.11.40 `transform_type( x4, y4, txSz )` per-luma-TU S() read
/// into `TxTypes[ y4 + j ][ x4 + i ]`. The dispatcher defaults the
/// per-TU `txType` to `DCT_DCT` (the §5.11.40 fallback returned by
/// `compute_tx_type` for `Lossless || txSzSqrUp > TX_32X32`),
/// feeding `coefficients()` a `tx_class = TX_CLASS_2D`.
/// * §5.11.41 `compute_tx_type( plane, txSz, x4, y4 )` — full §5.11.41
/// body (`TxTypes[]` lookup, `Mode_To_Txfm[ UVMode ]` fallback,
/// `is_tx_type_in_set` set membership). Defaulted to `DCT_DCT`
/// alongside §5.11.40.
/// * §7.5 `get_scan( txSz )` — the per-`(txSz, PlaneTxType)` scan-table
/// dispatch over `Default_Scan_*` / `Mrow_Scan_*` / `Mcol_Scan_*`. The
/// dispatcher passes `coefficients()` an identity scan `0..segEob`,
/// sufficient for the gate-closed (`all_zero == 1`) bitstream path
/// the §5.11.34 reachable site exercises today.
/// * `AboveLevelContext` / `LeftLevelContext` / `AboveDcContext` /
/// `LeftDcContext` per-plane context arrays. The §8.3.2 `txb_skip_ctx`
/// / `dc_sign_ctx` derivation reads these; the dispatcher passes both
/// contexts as `0` (the first-block / clean-state context — exactly
/// matches the first TU of every reachable §5.11.34 site, where the
/// neighbour arrays are zero-initialized).
/// * §7.12.3 step-1 `Dequant[][]` per-`(plane, pos)` dequantization.
/// `coefficients()` returns the §5.11.39 `Quant[ pos ]` raw signed
/// levels; the r182 dispatcher feeds these directly to the §7.13
/// inverse transform as a placeholder identity dequant (the spec's
/// `Dequant[i][j] = Quant[pos] * q2 / dqDenom` would require
/// per-segment quantizer / Quantizer_Matrix state not yet plumbed).
///
/// ## Skip-path semantics
///
/// On `skip == true` the §5.11.34 / §5.11.35 dispatcher visits every
/// per-TU task site to drive the `LoopfilterTxSizes[]` /
/// `BlockDecoded[]` stamps (no-op on this walker), but does NOT invoke
/// `coefficients()` — the §5.11.35 `if ( !skip )` gate short-circuits
/// the bitstream read. [`Self::coeffs`] is empty on this path.
///
/// On `skip == false` (and `Lossless == false`) the dispatcher invokes
/// `coefficients()` per leaf TU; the per-TU outcomes accumulate in
/// [`Self::coeffs`] in `tasks` order. For each TU with `eob > 0` the
/// dispatcher then invokes
/// [`crate::transform::inverse_transform_2d`] and pushes
/// `Some(residual)` onto [`Self::residuals`]; for `eob == 0` it
/// pushes `None`. The dispatcher returns `Ok(())` cleanly on this
/// path post-r182.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ResidualReadout {
/// `widthChunks = Max( 1, Block_Width[ MiSize ] >> 6 )` per §5.11.34
/// line 3. For `MiSize < BLOCK_64X64` this is `1`. For 64×64
/// blocks this is `1` (Block_Width = 64, `64 >> 6 = 1`). For 128×*
/// blocks the spec yields `2`.
pub width_chunks: u8,
/// `heightChunks = Max( 1, Block_Height[ MiSize ] >> 6 )` companion
/// to [`Self::width_chunks`].
pub height_chunks: u8,
/// `miSizeChunk` per §5.11.34 line 5 — `BLOCK_64X64` on the
/// `widthChunks > 1 || heightChunks > 1` arm, otherwise `MiSize`.
/// The §5.11.38 `get_plane_residual_size(miSizeChunk, plane)` lookup
/// per-plane consumes this.
pub mi_size_chunk: u8,
/// `1 + HasChroma * 2` — the §5.11.34 outer `for plane = 0..1 +
/// HasChroma * 2` loop's bound.
pub num_planes_visited: u8,
/// Per-TU enumeration of every `transform_block` leaf the §5.11.34
/// outer dispatch emitted. Ordered as the §5.11.34 spec body
/// emits them: `chunkY` outer, `chunkX` next, `plane` next, then
/// the per-plane `(y, x)` step inside `transform_block`'s direct
/// iteration (or §5.11.36 `transform_tree` DFS for the inter-luma
/// arm).
pub tasks: Vec<ResidualTuTask>,
/// Per-TU `coefficients()` outcomes — one entry per `tasks` entry
/// on the `!skip && !Lossless` path; empty on the `skip == true`
/// path. On the `Lossless == true` path the §5.11.34 forces
/// `txSz = TX_4X4` and the coefficient read still fires per TU.
pub coeffs: Vec<CoefficientsReadout>,
/// r182: per-TU `Residual[][]` arrays produced by §7.13.3
/// [`crate::transform::inverse_transform_2d`]. One entry per
/// `tasks` entry on the `!skip && eob > 0` path; entries are
/// `Some(residual)` when the inverse-transform fired, `None`
/// when the TU was all-zero (`eob == 0`) or the `skip` gate
/// suppressed the coefficient read.
///
/// Each `Vec<i64>` is the row-major `w * h` `Residual` buffer
/// (`w = Tx_Width[tx_sz]`, `h = Tx_Height[tx_sz]`). r185 wires
/// the §7.12.3 step-3 frame-buffer merge into the dispatcher
/// (`PartitionWalker::curr_frame_step3_merge`); the buffer is
/// mutated in-place and observable via
/// [`PartitionWalker::curr_frame`]. r183 wires the §7.12.3
/// step-1 dequantization via [`dequantize_step1`]; the §9.5.3
/// `Quantizer_Matrix` arm is still pending (the `using_qmatrix
/// && plane_tx_type < IDTX && seg_qm_level < 15` path falls
/// through to the no-QM identity). The §7.13 inverse transform
/// itself is bit-exact per spec.
pub residuals: Vec<Option<Vec<i64>>>,
/// `skip` — routed through from the §5.11.5 walker's `skip` field
/// (the per-block `skip` syntax element from §5.11.11). Recorded
/// for caller-side reconstruction-pass scheduling.
pub skip: bool,
}
/// §5.11.33 `compute_prediction()` per-plane enumeration — one entry
/// per `(plane, y, x)` prediction-region the §5.11.33 body visits
/// inside its `for ( plane ) { ... }` outer loop (with the `is_inter`
/// inner `for y for x` tiling expanded into discrete tasks).
///
/// On the §5.11.33 intra path (`is_inter == 0`) every plane produces
/// a single task covering the full per-plane residual block
/// (`baseX`, `baseY`, `log2W`, `log2H` from §5.11.33 prologue
/// lines). On the inter path the §5.11.33 `predict_inter` body emits
/// one task per `(y, x)` step of the `(predW, predH)` tiling.
///
/// `mode` carries the §5.11.33 `predict_intra` `mode` argument (intra
/// path) or the §5.11.33 `predict_inter` placeholder
/// [`COMPUTE_PRED_MODE_INTER`] sentinel (inter path — the
/// `predict_inter` body needs MV / ref-frame state, not a mode).
///
/// `have_above_chroma` / `have_left_chroma` are the §5.11.33
/// `AvailUChroma` / `AvailLChroma` flags routed through the
/// §5.11.5 prologue for the chroma planes; for plane `0` they
/// collapse to `AvailU` / `AvailL`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlanePredictionTask {
/// `plane` — `0` (luma), `1` (Cb), `2` (Cr).
pub plane: u8,
/// `baseX + x` per §5.11.33 `predict_intra` / `predict_inter`
/// arguments — the top-left luma-sample x offset of the
/// prediction region (post-subsampling for `plane > 0`).
pub start_x: u32,
/// `baseY + y` per §5.11.33 — the top-left y offset.
pub start_y: u32,
/// `log2W` for the §5.11.33 `predict_intra` call (intra path).
/// On the inter path this carries `log2(predW)` (derived from
/// `Block_Width[ MiSize ] >> subX` or `num4x4W * 4` per the
/// `someUseIntra` arm).
pub log2_w: u32,
/// `log2H` companion to [`Self::log2_w`].
pub log2_h: u32,
/// `mode` — for the intra path, the §5.11.33 `predict_intra`
/// mode argument (`DC_PRED` / `V_PRED` / `H_PRED` / etc., one of
/// the §6.10.x intra-mode ordinals `0..INTRA_MODES`). For the
/// inter path, [`COMPUTE_PRED_MODE_INTER`] (= 255).
pub mode: u8,
/// `haveLeft` argument routed into `predict_intra` (intra) or the
/// §5.11.33 `predict_inter` `candCol` neighbour gate. For
/// `plane == 0` this is `AvailL`; for `plane > 0` this is
/// `AvailLChroma`.
pub have_left: bool,
/// `haveAbove` companion to [`Self::have_left`].
pub have_above: bool,
}
/// §5.11.33 `compute_prediction()` post-walk outcome — the
/// per-plane prediction-task list plus the §5.11.33 derived flags
/// (`is_inter_intra`, `is_inter`, `num_planes_visited`).
///
/// The §5.11.33 body itself is a `for plane = 0..1 + HasChroma * 2`
/// loop with two inner gates: `IsInterIntra` (one `predict_intra`
/// per plane on `RefFrame[1] == INTRA_FRAME` blocks) and `is_inter`
/// (one or more `predict_inter` calls per plane). On the reachable
/// path (the §5.11.5 walker emits `is_inter == 0` blocks — the only
/// arm where `YMode != DC_PRED` is still next-arc; today luma always
/// uses the `intra_frame_y_mode` reader's default DC_PRED), the
/// outer plane loop fires `predict_intra` once per plane.
///
/// `tasks` carries one [`PlanePredictionTask`] per `(plane, y, x)`
/// task the §5.11.33 body emits. The length is bounded by `num_planes
/// * (num4x4H * num4x4W)` (the inter path's worst-case), or by
/// `num_planes` on the pure-intra path (one task per plane).
///
/// On the §5.11.33 reachable path with no chroma (e.g. monochrome
/// streams or sub-8 luma edge cases), `tasks.len() == 1`. On the
/// 3-plane intra path with `has_chroma == true`, `tasks.len() == 3`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComputePredictionReadout {
/// §5.11.33 inner `IsInterIntra = is_inter && RefFrame[1] ==
/// INTRA_FRAME`. `false` for every intra-only block (the only
/// reachable path today).
pub is_inter_intra: bool,
/// §5.11.33 `is_inter` flag (routed through from the §5.11.5
/// walker's `is_inter` derivation).
pub is_inter: bool,
/// §5.11.33 outer loop bound `1 + HasChroma * 2` — the number of
/// planes the §5.11.33 body visited. `1` (luma only) when
/// `has_chroma == false`; `3` (luma + Cb + Cr) when
/// `has_chroma == true`.
pub num_planes_visited: u8,
/// Per-plane prediction tasks in §5.11.33 spec order (plane 0
/// first, then 1, then 2; within a plane: `y` outer, `x` inner
/// for the inter path). One entry on the intra path.
pub tasks: Vec<PlanePredictionTask>,
}
/// Sentinel mode value carried by [`PlanePredictionTask::mode`] on
/// the §5.11.33 `is_inter == 1` arm (the §5.11.33 `predict_inter`
/// body needs MV / ref-frame state, not a mode ordinal). Distinct
/// from every §6.10.x `INTRA_MODES = 13` ordinal value.
pub const COMPUTE_PRED_MODE_INTER: u8 = 255;
#[cfg(test)]
mod tests {
use super::*;
use crate::symbol_decoder::SymbolDecoder;
/// Helper for §5.11.23 decode_inter_block_mode_info tests: builds
/// the §5.9.24 identity-default `gm_params` table
/// (`gm_params[ref][2] = gm_params[ref][5] = 1 <<
/// WARPEDMODEL_PREC_BITS`, other slots = 0). Returns the matrix
/// alone; pair with `[GM_TYPE_IDENTITY; 8]` for the §7.10.2.1
/// identity short-circuit.
fn identity_gm_params() -> [[i32; 6]; 8] {
let mut params = [[0i32; 6]; 8];
for p in &mut params {
p[2] = 1 << WARPEDMODEL_PREC_BITS;
p[5] = 1 << WARPEDMODEL_PREC_BITS;
}
params
}
/// r180: `SUBSAMPLED_SIZE` table-shape sanity. Every plane-0
/// `(subx=0, suby=0)` cell is a pass-through (equals the
/// `subsize` index), and every cell is either a `BLOCK_*`
/// ordinal in `0..BLOCK_SIZES` or the `BLOCK_INVALID` sentinel.
#[test]
fn subsampled_size_shape_holds() {
assert_eq!(SUBSAMPLED_SIZE.len(), BLOCK_SIZES);
for (i, row) in SUBSAMPLED_SIZE.iter().enumerate() {
assert_eq!(row[0][0], i, "plane 0 (subx=0,suby=0) is pass-through");
for (sx, sx_row) in row.iter().enumerate() {
for (sy, &v) in sx_row.iter().enumerate() {
assert!(
v < BLOCK_SIZES || v == BLOCK_INVALID,
"row {i} ({sx},{sy}) = {v} must be a BLOCK_* or BLOCK_INVALID",
);
}
}
}
}
/// r180: §7.11.2.5 DC_PRED no-neighbour fallback writes
/// `1 << (BitDepth - 1)`.
#[test]
fn dc_pred_no_neighbour_fallback_bit_depths() {
let none: [u16; 0] = [];
let mut pred = [0u16; 16];
predict_intra_dc_pred(0, 0, 2, 2, 4, 4, 8, &none, &none, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 128));
predict_intra_dc_pred(0, 0, 2, 2, 4, 4, 10, &none, &none, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 512));
predict_intra_dc_pred(0, 0, 2, 2, 4, 4, 12, &none, &none, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 2048));
}
/// r180: `clip1_bit_depth` saturates to `(1 << BitDepth) - 1`.
#[test]
fn clip1_bit_depth_saturates() {
assert_eq!(clip1_bit_depth(10, 8), 10);
assert_eq!(clip1_bit_depth(300, 8), 255);
assert_eq!(clip1_bit_depth(2000, 10), 1023);
assert_eq!(clip1_bit_depth(8000, 12), 4095);
}
/// r186: §7.11.2.4 step 10 V_PRED — every row is a copy of
/// `AboveRow[ 0..w-1 ]`. Verify on a 4×4 block with distinct
/// per-column above samples.
#[test]
fn v_pred_broadcasts_above_row_into_every_row() {
let above = [10u16, 20, 30, 40];
let mut pred = [0u16; 16];
predict_intra_v_pred(4, 4, &above, &mut pred).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(
pred[i * 4 + j],
above[j],
"row {i} col {j} must equal AboveRow[{j}]"
);
}
}
}
/// r186: §7.11.2.4 step 11 H_PRED — every column is a copy of
/// `LeftCol[ 0..h-1 ]`. Verify on a 4×4 block with distinct
/// per-row left samples.
#[test]
fn h_pred_broadcasts_left_col_into_every_col() {
let left = [10u16, 20, 30, 40];
let mut pred = [0u16; 16];
predict_intra_h_pred(4, 4, &left, &mut pred).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(
pred[i * 4 + j],
left[i],
"row {i} col {j} must equal LeftCol[{i}]"
);
}
}
}
/// r186: V_PRED on a rectangular `w=8, h=4` block exercises the
/// non-square case (row stride differs from `h`).
#[test]
fn v_pred_rectangular_8x4_uses_w_for_row_stride() {
let above = [1u16, 2, 3, 4, 5, 6, 7, 8];
let mut pred = [0u16; 32];
predict_intra_v_pred(8, 4, &above, &mut pred).unwrap();
for i in 0..4 {
for j in 0..8 {
assert_eq!(pred[i * 8 + j], above[j]);
}
}
}
/// r186: H_PRED on a rectangular `w=4, h=8` block — eight rows
/// of constant samples, indexed by `i`.
#[test]
fn h_pred_rectangular_4x8_uses_left_per_row() {
let left = [100u16, 101, 102, 103, 104, 105, 106, 107];
let mut pred = [0u16; 32];
predict_intra_h_pred(4, 8, &left, &mut pred).unwrap();
for i in 0..8 {
for j in 0..4 {
assert_eq!(pred[i * 4 + j], left[i]);
}
}
}
/// r186: V_PRED admits the largest §3 transform width (64) and
/// height (64). The §7.11.2.4 step-10 body has no bit-depth
/// dependence on the input samples themselves — the values are
/// just copied — so a `2^16 - 1`-sized sample (the largest a
/// 12-bit decoded sample can be) round-trips intact.
#[test]
fn v_pred_max_size_64x64_round_trips_12bit_samples() {
let above = vec![4095u16; 64];
let mut pred = vec![0u16; 64 * 64];
predict_intra_v_pred(64, 64, &above, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 4095));
}
/// r186: H_PRED admits the largest §3 transform height (64).
#[test]
fn h_pred_max_size_64x64_round_trips_12bit_samples() {
let left = vec![4095u16; 64];
let mut pred = vec![0u16; 64 * 64];
predict_intra_h_pred(64, 64, &left, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 4095));
}
/// r186: V_PRED only reads `above_row[ 0..w-1 ]`. Trailing slots
/// (`w..w+h-1` carried by a real `AboveRow` array per §7.11.2.1)
/// are ignored on the step-10 path. Verify the helper writes
/// only the first `w` slots' values.
#[test]
fn v_pred_ignores_above_row_past_w() {
let above = [1u16, 2, 3, 4, 99, 99, 99, 99];
let mut pred = [0u16; 16];
predict_intra_v_pred(4, 4, &above, &mut pred).unwrap();
for j in 0..4 {
assert_eq!(pred[j], above[j]);
}
// Sanity: none of the trailing `99`-sentinel slots leaked
// into the prediction buffer (only `1..=4` should appear).
assert!(pred.iter().all(|&v| (1..=4).contains(&v)));
}
/// r186: H_PRED only reads `left_col[ 0..h-1 ]`.
#[test]
fn h_pred_ignores_left_col_past_h() {
let left = [1u16, 2, 3, 4, 99, 99, 99, 99];
let mut pred = [0u16; 16];
predict_intra_h_pred(4, 4, &left, &mut pred).unwrap();
for i in 0..4 {
assert_eq!(pred[i * 4], left[i]);
}
assert!(pred.iter().all(|&v| (1..=4).contains(&v)));
}
/// r186: V_PRED caller-bug guards — undersized `above_row`,
/// undersized `pred`, out-of-range `w` / `h`.
#[test]
fn v_pred_caller_bug_guards() {
let above = [1u16; 4];
let mut pred = [0u16; 16];
// w > 64 — caller bug.
assert!(matches!(
predict_intra_v_pred(65, 4, &above, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// h > 64 — caller bug.
assert!(matches!(
predict_intra_v_pred(4, 65, &above, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// above_row.len() < w — caller bug.
let short_above = [1u16; 3];
assert!(matches!(
predict_intra_v_pred(4, 4, &short_above, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// pred.len() < h * w — caller bug.
let mut short_pred = [0u16; 8];
assert!(matches!(
predict_intra_v_pred(4, 4, &above, &mut short_pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r186: H_PRED caller-bug guards — undersized `left_col`,
/// undersized `pred`, out-of-range `w` / `h`.
#[test]
fn h_pred_caller_bug_guards() {
let left = [1u16; 4];
let mut pred = [0u16; 16];
assert!(matches!(
predict_intra_h_pred(65, 4, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
predict_intra_h_pred(4, 65, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
let short_left = [1u16; 3];
assert!(matches!(
predict_intra_h_pred(4, 4, &short_left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
let mut short_pred = [0u16; 8];
assert!(matches!(
predict_intra_h_pred(4, 4, &left, &mut short_pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r186: §3 / §6.10.x intra-mode ordinals — `DC_PRED == 0`,
/// `V_PRED == 1`, `H_PRED == 2`. Pins the constants the dispatcher
/// gate consults.
#[test]
fn intra_mode_ordinals_dc_v_h_match_spec() {
assert_eq!(DC_PRED, 0);
assert_eq!(V_PRED, 1);
assert_eq!(H_PRED, 2);
}
/// r186: §7.11.2.1 `AboveRow[]` arm 3 — `haveAbove == 1` reads
/// `CurrFrame[plane][y-1][x..x+w-1]` (clamped by `aboveLimit`).
/// Build a 4-row × 8-col buffer with a recognisable above row and
/// confirm the derivation picks it up.
#[test]
fn derive_above_row_have_above_reads_curr_frame_top_row() {
// 4 rows × 8 cols. Row 0 = [10, 20, 30, 40, 50, 60, 70, 80];
// other rows = 0.
let mut frame = vec![0i32; 4 * 8];
for (j, slot) in frame.iter_mut().take(8).enumerate() {
*slot = ((j + 1) * 10) as i32;
}
let mut above = [0u16; 8];
// TU at (x=0, y=1), w=4, h=4 — reads row 0.
derive_above_row(
/* have_above = */ 1, /* have_left = */ 0, /* have_above_right = */ 0,
/* x = */ 0, /* y = */ 1, /* w = */ 4, /* h = */ 4,
/* max_x = */ 7, &frame, /* curr_frame_cols = */ 8, /* bit_depth = */ 8,
&mut above,
)
.unwrap();
assert_eq!(&above[..4], &[10u16, 20, 30, 40]);
// The `aboveLimit = Min(7, 0 + 4 - 1) = 3` clamp pins the
// tail to `CurrFrame[0][3] = 40`.
assert_eq!(&above[4..8], &[40u16; 4]);
}
/// r186: §7.11.2.1 `AboveRow[]` arm 3 — `haveAboveRight == 1`
/// extends the tail with samples up to `x + 2w - 1`.
#[test]
fn derive_above_row_have_above_right_extends_tail() {
let mut frame = vec![0i32; 4 * 8];
for (j, slot) in frame.iter_mut().take(8).enumerate() {
*slot = ((j + 1) * 10) as i32;
}
let mut above = [0u16; 8];
derive_above_row(
1, 0, /* have_above_right = */ 1, 0, 1, 4, 4, 7, &frame, 8, 8, &mut above,
)
.unwrap();
// aboveLimit = Min(7, 0 + 8 - 1) = 7 → full row visible.
assert_eq!(&above[..8], &[10u16, 20, 30, 40, 50, 60, 70, 80]);
}
/// r186: §7.11.2.1 `AboveRow[]` arm 3 — `max_x` clamps the tail
/// before `x + 2w - 1` does (frame-right-boundary case).
#[test]
fn derive_above_row_max_x_clamps_tail() {
let mut frame = vec![0i32; 4 * 4];
for (j, slot) in frame.iter_mut().take(4).enumerate() {
*slot = ((j + 1) * 10) as i32;
}
let mut above = [0u16; 8];
// x=0, w=4, have_above_right=1 ⇒ extend would request col 7,
// but max_x = 3 (4-col frame). Clamp pins everything past col 3
// to frame[3] = 40.
derive_above_row(
1, 0, 1, 0, 1, 4, 4, /* max_x = */ 3, &frame, 4, 8, &mut above,
)
.unwrap();
assert_eq!(&above[..4], &[10u16, 20, 30, 40]);
assert_eq!(&above[4..], &[40u16; 4]);
}
/// r186: §7.11.2.1 `AboveRow[]` arm 1 — `haveAbove == 0 &&
/// haveLeft == 1` propagates `CurrFrame[plane][y][x-1]` (a single
/// sample) into every cell.
#[test]
fn derive_above_row_have_left_only_propagates_left_sample() {
let mut frame = vec![0i32; 4 * 4];
// Place 123 at (row=1, col=0); test reads (y=1, x=1) ⇒ col=0.
frame[4] = 123;
let mut above = [0u16; 8];
derive_above_row(
/* have_above = */ 0, /* have_left = */ 1, 0, /* x = */ 1,
/* y = */ 1, 4, 4, 3, &frame, 4, 8, &mut above,
)
.unwrap();
assert!(above[..8].iter().all(|&v| v == 123));
}
/// r186: §7.11.2.1 `AboveRow[]` arm 2 — `haveAbove == 0 &&
/// haveLeft == 0` fills with `(1 << (BitDepth - 1)) - 1`
/// (mid-grey minus one).
#[test]
fn derive_above_row_no_neighbour_fills_mid_grey_minus_one() {
let frame = vec![0i32; 4 * 4];
let mut above = [0u16; 8];
derive_above_row(
0, 0, 0, 0, 0, 4, 4, 3, &frame, 4, /* bit_depth = */ 8, &mut above,
)
.unwrap();
assert!(above[..8].iter().all(|&v| v == 127));
let mut above10 = [0u16; 8];
derive_above_row(0, 0, 0, 0, 0, 4, 4, 3, &frame, 4, 10, &mut above10).unwrap();
assert!(above10[..8].iter().all(|&v| v == 511));
let mut above12 = [0u16; 8];
derive_above_row(0, 0, 0, 0, 0, 4, 4, 3, &frame, 4, 12, &mut above12).unwrap();
assert!(above12[..8].iter().all(|&v| v == 2047));
}
/// r186: §7.11.2.1 `LeftCol[]` arm 3 — `haveLeft == 1` reads
/// `CurrFrame[plane][y..y+h-1][x-1]` (clamped by `leftLimit`).
#[test]
fn derive_left_col_have_left_reads_curr_frame_left_col() {
// 8 rows × 4 cols. Col 0 = [10, 20, 30, 40, 50, 60, 70, 80].
let mut frame = vec![0i32; 8 * 4];
for i in 0..8 {
frame[i * 4] = ((i + 1) * 10) as i32;
}
let mut left = [0u16; 8];
derive_left_col(
/* have_left = */ 1, /* have_above = */ 0, /* have_below_left = */ 0,
/* x = */ 1, // col-0 reads x - 1 = 0
/* y = */ 0, /* w = */ 4, /* h = */ 4, /* max_y = */ 7, &frame,
/* curr_frame_cols = */ 4, /* bit_depth = */ 8, &mut left,
)
.unwrap();
assert_eq!(&left[..4], &[10u16, 20, 30, 40]);
// leftLimit = Min(7, 0 + 4 - 1) = 3 ⇒ tail pins to row-3
// sample (= 40).
assert_eq!(&left[4..8], &[40u16; 4]);
}
/// r186: §7.11.2.1 `LeftCol[]` arm 3 — `haveBelowLeft == 1`
/// extends the tail with samples up to `y + 2h - 1`.
#[test]
fn derive_left_col_have_below_left_extends_tail() {
let mut frame = vec![0i32; 8 * 4];
for i in 0..8 {
frame[i * 4] = ((i + 1) * 10) as i32;
}
let mut left = [0u16; 8];
derive_left_col(
1, 0, /* have_below_left = */ 1, 1, 0, 4, 4, 7, &frame, 4, 8, &mut left,
)
.unwrap();
// leftLimit = Min(7, 0 + 8 - 1) = 7 → full column visible.
assert_eq!(&left[..8], &[10u16, 20, 30, 40, 50, 60, 70, 80]);
}
/// r186: §7.11.2.1 `LeftCol[]` arm 1 — `haveLeft == 0 &&
/// haveAbove == 1` propagates `CurrFrame[plane][y-1][x]`.
#[test]
fn derive_left_col_have_above_only_propagates_above_sample() {
let mut frame = vec![0i32; 4 * 4];
frame[1] = 200; // row 0, col 1
let mut left = [0u16; 8];
derive_left_col(
0, 1, 0, /* x = */ 1, /* y = */ 1, 4, 4, 3, &frame, 4, 8, &mut left,
)
.unwrap();
assert!(left[..8].iter().all(|&v| v == 200));
}
/// r186: §7.11.2.1 `LeftCol[]` arm 2 — no-neighbour fill is
/// `(1 << (BitDepth - 1)) + 1` (mid-grey plus one). Note the
/// asymmetry vs `AboveRow[]`'s `-1`.
#[test]
fn derive_left_col_no_neighbour_fills_mid_grey_plus_one() {
let frame = vec![0i32; 4 * 4];
let mut left = [0u16; 8];
derive_left_col(0, 0, 0, 0, 0, 4, 4, 3, &frame, 4, 8, &mut left).unwrap();
assert!(left[..8].iter().all(|&v| v == 129));
let mut left10 = [0u16; 8];
derive_left_col(0, 0, 0, 0, 0, 4, 4, 3, &frame, 4, 10, &mut left10).unwrap();
assert!(left10[..8].iter().all(|&v| v == 513));
}
/// r186: end-to-end V_PRED through the §7.11.2.1 derivation —
/// `derive_above_row` → `predict_intra_v_pred` produces a block
/// whose every row is a copy of the top CurrFrame row.
#[test]
fn v_pred_end_to_end_with_derive_above_row() {
let mut frame = vec![0i32; 4 * 4];
for (j, slot) in frame.iter_mut().take(4).enumerate() {
*slot = ((j + 1) * 16) as i32; // row 0 = [16, 32, 48, 64]
}
let mut above = [0u16; 8];
derive_above_row(1, 0, 0, 0, 1, 4, 4, 3, &frame, 4, 8, &mut above).unwrap();
let mut pred = [0u16; 16];
predict_intra_v_pred(4, 4, &above, &mut pred).unwrap();
for i in 0..4 {
assert_eq!(&pred[i * 4..i * 4 + 4], &[16u16, 32, 48, 64]);
}
}
/// r186: end-to-end H_PRED through the §7.11.2.1 derivation —
/// `derive_left_col` → `predict_intra_h_pred` produces a block
/// whose every column is a copy of the left CurrFrame column.
#[test]
fn h_pred_end_to_end_with_derive_left_col() {
let mut frame = vec![0i32; 4 * 4];
for i in 0..4 {
frame[i * 4] = ((i + 1) * 16) as i32; // col 0 = [16, 32, 48, 64]
}
let mut left = [0u16; 8];
derive_left_col(1, 0, 0, 1, 0, 4, 4, 3, &frame, 4, 8, &mut left).unwrap();
let mut pred = [0u16; 16];
predict_intra_h_pred(4, 4, &left, &mut pred).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(pred[i * 4 + j], ((i + 1) * 16) as u16);
}
}
}
/// r186: `derive_above_row` caller-bug guards.
#[test]
fn derive_above_row_caller_bug_guards() {
let frame = vec![0i32; 16];
let mut above = [0u16; 8];
// w > 64.
assert!(matches!(
derive_above_row(0, 0, 0, 0, 0, 65, 4, 0, &frame, 4, 8, &mut above),
Err(crate::Error::PartitionWalkOutOfRange)
));
// bit_depth out of range.
assert!(matches!(
derive_above_row(0, 0, 0, 0, 0, 4, 4, 0, &frame, 4, /* bit_depth = */ 9, &mut above),
Err(crate::Error::PartitionWalkOutOfRange)
));
// above_row too small.
let mut small = [0u16; 4];
assert!(matches!(
derive_above_row(0, 0, 0, 0, 0, 4, 4, 0, &frame, 4, 8, &mut small),
Err(crate::Error::PartitionWalkOutOfRange)
));
// curr_frame_cols == 0.
assert!(matches!(
derive_above_row(0, 0, 0, 0, 0, 4, 4, 0, &frame, 0, 8, &mut above),
Err(crate::Error::PartitionWalkOutOfRange)
));
// have_above == 1 with y == 0 (caller bug — no row -1).
assert!(matches!(
derive_above_row(1, 0, 0, 0, /* y = */ 0, 4, 4, 3, &frame, 4, 8, &mut above),
Err(crate::Error::PartitionWalkOutOfRange)
));
// have_left == 1 with have_above == 0, x == 0 (caller bug
// — no col -1).
assert!(matches!(
derive_above_row(0, 1, 0, /* x = */ 0, 1, 4, 4, 3, &frame, 4, 8, &mut above),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r186: `derive_left_col` caller-bug guards (mirror of above).
#[test]
fn derive_left_col_caller_bug_guards() {
let frame = vec![0i32; 16];
let mut left = [0u16; 8];
assert!(matches!(
derive_left_col(0, 0, 0, 0, 0, 4, 65, 0, &frame, 4, 8, &mut left),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
derive_left_col(0, 0, 0, 0, 0, 4, 4, 0, &frame, 4, 7, &mut left),
Err(crate::Error::PartitionWalkOutOfRange)
));
// have_left == 1 with x == 0 (no col -1).
assert!(matches!(
derive_left_col(1, 0, 0, /* x = */ 0, 0, 4, 4, 3, &frame, 4, 8, &mut left),
Err(crate::Error::PartitionWalkOutOfRange)
));
// have_above == 1 with y == 0 (no row -1).
assert!(matches!(
derive_left_col(0, 1, 0, 1, /* y = */ 0, 4, 4, 3, &frame, 4, 8, &mut left),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
// -----------------------------------------------------------------
// r187 — §7.11.2.1 corner + §7.11.2.2 PAETH + §7.11.2.6 SMOOTH*
// -----------------------------------------------------------------
/// r187: §6.10.x intra-mode ordinals — `SMOOTH_PRED == 9`,
/// `SMOOTH_V_PRED == 10`, `SMOOTH_H_PRED == 11`, `PAETH_PRED == 12`.
#[test]
fn intra_mode_ordinals_smooth_and_paeth_match_spec() {
assert_eq!(SMOOTH_PRED, 9);
assert_eq!(SMOOTH_V_PRED, 10);
assert_eq!(SMOOTH_H_PRED, 11);
assert_eq!(PAETH_PRED, 12);
}
/// r187: `Sm_Weights_Tx_*` tables — verbatim transcription pin
/// against av1-spec p.508. Boundary values from each table.
#[test]
fn sm_weights_tables_match_spec_transcription() {
// 4x4.
assert_eq!(SM_WEIGHTS_TX_4X4, [255, 149, 85, 64]);
// 8x8.
assert_eq!(SM_WEIGHTS_TX_8X8, [255, 197, 146, 105, 73, 50, 37, 32]);
// 16x16 — first / last entries.
assert_eq!(SM_WEIGHTS_TX_16X16[0], 255);
assert_eq!(SM_WEIGHTS_TX_16X16[15], 16);
assert_eq!(SM_WEIGHTS_TX_16X16.len(), 16);
// 32x32 — first / last entries.
assert_eq!(SM_WEIGHTS_TX_32X32[0], 255);
assert_eq!(SM_WEIGHTS_TX_32X32[31], 8);
assert_eq!(SM_WEIGHTS_TX_32X32.len(), 32);
// 64x64 — first / last entries.
assert_eq!(SM_WEIGHTS_TX_64X64[0], 255);
assert_eq!(SM_WEIGHTS_TX_64X64[63], 4);
assert_eq!(SM_WEIGHTS_TX_64X64.len(), 64);
}
/// r187: §7.11.2.1 corner-cell arm 1 — `(haveAbove, haveLeft) ==
/// (1, 1)` reads `CurrFrame[plane][y-1][x-1]`.
#[test]
fn derive_above_left_arm1_reads_top_left_diagonal() {
// 4×4 frame, place `77` at (row=0, col=0).
let mut frame = vec![0i32; 16];
frame[0] = 77;
// TU at (x=1, y=1) → corner reads (0, 0).
let v = derive_above_left(1, 1, 1, 1, &frame, 4, 8).unwrap();
assert_eq!(v, 77);
}
/// r187: §7.11.2.1 corner-cell arm 2 — `(haveAbove, haveLeft) ==
/// (1, 0)` reads `CurrFrame[plane][y-1][x]`.
#[test]
fn derive_above_left_arm2_reads_above_only_sample() {
let mut frame = vec![0i32; 16];
frame[2] = 88; // (row=0, col=2)
let v = derive_above_left(1, 0, 2, 1, &frame, 4, 8).unwrap();
assert_eq!(v, 88);
}
/// r187: §7.11.2.1 corner-cell arm 3 — `(haveAbove, haveLeft) ==
/// (0, 1)` reads `CurrFrame[plane][y][x-1]`.
#[test]
fn derive_above_left_arm3_reads_left_only_sample() {
let mut frame = vec![0i32; 16];
frame[2 * 4 + 1] = 99; // (row=2, col=1)
let v = derive_above_left(0, 1, 2, 2, &frame, 4, 8).unwrap();
assert_eq!(v, 99);
}
/// r187: §7.11.2.1 corner-cell arm 4 — both unavailable returns
/// the symmetric mid-grey `1 << (BitDepth - 1)`. Note this differs
/// from the `AboveRow[]` (`mid - 1`) / `LeftCol[]` (`mid + 1`)
/// asymmetric fills.
#[test]
fn derive_above_left_no_neighbour_returns_mid_grey() {
let frame = vec![0i32; 16];
assert_eq!(derive_above_left(0, 0, 0, 0, &frame, 4, 8).unwrap(), 128);
assert_eq!(derive_above_left(0, 0, 0, 0, &frame, 4, 10).unwrap(), 512);
assert_eq!(derive_above_left(0, 0, 0, 0, &frame, 4, 12).unwrap(), 2048);
}
/// r187: `derive_above_left` clips negative / over-range samples
/// in the buffer to `[0, (1 << BitDepth) - 1]`.
#[test]
fn derive_above_left_clips_to_bit_depth_range() {
let mut frame = vec![0i32; 16];
frame[0] = -50;
frame[1] = 99_999;
assert_eq!(derive_above_left(1, 1, 1, 1, &frame, 4, 8).unwrap(), 0);
assert_eq!(derive_above_left(1, 0, 1, 1, &frame, 4, 8).unwrap(), 255);
}
/// r187: `derive_above_left` caller-bug guards.
#[test]
fn derive_above_left_caller_bug_guards() {
let frame = vec![0i32; 16];
// bit_depth out of range.
assert!(matches!(
derive_above_left(0, 0, 0, 0, &frame, 4, 9),
Err(crate::Error::PartitionWalkOutOfRange)
));
// curr_frame_cols == 0.
assert!(matches!(
derive_above_left(0, 0, 0, 0, &frame, 0, 8),
Err(crate::Error::PartitionWalkOutOfRange)
));
// arm 1 with x == 0.
assert!(matches!(
derive_above_left(1, 1, 0, 1, &frame, 4, 8),
Err(crate::Error::PartitionWalkOutOfRange)
));
// arm 1 with y == 0.
assert!(matches!(
derive_above_left(1, 1, 1, 0, &frame, 4, 8),
Err(crate::Error::PartitionWalkOutOfRange)
));
// arm 2 with y == 0.
assert!(matches!(
derive_above_left(1, 0, 1, 0, &frame, 4, 8),
Err(crate::Error::PartitionWalkOutOfRange)
));
// arm 3 with x == 0.
assert!(matches!(
derive_above_left(0, 1, 0, 1, &frame, 4, 8),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r187: §7.11.2.2 PAETH on a constant neighbour environment —
/// `AboveRow[j] == LeftCol[i] == AboveRow[-1] == c` produces
/// `base = c + c - c = c`, all three deltas are 0, the tie-break
/// `pLeft <= pTop && pLeft <= pTopLeft` arm fires, and every cell
/// equals `LeftCol[i] = c`.
#[test]
fn paeth_constant_neighbours_produce_constant_block() {
let above = vec![50u16; 4];
let left = vec![50u16; 4];
let mut pred = [0u16; 16];
predict_intra_paeth_pred(4, 4, &above, &left, 50, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 50));
}
/// r187: §7.11.2.2 PAETH — when `AboveRow[j] == AboveRow[-1]` for
/// all j and `LeftCol[i]` varies, `base = LeftCol[i]`, so `pLeft
/// = 0` and the first arm always wins: `pred[i][j] = LeftCol[i]`.
/// This is the H_PRED-degenerate case (each row a copy of
/// `LeftCol[i]`).
#[test]
fn paeth_degenerates_to_h_pred_when_above_equals_corner() {
let above = [100u16; 4];
let left = [10u16, 20, 30, 40];
let mut pred = [0u16; 16];
// AboveRow[-1] == AboveRow[j] = 100 ⇒ base = 100 + LeftCol[i]
// - 100 = LeftCol[i] ⇒ pLeft = 0 wins.
predict_intra_paeth_pred(4, 4, &above, &left, 100, &mut pred).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(pred[i * 4 + j], left[i]);
}
}
}
/// r187: §7.11.2.2 PAETH — when `LeftCol[i] == AboveRow[-1]` for
/// all i and `AboveRow[j]` varies, `base = AboveRow[j]`. Then
/// `pLeft = |AboveRow[j] - corner|`, `pTop = 0`, `pTopLeft =
/// |AboveRow[j] - corner|`. With `pTop = 0 < pLeft` (when
/// `AboveRow[j] != corner`) and `pTop <= pTopLeft`, the second
/// arm wins: `pred[i][j] = AboveRow[j]`. This is V_PRED-degenerate.
#[test]
fn paeth_degenerates_to_v_pred_when_left_equals_corner() {
let above = [10u16, 20, 30, 40];
let left = [100u16; 4];
let mut pred = [0u16; 16];
predict_intra_paeth_pred(4, 4, &above, &left, 100, &mut pred).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(pred[i * 4 + j], above[j]);
}
}
}
/// r187: §7.11.2.2 PAETH — 4×4 hand-trace pinning the first-arm
/// tie-break. `AboveRow[*] = LeftCol[*] = 200`, corner = `50`:
/// `base = 200 + 200 - 50 = 350`,
/// `pLeft = pTop = 150`, `pTopLeft = 300`.
/// pLeft (150) <= pTop (150) and pLeft (150) <= pTopLeft (300) →
/// first arm wins, pred = LeftCol[i] = 200 in every cell.
#[test]
fn paeth_first_arm_wins_when_pleft_ties_ptop_and_dominates_ptl() {
let above = [200u16; 4];
let left = [200u16; 4];
let mut pred = [0u16; 16];
predict_intra_paeth_pred(4, 4, &above, &left, 50, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 200));
}
/// r187: §7.11.2.2 PAETH third-arm reachable case — set
/// `AboveRow[j] = 250`, `LeftCol[i] = 250`, corner = `0`. Then
/// `base = 250 + 250 - 0 = 500`,
/// `pLeft = |500 - 250| = 250`,
/// `pTop = |500 - 250| = 250`,
/// `pTopLeft = |500 - 0| = 500`.
/// pLeft (250) <= pTop (250) and pLeft (250) <= pTopLeft (500) →
/// the first arm wins ⇒ pred = LeftCol[i] = 250. The third arm
/// is genuinely hard to hit; we exercise it with the spec's
/// degenerate inverse: `AboveRow == corner == LeftCol == c`
/// trivially returns c by ANY arm.
///
/// True third-arm trigger: pLeft > pTopLeft AND pTop > pTopLeft.
/// Try `AboveRow = 0`, `LeftCol = 0`, corner = 100:
/// `base = 0 + 0 - 100 = -100`,
/// `pLeft = |-100 - 0| = 100`,
/// `pTop = |-100 - 0| = 100`,
/// `pTopLeft = |-100 - 100| = 200`.
/// pLeft (100) <= pTop (100) and pLeft (100) <= pTopLeft (200)
/// — still first arm.
///
/// To actually fire the third arm we need `pTopLeft < pLeft` AND
/// `pTopLeft < pTop`. Try `AboveRow = 100, LeftCol = 100,
/// corner = 100`: all deltas zero ⇒ first arm wins.
///
/// `AboveRow = 110, LeftCol = 90, corner = 100`:
/// `base = 110 + 90 - 100 = 100`,
/// `pLeft = |100 - 90| = 10`,
/// `pTop = |100 - 110| = 10`,
/// `pTopLeft = |100 - 100| = 0`.
/// Now pLeft (10) > pTopLeft (0) and pTop (10) > pTopLeft (0):
/// first arm fails (10 > 0 fails the `<=` to pTopLeft), second
/// arm checks `pTop <= pTopLeft` → 10 <= 0 fails → THIRD arm
/// fires: pred = corner = 100.
#[test]
fn paeth_third_arm_reached_when_corner_uniquely_minimises() {
let above = [110u16; 4];
let left = [90u16; 4];
let mut pred = [0u16; 16];
predict_intra_paeth_pred(4, 4, &above, &left, 100, &mut pred).unwrap();
// Every cell falls into the third arm and returns corner.
assert!(
pred.iter().all(|&v| v == 100),
"third arm should produce corner-cell value, got {pred:?}"
);
}
/// r187: PAETH caller-bug guards.
#[test]
fn paeth_caller_bug_guards() {
let above = [10u16; 4];
let left = [20u16; 4];
let mut pred = [0u16; 16];
assert!(matches!(
predict_intra_paeth_pred(65, 4, &above, &left, 0, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
predict_intra_paeth_pred(4, 65, &above, &left, 0, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
let short = [10u16; 3];
assert!(matches!(
predict_intra_paeth_pred(4, 4, &short, &left, 0, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
predict_intra_paeth_pred(4, 4, &above, &short, 0, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
let mut small = [0u16; 8];
assert!(matches!(
predict_intra_paeth_pred(4, 4, &above, &left, 0, &mut small),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r187: §7.11.2.6 SMOOTH_PRED on a constant neighbour environment
/// (`AboveRow[*] == LeftCol[*] == c`) produces a constant block
/// `pred[i][j] == c`. Round2 of `c * (smY + smX + (256-smY) +
/// (256-smX)) = c * 512` then `>> 9` is `c` exactly.
#[test]
fn smooth_constant_neighbours_produce_constant_block() {
let above = [77u16; 4];
let left = [77u16; 4];
let mut pred = [0u16; 16];
predict_intra_smooth_pred(2, 2, 4, 4, &above, &left, &mut pred).unwrap();
assert!(
pred.iter().all(|&v| v == 77),
"constant block expected, got {pred:?}"
);
}
/// r187: §7.11.2.6 SMOOTH_PRED — corner cell `pred[0][0]` of a
/// 4×4 block hand-trace. With `sm_h[0] = sm_w[0] = 255` (the
/// `Sm_Weights_Tx_4x4[0]`) and reference samples `AboveRow[0]`,
/// `LeftCol[0]`, `LeftCol[h-1=3]`, `AboveRow[w-1=3]`:
///
/// `smoothPred = 255*AboveRow[0] + 1*LeftCol[3]
/// + 255*LeftCol[0] + 1*AboveRow[3]`
/// `pred[0][0] = (smoothPred + 256) >> 9`
///
/// With `AboveRow = [100, 100, 100, 100]`,
/// `LeftCol = [200, 200, 200, 200]`:
/// `smoothPred = 255*100 + 1*200 + 255*200 + 1*100 = 25500 + 200
/// + 51000 + 100 = 76800`
/// `pred[0][0] = (76800 + 256) >> 9 = 77056 >> 9 = 150` (exact).
#[test]
fn smooth_pred_corner_cell_hand_trace() {
let above = [100u16; 4];
let left = [200u16; 4];
let mut pred = [0u16; 16];
predict_intra_smooth_pred(2, 2, 4, 4, &above, &left, &mut pred).unwrap();
assert_eq!(pred[0], 150);
}
/// r187: §7.11.2.6 SMOOTH_PRED bottom-right corner pred[h-1][w-1]
/// of a 4×4 block. `sm_h[3] = sm_w[3] = 64`, references same as
/// above:
/// `smoothPred = 64*AboveRow[3] + (256-64)*LeftCol[3]
/// + 64*LeftCol[3] + (256-64)*AboveRow[3]`
/// = `64*100 + 192*200 + 64*200 + 192*100`
/// = `6400 + 38400 + 12800 + 19200 = 76800`
/// `pred[3][3] = (76800 + 256) >> 9 = 150`.
/// (Same answer here only because of the constant-edges symmetry;
/// per-cell positions still write the same Round2.)
#[test]
fn smooth_pred_bottom_right_cell_hand_trace() {
let above = [100u16; 4];
let left = [200u16; 4];
let mut pred = [0u16; 16];
predict_intra_smooth_pred(2, 2, 4, 4, &above, &left, &mut pred).unwrap();
assert_eq!(pred[3 * 4 + 3], 150);
}
/// r187: §7.11.2.6 SMOOTH_PRED admits the largest §3 transform
/// size (64×64) — output stays within 12-bit range when both
/// neighbour arrays sit at the 12-bit ceiling.
#[test]
fn smooth_pred_max_size_64x64_within_12bit_range() {
let above = vec![4095u16; 64];
let left = vec![4095u16; 64];
let mut pred = vec![0u16; 64 * 64];
predict_intra_smooth_pred(6, 6, 64, 64, &above, &left, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 4095));
}
/// r187: §7.11.2.6 SMOOTH_V_PRED — constant-edge case produces a
/// constant block. `wy * c + (256 - wy) * c = 256 * c`, Round2(_,
/// 8) is `c` exactly.
#[test]
fn smooth_v_constant_above_and_left_h_minus_1_equal_produces_constant() {
let above = [50u16; 4];
let left = [50u16; 4];
let mut pred = [0u16; 16];
predict_intra_smooth_v_pred(2, 4, 4, &above, &left, &mut pred).unwrap();
assert!(pred.iter().all(|&v| v == 50));
}
/// r187: §7.11.2.6 SMOOTH_V_PRED — top row uses `sm_h[0] = 255`
/// for the `AboveRow` weight (so it's heavily anchored to top),
/// bottom row uses `sm_h[h-1] = 64` (so it's heavily anchored to
/// `LeftCol[h-1]`). With `AboveRow = 0`, `LeftCol[h-1] = 100`:
///
/// `pred[0][j] = (255*0 + 1*100 + 128) >> 8 = 228 >> 8 = 0`.
/// `pred[3][j] = (64*0 + 192*100 + 128) >> 8 = 19328 >> 8 = 75`.
#[test]
fn smooth_v_pred_top_row_anchored_to_above_bottom_to_bottom_left() {
let above = [0u16; 4];
let left = [0u16, 0, 0, 100]; // only LeftCol[h-1=3] matters
let mut pred = [0u16; 16];
predict_intra_smooth_v_pred(2, 4, 4, &above, &left, &mut pred).unwrap();
// top row (sm_h[0] = 255): heavily AboveRow-weighted ⇒ 0.
assert_eq!(pred[0], 0);
assert_eq!(pred[3], 0);
// bottom row (sm_h[3] = 64): heavily LeftCol[h-1]-weighted.
// (64*0 + 192*100 + 128) >> 8 = (19200 + 128) >> 8 = 19328 >> 8 = 75.
assert_eq!(pred[3 * 4], 75);
assert_eq!(pred[3 * 4 + 3], 75);
}
/// r187: §7.11.2.6 SMOOTH_H_PRED — left column uses `sm_w[0] =
/// 255` for the LeftCol weight; right column uses `sm_w[w-1] = 64`
/// for LeftCol. Mirror of the SMOOTH_V test above.
#[test]
fn smooth_h_pred_left_col_anchored_to_left_right_to_top_right() {
let above = [0u16, 0, 0, 100]; // only AboveRow[w-1=3] matters
let left = [0u16; 4];
let mut pred = [0u16; 16];
predict_intra_smooth_h_pred(2, 4, 4, &above, &left, &mut pred).unwrap();
// left col (sm_w[0] = 255): heavily LeftCol-weighted ⇒ 0.
assert_eq!(pred[0], 0);
assert_eq!(pred[3 * 4], 0);
// right col (sm_w[3] = 64): heavily AboveRow[w-1]-weighted.
// (64*0 + 192*100 + 128) >> 8 = 75.
assert_eq!(pred[3], 75);
assert_eq!(pred[3 * 4 + 3], 75);
}
/// r187: SMOOTH_PRED caller-bug guards.
#[test]
fn smooth_pred_caller_bug_guards() {
let above = [10u16; 4];
let left = [20u16; 4];
let mut pred = [0u16; 16];
// log2 out of supported range.
assert!(matches!(
predict_intra_smooth_pred(1, 2, 2, 4, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
predict_intra_smooth_pred(7, 2, 128, 4, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// w mismatch with log2_w.
assert!(matches!(
predict_intra_smooth_pred(2, 2, 8, 4, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Undersized buffers.
let short = [10u16; 3];
assert!(matches!(
predict_intra_smooth_pred(2, 2, 4, 4, &short, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
predict_intra_smooth_pred(2, 2, 4, 4, &above, &short, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
let mut small = [0u16; 8];
assert!(matches!(
predict_intra_smooth_pred(2, 2, 4, 4, &above, &left, &mut small),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r187: SMOOTH_V_PRED / SMOOTH_H_PRED caller-bug guards.
#[test]
fn smooth_v_and_h_caller_bug_guards() {
let above = [10u16; 4];
let left = [20u16; 4];
let mut pred = [0u16; 16];
// V: log2_h out of range.
assert!(matches!(
predict_intra_smooth_v_pred(1, 4, 2, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// V: h mismatch.
assert!(matches!(
predict_intra_smooth_v_pred(2, 4, 8, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// V: w > 64.
assert!(matches!(
predict_intra_smooth_v_pred(2, 65, 4, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// H: log2_w out of range.
assert!(matches!(
predict_intra_smooth_h_pred(7, 128, 4, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// H: w mismatch.
assert!(matches!(
predict_intra_smooth_h_pred(2, 8, 4, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// H: h > 64.
assert!(matches!(
predict_intra_smooth_h_pred(2, 4, 65, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r187: end-to-end PAETH through the §7.11.2.1 derivation —
/// `derive_above_row` + `derive_left_col` + `derive_above_left`
/// → `predict_intra_paeth_pred` against a real CurrFrame slice.
/// On the all-unavailable arm (`haveAbove = haveLeft = 0`), the
/// AboveRow / LeftCol fills are mid-grey ± 1; corner is mid-grey;
/// PAETH then produces a near-constant mid-grey block.
#[test]
fn paeth_end_to_end_no_neighbours_writes_mid_grey_block() {
let frame = vec![0i32; 16];
let mut above = [0u16; 8];
let mut left = [0u16; 8];
derive_above_row(0, 0, 0, 0, 0, 4, 4, 3, &frame, 4, 8, &mut above).unwrap();
derive_left_col(0, 0, 0, 0, 0, 4, 4, 3, &frame, 4, 8, &mut left).unwrap();
let corner = derive_above_left(0, 0, 0, 0, &frame, 4, 8).unwrap();
assert_eq!(corner, 128);
assert!(above.iter().all(|&v| v == 127));
assert!(left.iter().all(|&v| v == 129));
let mut pred = [0u16; 16];
predict_intra_paeth_pred(4, 4, &above, &left, corner, &mut pred).unwrap();
// Hand-trace one cell. AboveRow[j] = 127, LeftCol[i] = 129,
// corner = 128. base = 127 + 129 - 128 = 128. pLeft = |128 -
// 129| = 1, pTop = |128 - 127| = 1, pTopLeft = |128 - 128|
// = 0. pLeft (1) > pTopLeft (0) so first arm fails. pTop (1)
// > pTopLeft (0) so second arm fails. Third arm fires ⇒ pred
// = corner = 128.
assert!(pred.iter().all(|&v| v == 128));
}
/// r187: end-to-end SMOOTH_PRED through the §7.11.2.1 derivation
/// on a real CurrFrame slice — a 4×4 TU at (x=1, y=1) inside a
/// 5×5 frame whose top row + left column carry distinct samples.
/// Verifies the leaves can be driven through the derivation
/// helpers end-to-end.
#[test]
fn smooth_pred_end_to_end_with_derive_neighbours() {
// Build a 5x5 frame whose top row + left col are non-zero.
let mut frame = vec![0i32; 25];
for (j, slot) in frame.iter_mut().take(5).enumerate() {
*slot = (10 + j as i32) * 10; // row 0
}
for i in 0..5 {
frame[i * 5] = (50 + i as i32) * 2; // col 0
}
let mut above = [0u16; 8];
let mut left = [0u16; 8];
derive_above_row(1, 1, 0, 1, 1, 4, 4, 4, &frame, 5, 8, &mut above).unwrap();
derive_left_col(1, 1, 0, 1, 1, 4, 4, 4, &frame, 5, 8, &mut left).unwrap();
let corner = derive_above_left(1, 1, 1, 1, &frame, 5, 8).unwrap();
// Sanity: corner is frame[0][0] = (10+0)*10 = 100, BUT it was
// overwritten by the col-0 walk: frame[0][0] = (50+0)*2 = 100
// (same value either way). Above[0] = frame[0][1] = 110.
// Left[0] = frame[1][0] = 102.
assert_eq!(corner, 100);
assert_eq!(above[0], 110);
assert_eq!(left[0], 102);
let mut pred = [0u16; 16];
predict_intra_smooth_pred(2, 2, 4, 4, &above, &left, &mut pred).unwrap();
// SMOOTH outputs are samples in `[0, 255]` (8-bit).
assert!(pred.iter().all(|&v| v <= 255));
}
/// §8.3.1: a fresh context is a verbatim copy of the §9.4 defaults,
/// and the well-formedness invariants the §8.2.6 decoder relies on
/// hold for every row: the second-to-last entry is `1 << 15` and the
/// last (counter) entry is 0.
#[test]
fn init_from_defaults_copies_tables() {
let ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.intra_frame_y_mode, DEFAULT_INTRA_FRAME_Y_MODE_CDF);
assert_eq!(ctx.partition_w8, DEFAULT_PARTITION_W8_CDF);
assert_eq!(ctx.partition_w16, DEFAULT_PARTITION_W16_CDF);
assert_eq!(ctx.partition_w32, DEFAULT_PARTITION_W32_CDF);
assert_eq!(ctx.partition_w64, DEFAULT_PARTITION_W64_CDF);
assert_eq!(ctx.partition_w128, DEFAULT_PARTITION_W128_CDF);
assert_eq!(ctx.skip, DEFAULT_SKIP_CDF);
assert_eq!(ctx.segment_id, DEFAULT_SEGMENT_ID_CDF);
// §8.2.6 contract checks on every transcribed row.
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for a in &DEFAULT_INTRA_FRAME_Y_MODE_CDF {
for r in a {
check(r);
}
}
for r in &DEFAULT_PARTITION_W8_CDF {
check(r);
}
for r in &DEFAULT_PARTITION_W16_CDF {
check(r);
}
for r in &DEFAULT_PARTITION_W32_CDF {
check(r);
}
for r in &DEFAULT_PARTITION_W64_CDF {
check(r);
}
for r in &DEFAULT_PARTITION_W128_CDF {
check(r);
}
for r in &DEFAULT_SKIP_CDF {
check(r);
}
for r in &DEFAULT_SEGMENT_ID_CDF {
check(r);
}
}
/// §8.3.1 independence: adapting the working copy must not mutate the
/// `Default_*` source (the next tile re-inits from it).
#[test]
fn working_copy_is_independent_of_defaults() {
let mut ctx = TileCdfContext::new_from_defaults();
ctx.skip_cdf(0)[0] = 12345;
assert_ne!(ctx.skip[0][0], DEFAULT_SKIP_CDF[0][0]);
// The immutable source is untouched.
assert_eq!(DEFAULT_SKIP_CDF[0][0], 31671);
}
/// §8.3.2 `Intra_Mode_Context[]` mapping, term by term.
#[test]
fn intra_mode_context_maps_per_spec() {
let expected = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0];
for (mode, &want) in expected.iter().enumerate() {
assert_eq!(intra_mode_ctx(mode), want);
}
}
/// §8.3.2 `partition` ctx = `left * 2 + above`.
#[test]
fn partition_context_formula() {
assert_eq!(partition_ctx(false, false), 0);
assert_eq!(partition_ctx(true, false), 1);
assert_eq!(partition_ctx(false, true), 2);
assert_eq!(partition_ctx(true, true), 3);
}
/// §5.11.15 `Max_Tx_Size_Rect[ BLOCK_SIZES ]` square-block
/// invariants: a BLOCK_NxN row maps to TX_NxN for the four primary
/// square sizes (4×4 through 64×64). The 128×* / *×128 caps clip
/// to TX_64X64 per the spec.
#[test]
fn max_tx_size_rect_square_block_identity() {
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_4X4], TX_4X4);
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_8X8], TX_8X8);
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_16X16], TX_16X16);
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_32X32], TX_32X32);
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_64X64], TX_64X64);
// 128×128 / 64×128 / 128×64 cap at TX_64X64 — the spec's
// largest rectangular transform.
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_128X128], TX_64X64);
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_64X128], TX_64X64);
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_128X64], TX_64X64);
// Rectangular blocks pick the matching rectangular TX size.
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_4X8], TX_4X8);
assert_eq!(MAX_TX_SIZE_RECT[BLOCK_16X64], TX_16X64);
}
/// §5.11.15 `Max_Tx_Depth[ BLOCK_SIZES ]` invariants per the
/// spec listing on av1-spec p.69.
#[test]
fn max_tx_depth_table_matches_spec() {
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_4X4], 0);
// Row 1: 4×8 / 8×4 / 8×8 all depth 1.
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_4X8], 1);
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_8X4], 1);
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_8X8], 1);
// Row 2: 8×16 / 16×8 / 16×16 / 16×32.
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_8X16], 2);
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_16X16], 2);
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_16X32], 3);
// The 128×* tail is depth 4.
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_128X128], 4);
// The extended rectangular tail.
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_4X16], 2);
assert_eq!(MAX_TX_DEPTH_TABLE[BLOCK_64X16], 4);
}
/// §5.11.15 / §5.11.17 `Split_Tx_Size[ TX_SIZES_ALL ]`: every
/// entry maps to a smaller-or-equal TX size; chained splits
/// terminate at TX_4X4.
#[test]
fn split_tx_size_terminates_at_tx_4x4() {
// Square chain: TX_64X64 -> TX_32X32 -> TX_16X16 -> TX_8X8 -> TX_4X4 -> TX_4X4.
assert_eq!(SPLIT_TX_SIZE[TX_64X64], TX_32X32);
assert_eq!(SPLIT_TX_SIZE[TX_32X32], TX_16X16);
assert_eq!(SPLIT_TX_SIZE[TX_16X16], TX_8X8);
assert_eq!(SPLIT_TX_SIZE[TX_8X8], TX_4X4);
assert_eq!(SPLIT_TX_SIZE[TX_4X4], TX_4X4);
// Rectangular chain: TX_16X64 -> TX_16X32 -> TX_16X16 ...
assert_eq!(SPLIT_TX_SIZE[TX_16X64], TX_16X32);
assert_eq!(SPLIT_TX_SIZE[TX_16X32], TX_16X16);
// Asymmetric chain: TX_4X16 -> TX_4X8 -> TX_4X4.
assert_eq!(SPLIT_TX_SIZE[TX_4X16], TX_4X8);
assert_eq!(SPLIT_TX_SIZE[TX_4X8], TX_4X4);
}
/// §8.3.2 `tx_depth` ctx formula contract: ctx ∈ 0..TX_SIZE_CONTEXTS.
/// Each combination of (`aboveW >= maxTxWidth`, `leftH >= maxTxHeight`)
/// sums to a unique ctx.
#[test]
fn tx_depth_ctx_combination_table() {
// Neither neighbour reaches maxTxWidth/Height.
assert_eq!(tx_depth_ctx(0, 0, 16, 16), 0);
// Above only.
assert_eq!(tx_depth_ctx(16, 0, 16, 16), 1);
// Left only.
assert_eq!(tx_depth_ctx(0, 16, 16, 16), 1);
// Both reach.
assert_eq!(tx_depth_ctx(16, 16, 16, 16), 2);
// Above strictly larger: still counts.
assert_eq!(tx_depth_ctx(32, 8, 16, 16), 1);
}
/// §3 `MAX_VARTX_DEPTH = 2` per av1-spec p.7.
#[test]
fn max_vartx_depth_constant() {
assert_eq!(MAX_VARTX_DEPTH, 2);
}
/// §5.11.36 `find_tx_size( w, h )`: returns the `TX_NxN` ordinal
/// for every square size in `{4, 8, 16, 32, 64}`, and the matching
/// rectangular ordinal for the §6.10.16 rectangular sizes.
#[test]
fn find_tx_size_square_and_rectangular() {
assert_eq!(find_tx_size(4, 4), Some(TX_4X4));
assert_eq!(find_tx_size(8, 8), Some(TX_8X8));
assert_eq!(find_tx_size(16, 16), Some(TX_16X16));
assert_eq!(find_tx_size(32, 32), Some(TX_32X32));
assert_eq!(find_tx_size(64, 64), Some(TX_64X64));
// Rectangular cases.
assert_eq!(find_tx_size(4, 8), Some(TX_4X8));
assert_eq!(find_tx_size(8, 4), Some(TX_8X4));
assert_eq!(find_tx_size(16, 32), Some(TX_16X32));
assert_eq!(find_tx_size(32, 16), Some(TX_32X16));
assert_eq!(find_tx_size(64, 16), Some(TX_64X16));
// Out-of-range (caller bug): no match.
assert_eq!(find_tx_size(128, 128), None);
assert_eq!(find_tx_size(3, 3), None);
}
/// §8.3.2 `partition` array selection by `bsl`.
#[test]
fn partition_cdf_selected_by_bsl() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.partition_cdf(1, 0).unwrap().len(), 5); // W8
assert_eq!(ctx.partition_cdf(2, 0).unwrap().len(), 11); // W16
assert_eq!(ctx.partition_cdf(3, 0).unwrap().len(), 11); // W32
assert_eq!(ctx.partition_cdf(4, 0).unwrap().len(), 11); // W64
assert_eq!(ctx.partition_cdf(5, 0).unwrap().len(), 9); // W128
assert!(ctx.partition_cdf(0, 0).is_none());
assert!(ctx.partition_cdf(6, 0).is_none());
// The selected row matches the §9.4 default for that ctx.
assert_eq!(
ctx.partition_cdf(2, 3).unwrap(),
&DEFAULT_PARTITION_W16_CDF[3]
);
}
/// §8.3.2 `skip` ctx = sum of neighbour `Skips[]`.
#[test]
fn skip_context_sum() {
assert_eq!(skip_ctx(0, 0), 0);
assert_eq!(skip_ctx(1, 0), 1);
assert_eq!(skip_ctx(0, 1), 1);
assert_eq!(skip_ctx(1, 1), 2);
}
/// §8.3.2 `segment_id` ctx derivation across the four branches.
#[test]
fn segment_id_context_branches() {
// prevUL < 0 (unavailable) ⇒ 0.
assert_eq!(segment_id_ctx(None, Some(1), Some(1)), 0);
// all three equal ⇒ 2.
assert_eq!(segment_id_ctx(Some(3), Some(3), Some(3)), 2);
// exactly one pair equal ⇒ 1.
assert_eq!(segment_id_ctx(Some(3), Some(3), Some(5)), 1); // UL==U
assert_eq!(segment_id_ctx(Some(3), Some(5), Some(3)), 1); // UL==L
assert_eq!(segment_id_ctx(Some(3), Some(5), Some(5)), 1); // U==L
// all distinct ⇒ 0.
assert_eq!(segment_id_ctx(Some(3), Some(5), Some(7)), 0);
// A missing U/L cannot equal a present UL, so it falls through.
assert_eq!(segment_id_ctx(Some(3), None, Some(3)), 1); // UL==L
assert_eq!(segment_id_ctx(Some(3), None, None), 0);
}
/// End-to-end: decode a `skip` symbol through a default CDF selected
/// by §8.3.2, driving the real §8.2 `SymbolDecoder`.
///
/// We pick `ctx = 2` (`Default_Skip_Cdf[2] = {4576, 32768, 0}`, a
/// strongly-toward-1 distribution) and a window whose `SymbolValue`
/// lands in the high (symbol-1) region, then assert both the decoded
/// value and that the §8.3 update mutated the working copy while the
/// §9.4 source stayed put.
#[test]
fn decode_skip_through_default_cdf() {
// sz = 2 ⇒ numBits = 15. bytes = 0xFF 0xFE ⇒ top 15 bits =
// 111111111111111 = 0x7FFF; SymbolValue = 0x7FFF ^ 0x7FFF = 0.
// A SymbolValue of 0 is below every `cur` boundary, so the
// §8.2.6 search returns the LAST symbol (here symbol 1).
let bytes = [0xFFu8, 0xFEu8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 2, false).unwrap();
assert_eq!(dec.symbol_value(), 0);
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.skip;
let cdf = ctx.skip_cdf(2);
let sym = dec.read_symbol(cdf).unwrap();
assert_eq!(sym, 1, "SymbolValue 0 selects the final (skip) symbol");
// §8.3 update ran (disable_cdf_update == false at init): the
// counter advanced and the row changed.
assert_ne!(ctx.skip, before, "read_symbol must adapt the working CDF");
assert_eq!(ctx.skip[2][2], 1, "§8.3 counter incremented to 1");
// The §9.4 source is immutable.
assert_eq!(DEFAULT_SKIP_CDF[2], [4576, 32768, 0]);
}
/// End-to-end through a multisymbol partition CDF, confirming the
/// §8.3.2-selected row drives a valid §8.2.6 decode in range.
#[test]
fn decode_partition_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
// bsl = 2 (W16), ctx = partition_ctx(above=true, left=false) = 1.
let pctx = partition_ctx(true, false);
assert_eq!(pctx, 1);
let cdf = ctx.partition_cdf(2, pctx).unwrap();
let sym = dec.read_symbol(cdf).unwrap();
// W16 codes a 10-value symbol.
assert!(sym < 10, "partition symbol in 0..10, got {sym}");
// disable_cdf_update was true ⇒ the row is untouched.
assert_eq!(ctx.partition_w16[1], DEFAULT_PARTITION_W16_CDF[1]);
}
// -----------------------------------------------------------------
// Round 17 — motion-vector default CDF tests.
// -----------------------------------------------------------------
/// §9.4 verbatim values for the small / flat MV defaults: the
/// `216*128`-style fixed-point expansions land as the bytes
/// `SymbolDecoder::read_symbol` will see.
#[test]
fn mv_default_byte_exact_values() {
// Default_Mv_Joint_Cdf = { 4096, 11264, 19328, 32768, 0 }
assert_eq!(DEFAULT_MV_JOINT_CDF, [4096, 11264, 19328, 32768, 0]);
// Default_Mv_Sign_Cdf = { 128*128, 32768, 0 } = { 16384, 32768, 0 }
assert_eq!(DEFAULT_MV_SIGN_CDF, [16384, 32768, 0]);
// Default_Mv_Hp_Cdf = { 128*128, 32768, 0 }
assert_eq!(DEFAULT_MV_HP_CDF, [16384, 32768, 0]);
// Default_Mv_Class0_Bit_Cdf = { 216*128, 32768, 0 } = { 27648, ... }
assert_eq!(DEFAULT_MV_CLASS0_BIT_CDF, [27648, 32768, 0]);
// Default_Mv_Class0_Hp_Cdf = { 160*128, 32768, 0 } = { 20480, ... }
assert_eq!(DEFAULT_MV_CLASS0_HP_CDF, [20480, 32768, 0]);
// Default_Mv_Bit_Cdf[ MV_OFFSET_BITS ][ 3 ] — every multiplier
// verbatim from the spec.
let expected: [[u16; 3]; MV_OFFSET_BITS] = [
[136 * 128, 32768, 0],
[140 * 128, 32768, 0],
[148 * 128, 32768, 0],
[160 * 128, 32768, 0],
[176 * 128, 32768, 0],
[192 * 128, 32768, 0],
[224 * 128, 32768, 0],
[234 * 128, 32768, 0],
[234 * 128, 32768, 0],
[240 * 128, 32768, 0],
];
assert_eq!(DEFAULT_MV_BIT_CDF, expected);
// Default_Mv_Class_Cdf — first row, by literal §9.4 listing.
assert_eq!(
DEFAULT_MV_CLASS_CDF[0],
[28672, 30976, 31858, 32320, 32551, 32656, 32740, 32757, 32762, 32767, 32768, 0]
);
// The leading `2` axis carries identical rows per spec.
assert_eq!(DEFAULT_MV_CLASS_CDF[0], DEFAULT_MV_CLASS_CDF[1]);
// Default_Mv_Class0_Fr_Cdf — both comp rows, both mv_class0_bit
// sub-rows.
assert_eq!(
DEFAULT_MV_CLASS0_FR_CDF[0][0],
[16384, 24576, 26624, 32768, 0]
);
assert_eq!(
DEFAULT_MV_CLASS0_FR_CDF[0][1],
[12288, 21248, 24128, 32768, 0]
);
assert_eq!(DEFAULT_MV_CLASS0_FR_CDF[1], DEFAULT_MV_CLASS0_FR_CDF[0]);
// Default_Mv_Fr_Cdf — both comp rows identical.
assert_eq!(DEFAULT_MV_FR_CDF[0], [8192, 17408, 21248, 32768, 0]);
assert_eq!(DEFAULT_MV_FR_CDF[1], DEFAULT_MV_FR_CDF[0]);
}
/// §8.3.1 init step for the MV group: every working row matches the
/// transcribed §9.4 default, broadcast to `MV_CONTEXTS` slots (and
/// to `MV_COMPS` slots for the flat per-component defaults). The
/// §8.2.6 well-formedness invariants hold on every row.
#[test]
fn init_from_defaults_copies_mv_tables() {
let ctx = TileCdfContext::new_from_defaults();
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for i in 0..MV_CONTEXTS {
assert_eq!(ctx.mv_joint[i], DEFAULT_MV_JOINT_CDF);
check(&ctx.mv_joint[i]);
for comp in 0..MV_COMPS {
assert_eq!(ctx.mv_sign[i][comp], DEFAULT_MV_SIGN_CDF);
check(&ctx.mv_sign[i][comp]);
assert_eq!(ctx.mv_class[i][comp], DEFAULT_MV_CLASS_CDF[comp]);
check(&ctx.mv_class[i][comp]);
assert_eq!(ctx.mv_class0_bit[i][comp], DEFAULT_MV_CLASS0_BIT_CDF);
check(&ctx.mv_class0_bit[i][comp]);
assert_eq!(ctx.mv_class0_hp[i][comp], DEFAULT_MV_CLASS0_HP_CDF);
check(&ctx.mv_class0_hp[i][comp]);
assert_eq!(ctx.mv_hp[i][comp], DEFAULT_MV_HP_CDF);
check(&ctx.mv_hp[i][comp]);
assert_eq!(ctx.mv_fr[i][comp], DEFAULT_MV_FR_CDF[comp]);
check(&ctx.mv_fr[i][comp]);
for (bit, default_row) in DEFAULT_MV_CLASS0_FR_CDF[comp].iter().enumerate() {
assert_eq!(ctx.mv_class0_fr[i][comp][bit], *default_row);
check(&ctx.mv_class0_fr[i][comp][bit]);
}
for (off, default_row) in DEFAULT_MV_BIT_CDF.iter().enumerate() {
assert_eq!(ctx.mv_bit[i][comp][off], *default_row);
check(&ctx.mv_bit[i][comp][off]);
}
}
}
}
/// §5.11.31 `MvCtx = use_intrabc ? MV_INTRABC_CONTEXT : 0`.
#[test]
fn mv_ctx_derivation_per_spec() {
assert_eq!(mv_ctx(false), 0);
assert_eq!(mv_ctx(true), MV_INTRABC_CONTEXT);
assert_eq!(MV_INTRABC_CONTEXT, 1);
// The §5.11.31 result must always be a valid MvCtx index.
assert!(mv_ctx(false) < MV_CONTEXTS);
assert!(mv_ctx(true) < MV_CONTEXTS);
}
/// §8.3.2 MV selectors all return the §9.4 default row (and the
/// `MvCtx + comp + mv_class0_bit / i` indexing matches the spec's
/// `[ MvCtx ][ comp ][ ... ]` literal).
#[test]
fn mv_selectors_return_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for i in 0..MV_CONTEXTS {
assert_eq!(ctx.mv_joint_cdf(i), &DEFAULT_MV_JOINT_CDF);
for comp in 0..MV_COMPS {
assert_eq!(ctx.mv_sign_cdf(i, comp), &DEFAULT_MV_SIGN_CDF);
assert_eq!(ctx.mv_class_cdf(i, comp), &DEFAULT_MV_CLASS_CDF[comp]);
assert_eq!(ctx.mv_class0_bit_cdf(i, comp), &DEFAULT_MV_CLASS0_BIT_CDF);
assert_eq!(ctx.mv_class0_hp_cdf(i, comp), &DEFAULT_MV_CLASS0_HP_CDF);
assert_eq!(ctx.mv_hp_cdf(i, comp), &DEFAULT_MV_HP_CDF);
assert_eq!(ctx.mv_fr_cdf(i, comp), &DEFAULT_MV_FR_CDF[comp]);
for (bit, default_row) in DEFAULT_MV_CLASS0_FR_CDF[comp].iter().enumerate() {
assert_eq!(ctx.mv_class0_fr_cdf(i, comp, bit), default_row);
}
for (off, default_row) in DEFAULT_MV_BIT_CDF.iter().enumerate() {
assert_eq!(ctx.mv_bit_cdf(i, comp, off), default_row);
}
}
}
}
/// §8.3.1 independence for the MV group: adapting the working copy
/// must not mutate the §9.4 source.
#[test]
fn mv_working_copy_is_independent_of_defaults() {
let mut ctx = TileCdfContext::new_from_defaults();
ctx.mv_joint_cdf(0)[0] = 17;
ctx.mv_sign_cdf(1, 1)[0] = 33;
ctx.mv_class0_fr_cdf(0, 1, 0)[2] = 99;
ctx.mv_bit_cdf(1, 0, 3)[0] = 41;
assert_ne!(ctx.mv_joint[0][0], DEFAULT_MV_JOINT_CDF[0]);
assert_ne!(ctx.mv_sign[1][1][0], DEFAULT_MV_SIGN_CDF[0]);
assert_ne!(
ctx.mv_class0_fr[0][1][0][2],
DEFAULT_MV_CLASS0_FR_CDF[1][0][2]
);
assert_ne!(ctx.mv_bit[1][0][3][0], DEFAULT_MV_BIT_CDF[3][0]);
// §9.4 sources untouched.
assert_eq!(DEFAULT_MV_JOINT_CDF, [4096, 11264, 19328, 32768, 0]);
assert_eq!(DEFAULT_MV_SIGN_CDF, [16384, 32768, 0]);
assert_eq!(
DEFAULT_MV_CLASS0_FR_CDF[1][0],
[16384, 24576, 26624, 32768, 0]
);
assert_eq!(DEFAULT_MV_BIT_CDF[3], [160 * 128, 32768, 0]);
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// `mv_joint` (4-value) default CDF selected by §8.3.2, and assert
/// the §8.3 update path actually mutated the working row + counter.
#[test]
fn decode_mv_joint_through_default_cdf() {
// sz = 2 ⇒ numBits = 15. bytes = 0xFF 0xFE ⇒ top 15 bits =
// 0x7FFF; SymbolValue = 0x7FFF ^ 0x7FFF = 0. SymbolValue 0 is
// below every `cur` boundary, so §8.2.6 returns the LAST symbol
// (here symbol 3 = MV_JOINT_HNZVNZ).
let bytes = [0xFFu8, 0xFEu8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 2, false).unwrap();
assert_eq!(dec.symbol_value(), 0);
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.mv_joint;
let mctx = mv_ctx(false);
assert_eq!(mctx, 0);
let cdf = ctx.mv_joint_cdf(mctx);
let sym = dec.read_symbol(cdf).unwrap();
assert_eq!(sym, 3, "SymbolValue 0 selects MV_JOINT_HNZVNZ");
// §8.3 update ran: counter advanced and the working row changed.
assert_ne!(ctx.mv_joint, before);
assert_eq!(ctx.mv_joint[0][4], 1, "§8.3 counter incremented to 1");
// §9.4 source is immutable.
assert_eq!(DEFAULT_MV_JOINT_CDF, [4096, 11264, 19328, 32768, 0]);
}
/// End-to-end through a binary `mv_bit` default CDF with
/// `disable_cdf_update == true`, confirming the §8.3.2 selector
/// drives a valid §8.2.6 decode in range and the working row stays
/// untouched in the non-adaptive path.
#[test]
fn decode_mv_bit_through_default_cdf_no_update() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
// §5.11.32 inputs: MvCtx for a non-intrabc inter MV; comp = 1
// (vertical); offset bit position i = 3.
let mctx = mv_ctx(false);
let cdf = ctx.mv_bit_cdf(mctx, 1, 3);
let sym = dec.read_symbol(cdf).unwrap();
assert!(sym < 2, "mv_bit is binary; got {sym}");
// disable_cdf_update was true ⇒ the row is untouched.
assert_eq!(ctx.mv_bit[mctx][1][3], DEFAULT_MV_BIT_CDF[3]);
}
// -----------------------------------------------------------------
// Round 18 — inter-mode / reference-frame default CDF tests.
// -----------------------------------------------------------------
/// (a) Table dimensions match §9.4 verbatim for every new
/// inter-mode / ref-frame array.
#[test]
fn inter_default_cdf_dimensions_match_spec() {
assert_eq!(DEFAULT_NEW_MV_CDF.len(), NEW_MV_CONTEXTS);
assert_eq!(DEFAULT_ZERO_MV_CDF.len(), ZERO_MV_CONTEXTS);
assert_eq!(DEFAULT_REF_MV_CDF.len(), REF_MV_CONTEXTS);
assert_eq!(DEFAULT_DRL_MODE_CDF.len(), DRL_MODE_CONTEXTS);
assert_eq!(DEFAULT_IS_INTER_CDF.len(), IS_INTER_CONTEXTS);
assert_eq!(DEFAULT_COMP_MODE_CDF.len(), COMP_INTER_CONTEXTS);
assert_eq!(DEFAULT_SKIP_MODE_CDF.len(), SKIP_MODE_CONTEXTS);
assert_eq!(DEFAULT_COMP_REF_TYPE_CDF.len(), COMP_REF_TYPE_CONTEXTS);
// Binary CDFs all have 3 slots = N + 1 with N = 2.
for row in &DEFAULT_NEW_MV_CDF {
assert_eq!(row.len(), 3);
}
for row in &DEFAULT_IS_INTER_CDF {
assert_eq!(row.len(), 3);
}
// Three-axis ref CDFs.
assert_eq!(DEFAULT_COMP_REF_CDF.len(), REF_CONTEXTS);
for row in &DEFAULT_COMP_REF_CDF {
assert_eq!(row.len(), FWD_REFS - 1);
}
assert_eq!(DEFAULT_COMP_BWD_REF_CDF.len(), REF_CONTEXTS);
for row in &DEFAULT_COMP_BWD_REF_CDF {
assert_eq!(row.len(), BWD_REFS - 1);
}
assert_eq!(DEFAULT_SINGLE_REF_CDF.len(), REF_CONTEXTS);
for row in &DEFAULT_SINGLE_REF_CDF {
assert_eq!(row.len(), SINGLE_REFS - 1);
}
assert_eq!(DEFAULT_UNI_COMP_REF_CDF.len(), REF_CONTEXTS);
for row in &DEFAULT_UNI_COMP_REF_CDF {
assert_eq!(row.len(), UNIDIR_COMP_REFS - 1);
}
// Compound-mode CDF: 8 ctxs × (8 + 1) cumulatives.
assert_eq!(DEFAULT_COMPOUND_MODE_CDF.len(), COMPOUND_MODE_CONTEXTS);
for row in &DEFAULT_COMPOUND_MODE_CDF {
assert_eq!(row.len(), COMPOUND_MODES + 1);
}
// §8.2.6 invariants on every transcribed row.
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for r in &DEFAULT_NEW_MV_CDF {
check(r);
}
for r in &DEFAULT_ZERO_MV_CDF {
check(r);
}
for r in &DEFAULT_REF_MV_CDF {
check(r);
}
for r in &DEFAULT_DRL_MODE_CDF {
check(r);
}
for r in &DEFAULT_IS_INTER_CDF {
check(r);
}
for r in &DEFAULT_COMP_MODE_CDF {
check(r);
}
for r in &DEFAULT_SKIP_MODE_CDF {
check(r);
}
for a in &DEFAULT_COMP_REF_CDF {
for r in a {
check(r);
}
}
for a in &DEFAULT_COMP_BWD_REF_CDF {
for r in a {
check(r);
}
}
for a in &DEFAULT_SINGLE_REF_CDF {
for r in a {
check(r);
}
}
for r in &DEFAULT_COMPOUND_MODE_CDF {
check(r);
}
for r in &DEFAULT_COMP_REF_TYPE_CDF {
check(r);
}
for a in &DEFAULT_UNI_COMP_REF_CDF {
for r in a {
check(r);
}
}
}
/// (a) Byte-exact §9.4 verbatim values for hand-picked rows of every
/// new inter-mode / ref-frame default array — every literal that
/// appears in the spec table is read back unchanged.
#[test]
fn inter_default_cdf_byte_exact_values() {
assert_eq!(DEFAULT_NEW_MV_CDF[0], [24035, 32768, 0]);
assert_eq!(DEFAULT_NEW_MV_CDF[5], [4676, 32768, 0]);
assert_eq!(DEFAULT_ZERO_MV_CDF[0], [2175, 32768, 0]);
assert_eq!(DEFAULT_ZERO_MV_CDF[1], [1054, 32768, 0]);
assert_eq!(DEFAULT_REF_MV_CDF[3], [28622, 32768, 0]);
assert_eq!(DEFAULT_DRL_MODE_CDF[1], [24560, 32768, 0]);
assert_eq!(DEFAULT_IS_INTER_CDF[0], [806, 32768, 0]);
assert_eq!(DEFAULT_IS_INTER_CDF[3], [26538, 32768, 0]);
assert_eq!(DEFAULT_COMP_MODE_CDF[2], [12031, 32768, 0]);
assert_eq!(DEFAULT_SKIP_MODE_CDF[0], [32621, 32768, 0]);
assert_eq!(DEFAULT_COMP_REF_CDF[0][0], [4946, 32768, 0]);
assert_eq!(DEFAULT_COMP_REF_CDF[2][2], [27544, 32768, 0]);
assert_eq!(DEFAULT_COMP_BWD_REF_CDF[0][0], [2235, 32768, 0]);
assert_eq!(DEFAULT_COMP_BWD_REF_CDF[2][1], [30489, 32768, 0]);
assert_eq!(DEFAULT_SINGLE_REF_CDF[0][0], [4897, 32768, 0]);
assert_eq!(DEFAULT_SINGLE_REF_CDF[1][3], [24773, 32768, 0]);
assert_eq!(DEFAULT_SINGLE_REF_CDF[2][5], [30304, 32768, 0]);
assert_eq!(
DEFAULT_COMPOUND_MODE_CDF[0],
[7760, 13823, 15808, 17641, 19156, 20666, 26891, 32768, 0]
);
assert_eq!(
DEFAULT_COMPOUND_MODE_CDF[7],
[13046, 23214, 24505, 25942, 27435, 28442, 29330, 32768, 0]
);
assert_eq!(DEFAULT_COMP_REF_TYPE_CDF[0], [1198, 32768, 0]);
assert_eq!(DEFAULT_COMP_REF_TYPE_CDF[4], [22475, 32768, 0]);
assert_eq!(DEFAULT_UNI_COMP_REF_CDF[1][2], [15270, 32768, 0]);
}
/// (b) §8.3.1 init places every §9.4 row into the corresponding
/// `Tile*Cdf` working slot of the freshly-constructed
/// `TileCdfContext`.
#[test]
fn init_from_defaults_copies_inter_tables() {
let ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.new_mv, DEFAULT_NEW_MV_CDF);
assert_eq!(ctx.zero_mv, DEFAULT_ZERO_MV_CDF);
assert_eq!(ctx.ref_mv, DEFAULT_REF_MV_CDF);
assert_eq!(ctx.drl_mode, DEFAULT_DRL_MODE_CDF);
assert_eq!(ctx.is_inter, DEFAULT_IS_INTER_CDF);
assert_eq!(ctx.comp_mode, DEFAULT_COMP_MODE_CDF);
assert_eq!(ctx.skip_mode, DEFAULT_SKIP_MODE_CDF);
assert_eq!(ctx.comp_ref, DEFAULT_COMP_REF_CDF);
assert_eq!(ctx.comp_bwd_ref, DEFAULT_COMP_BWD_REF_CDF);
assert_eq!(ctx.single_ref, DEFAULT_SINGLE_REF_CDF);
assert_eq!(ctx.compound_mode, DEFAULT_COMPOUND_MODE_CDF);
assert_eq!(ctx.comp_ref_type, DEFAULT_COMP_REF_TYPE_CDF);
assert_eq!(ctx.uni_comp_ref, DEFAULT_UNI_COMP_REF_CDF);
}
/// (b) §8.3.1 init + §8.3.2 selection: at a hand-picked
/// `(frame_type, ctx)` tuple — here the intra-only "tile init" path
/// for the `comp_ref` syntax — the selected row is exactly the
/// `Default_Comp_Ref_Cdf[ ctx ][ p ]` value from §9.4.
///
/// AV1 §8.3.1 always seeds `Tile*Cdf` from the same `Default_*_Cdf`
/// regardless of `frame_type` (the only `frame_type`-keyed variant
/// is `intra_frame_y_mode`, which uses a separate `Y_Mode_Cdf` table
/// for non-intra frames — out of this round's scope). So we exercise
/// every § member here with both extremes of its `ctx` index.
#[test]
fn inter_selectors_return_default_rows_at_hand_picked_ctx() {
let mut ctx = TileCdfContext::new_from_defaults();
// (frame_type=key-or-intra-only, new_mv, ctx=0).
assert_eq!(ctx.new_mv_cdf(0), &DEFAULT_NEW_MV_CDF[0]);
assert_eq!(
ctx.new_mv_cdf(NEW_MV_CONTEXTS - 1),
&DEFAULT_NEW_MV_CDF[NEW_MV_CONTEXTS - 1]
);
assert_eq!(ctx.zero_mv_cdf(0), &DEFAULT_ZERO_MV_CDF[0]);
assert_eq!(ctx.zero_mv_cdf(1), &DEFAULT_ZERO_MV_CDF[1]);
assert_eq!(ctx.ref_mv_cdf(0), &DEFAULT_REF_MV_CDF[0]);
assert_eq!(ctx.ref_mv_cdf(5), &DEFAULT_REF_MV_CDF[5]);
assert_eq!(ctx.drl_mode_cdf(0), &DEFAULT_DRL_MODE_CDF[0]);
assert_eq!(ctx.drl_mode_cdf(2), &DEFAULT_DRL_MODE_CDF[2]);
assert_eq!(ctx.is_inter_cdf(0), &DEFAULT_IS_INTER_CDF[0]);
assert_eq!(ctx.is_inter_cdf(3), &DEFAULT_IS_INTER_CDF[3]);
assert_eq!(ctx.comp_mode_cdf(0), &DEFAULT_COMP_MODE_CDF[0]);
assert_eq!(ctx.comp_mode_cdf(4), &DEFAULT_COMP_MODE_CDF[4]);
assert_eq!(ctx.skip_mode_cdf(0), &DEFAULT_SKIP_MODE_CDF[0]);
assert_eq!(ctx.skip_mode_cdf(2), &DEFAULT_SKIP_MODE_CDF[2]);
assert_eq!(ctx.comp_ref_cdf(0, 0), &DEFAULT_COMP_REF_CDF[0][0]);
assert_eq!(ctx.comp_ref_cdf(2, 2), &DEFAULT_COMP_REF_CDF[2][2]);
assert_eq!(ctx.comp_bwd_ref_cdf(0, 0), &DEFAULT_COMP_BWD_REF_CDF[0][0]);
assert_eq!(ctx.comp_bwd_ref_cdf(2, 1), &DEFAULT_COMP_BWD_REF_CDF[2][1]);
assert_eq!(ctx.single_ref_cdf(0, 0), &DEFAULT_SINGLE_REF_CDF[0][0]);
assert_eq!(ctx.single_ref_cdf(2, 5), &DEFAULT_SINGLE_REF_CDF[2][5]);
assert_eq!(ctx.compound_mode_cdf(0), &DEFAULT_COMPOUND_MODE_CDF[0]);
assert_eq!(ctx.compound_mode_cdf(7), &DEFAULT_COMPOUND_MODE_CDF[7]);
assert_eq!(ctx.comp_ref_type_cdf(0), &DEFAULT_COMP_REF_TYPE_CDF[0]);
assert_eq!(ctx.comp_ref_type_cdf(4), &DEFAULT_COMP_REF_TYPE_CDF[4]);
assert_eq!(ctx.uni_comp_ref_cdf(0, 0), &DEFAULT_UNI_COMP_REF_CDF[0][0]);
assert_eq!(ctx.uni_comp_ref_cdf(2, 2), &DEFAULT_UNI_COMP_REF_CDF[2][2]);
}
/// §8.3.1 independence for the inter group: adapting the working
/// copy must not mutate the §9.4 source (the next tile re-inits
/// from it).
#[test]
fn inter_working_copy_is_independent_of_defaults() {
let mut ctx = TileCdfContext::new_from_defaults();
ctx.new_mv_cdf(0)[0] = 7;
ctx.comp_ref_cdf(1, 2)[0] = 13;
ctx.compound_mode_cdf(3)[5] = 999;
assert_ne!(ctx.new_mv[0][0], DEFAULT_NEW_MV_CDF[0][0]);
assert_ne!(ctx.comp_ref[1][2][0], DEFAULT_COMP_REF_CDF[1][2][0]);
assert_ne!(ctx.compound_mode[3][5], DEFAULT_COMPOUND_MODE_CDF[3][5]);
// §9.4 sources untouched.
assert_eq!(DEFAULT_NEW_MV_CDF[0], [24035, 32768, 0]);
assert_eq!(DEFAULT_COMP_REF_CDF[1][2], [15160, 32768, 0]);
assert_eq!(DEFAULT_COMPOUND_MODE_CDF[3][5], 25736);
}
/// §8.3.2 `is_inter` context branches — every neighbour-availability
/// + intra-flag combination per the §8.3.2 paragraph.
#[test]
fn is_inter_context_branches() {
// AvailU && AvailL.
assert_eq!(is_inter_ctx(Some(false), Some(false)), 0); // both inter
assert_eq!(is_inter_ctx(Some(true), Some(false)), 1); // exactly one intra
assert_eq!(is_inter_ctx(Some(false), Some(true)), 1);
assert_eq!(is_inter_ctx(Some(true), Some(true)), 3); // both intra
// AvailU XOR AvailL.
assert_eq!(is_inter_ctx(Some(true), None), 2);
assert_eq!(is_inter_ctx(Some(false), None), 0);
assert_eq!(is_inter_ctx(None, Some(true)), 2);
assert_eq!(is_inter_ctx(None, Some(false)), 0);
// Neither available.
assert_eq!(is_inter_ctx(None, None), 0);
// Every result is a valid IS_INTER context index.
for above in [None, Some(false), Some(true)] {
for left in [None, Some(false), Some(true)] {
assert!(is_inter_ctx(above, left) < IS_INTER_CONTEXTS);
}
}
}
/// §8.3.2 `skip_mode` context: sum of neighbour `SkipModes[]`.
#[test]
fn skip_mode_context_sum() {
assert_eq!(skip_mode_ctx(0, 0), 0);
assert_eq!(skip_mode_ctx(1, 0), 1);
assert_eq!(skip_mode_ctx(0, 1), 1);
assert_eq!(skip_mode_ctx(1, 1), 2);
for a in 0..=1 {
for l in 0..=1 {
assert!(skip_mode_ctx(a, l) < SKIP_MODE_CONTEXTS);
}
}
}
/// §8.3.2 `ref_count_ctx` three-branch ladder.
#[test]
fn ref_count_context_branches() {
assert_eq!(ref_count_ctx(0, 1), 0);
assert_eq!(ref_count_ctx(3, 4), 0);
assert_eq!(ref_count_ctx(0, 0), 1);
assert_eq!(ref_count_ctx(7, 7), 1);
assert_eq!(ref_count_ctx(2, 1), 2);
assert_eq!(ref_count_ctx(99, 0), 2);
// Every result is a valid REF_CONTEXTS index.
for c0 in 0..3 {
for c1 in 0..3 {
assert!(ref_count_ctx(c0, c1) < REF_CONTEXTS);
}
}
}
/// §8.3.2 `compound_mode` context: the `Compound_Mode_Ctx_Map`
/// lookup with the `RefMvContext >> 1` / `Min(NewMvContext,
/// COMP_NEWMV_CTXS - 1)` indexing — three hand-picked entries from
/// each of the three map rows.
#[test]
fn compound_mode_context_map_lookup() {
// Row 0 (RefMvContext >> 1 == 0): map = { 0, 1, 1, 1, 1 }.
assert_eq!(compound_mode_ctx(0, 0), 0);
assert_eq!(compound_mode_ctx(1, 1), 1); // 1 >> 1 == 0
assert_eq!(compound_mode_ctx(0, 4), 1);
// Row 1 (RefMvContext >> 1 == 1): map = { 1, 2, 3, 4, 4 }.
assert_eq!(compound_mode_ctx(2, 0), 1);
assert_eq!(compound_mode_ctx(2, 2), 3);
assert_eq!(compound_mode_ctx(3, 3), 4); // 3 >> 1 == 1
// Row 2 (RefMvContext >> 1 == 2): map = { 4, 4, 5, 6, 7 }.
assert_eq!(compound_mode_ctx(4, 0), 4);
assert_eq!(compound_mode_ctx(4, 4), 7);
assert_eq!(compound_mode_ctx(5, 2), 5); // 5 >> 1 == 2
// The `Min(.., COMP_NEWMV_CTXS - 1)` clamp: anything ≥ 4 is
// treated as 4.
assert_eq!(compound_mode_ctx(4, 99), 7);
// The map has only 3 rows; anything beyond row 2 saturates at
// row 2 (the §8.3.2 spec doesn't define a fourth row — every
// valid `RefMvContext` value yields `RefMvContext >> 1 < 3`,
// since `RefMvContext < REF_MV_CONTEXTS == 6`).
assert_eq!(compound_mode_ctx(REF_MV_CONTEXTS - 1, 0), 4);
// Every result is a valid COMPOUND_MODE_CONTEXTS index.
for r in 0..REF_MV_CONTEXTS {
for n in 0..NEW_MV_CONTEXTS {
assert!(compound_mode_ctx(r, n) < COMPOUND_MODE_CONTEXTS);
}
}
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// `compound_mode` (8-value) default CDF selected by §8.3.2
/// `compound_mode_ctx`, confirming the §8.3.2-selected row matches
/// the §9.4 default and a valid §8.2.6 decode lands in range.
#[test]
fn decode_compound_mode_through_default_cdf() {
// Pick a hand-picked (RefMvContext, NewMvContext) pair so that
// `compound_mode_ctx` lands on row 7 (= map[2][4]).
let cm_ctx = compound_mode_ctx(4, 4);
assert_eq!(cm_ctx, 7);
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
// §8.3.2 selection: `TileCompoundModeCdf[ cm_ctx ]` equals
// `Default_Compound_Mode_Cdf[ 7 ]` since we just init'd.
let row = ctx.compound_mode_cdf(cm_ctx);
assert_eq!(row, &DEFAULT_COMPOUND_MODE_CDF[7]);
let sym = dec.read_symbol(row).unwrap();
assert!(sym < COMPOUND_MODES as u32, "compound_mode is in 0..8");
// disable_cdf_update was true ⇒ row untouched.
assert_eq!(ctx.compound_mode[7], DEFAULT_COMPOUND_MODE_CDF[7]);
}
// -----------------------------------------------------------------
// Round 19 — palette / filter-intra / CFL default CDF tests.
// -----------------------------------------------------------------
/// §9.4 dimensions for every palette / filter-intra / CFL table,
/// matching the spec's declared array shapes, and the §8.2.6
/// well-formedness invariant (`cdf[N-1] == 32768`, `cdf[N] == 0`) on
/// every transcribed row.
#[test]
fn palette_group_table_dimensions_and_invariants() {
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
// Filter-intra.
assert_eq!(DEFAULT_FILTER_INTRA_MODE_CDF.len(), INTRA_FILTER_MODES + 1);
check(&DEFAULT_FILTER_INTRA_MODE_CDF);
assert_eq!(DEFAULT_FILTER_INTRA_CDF.len(), BLOCK_SIZES);
for r in &DEFAULT_FILTER_INTRA_CDF {
assert_eq!(r.len(), 3);
check(r);
}
// Palette mode / size.
assert_eq!(
DEFAULT_PALETTE_Y_MODE_CDF.len(),
PALETTE_BLOCK_SIZE_CONTEXTS
);
for plane in &DEFAULT_PALETTE_Y_MODE_CDF {
assert_eq!(plane.len(), PALETTE_Y_MODE_CONTEXTS);
for r in plane {
assert_eq!(r.len(), 3);
check(r);
}
}
assert_eq!(DEFAULT_PALETTE_UV_MODE_CDF.len(), PALETTE_UV_MODE_CONTEXTS);
for r in &DEFAULT_PALETTE_UV_MODE_CDF {
check(r);
}
assert_eq!(
DEFAULT_PALETTE_Y_SIZE_CDF.len(),
PALETTE_BLOCK_SIZE_CONTEXTS
);
assert_eq!(
DEFAULT_PALETTE_UV_SIZE_CDF.len(),
PALETTE_BLOCK_SIZE_CONTEXTS
);
for r in &DEFAULT_PALETTE_Y_SIZE_CDF {
assert_eq!(r.len(), PALETTE_SIZES + 1);
check(r);
}
for r in &DEFAULT_PALETTE_UV_SIZE_CDF {
assert_eq!(r.len(), PALETTE_SIZES + 1);
check(r);
}
// Palette colour-index: a size-K table codes K symbols (row len
// K + 1), with PALETTE_COLOR_CONTEXTS rows each.
macro_rules! check_color {
($tab:expr, $size:expr) => {{
assert_eq!($tab.len(), PALETTE_COLOR_CONTEXTS);
for r in &$tab {
assert_eq!(r.len(), $size + 1, "size-{} table row len", $size);
check(r);
}
}};
}
check_color!(DEFAULT_PALETTE_SIZE_2_Y_COLOR_CDF, 2);
check_color!(DEFAULT_PALETTE_SIZE_3_Y_COLOR_CDF, 3);
check_color!(DEFAULT_PALETTE_SIZE_4_Y_COLOR_CDF, 4);
check_color!(DEFAULT_PALETTE_SIZE_5_Y_COLOR_CDF, 5);
check_color!(DEFAULT_PALETTE_SIZE_6_Y_COLOR_CDF, 6);
check_color!(DEFAULT_PALETTE_SIZE_7_Y_COLOR_CDF, 7);
check_color!(DEFAULT_PALETTE_SIZE_8_Y_COLOR_CDF, 8);
check_color!(DEFAULT_PALETTE_SIZE_2_UV_COLOR_CDF, 2);
check_color!(DEFAULT_PALETTE_SIZE_3_UV_COLOR_CDF, 3);
check_color!(DEFAULT_PALETTE_SIZE_4_UV_COLOR_CDF, 4);
check_color!(DEFAULT_PALETTE_SIZE_5_UV_COLOR_CDF, 5);
check_color!(DEFAULT_PALETTE_SIZE_6_UV_COLOR_CDF, 6);
check_color!(DEFAULT_PALETTE_SIZE_7_UV_COLOR_CDF, 7);
check_color!(DEFAULT_PALETTE_SIZE_8_UV_COLOR_CDF, 8);
// CFL.
assert_eq!(DEFAULT_CFL_SIGN_CDF.len(), CFL_JOINT_SIGNS + 1);
check(&DEFAULT_CFL_SIGN_CDF);
assert_eq!(DEFAULT_CFL_ALPHA_CDF.len(), CFL_ALPHA_CONTEXTS);
for r in &DEFAULT_CFL_ALPHA_CDF {
assert_eq!(r.len(), CFL_ALPHABET_SIZE + 1);
check(r);
}
}
/// §9.4 hand-picked byte-exact values, spot-checking each table type
/// at a row the spec lists explicitly.
#[test]
fn palette_group_byte_exact_values() {
assert_eq!(
DEFAULT_FILTER_INTRA_MODE_CDF,
[8949, 12776, 17211, 29558, 32768, 0]
);
// Filter-intra MiSize 0 and the "never used" filler at index 10.
assert_eq!(DEFAULT_FILTER_INTRA_CDF[0], [4621, 32768, 0]);
assert_eq!(DEFAULT_FILTER_INTRA_CDF[10], [16384, 32768, 0]);
// Palette Y-mode last block-size context, third ctx (the
// distinctive `{ 129, .. }` row).
assert_eq!(DEFAULT_PALETTE_Y_MODE_CDF[6][2], [129, 32768, 0]);
assert_eq!(DEFAULT_PALETTE_UV_MODE_CDF[1], [21488, 32768, 0]);
assert_eq!(
DEFAULT_PALETTE_Y_SIZE_CDF[0],
[7952, 13000, 18149, 21478, 25527, 29241, 32768, 0]
);
assert_eq!(
DEFAULT_PALETTE_UV_SIZE_CDF[6],
[1269, 5435, 10433, 18963, 21700, 25865, 32768, 0]
);
// Largest Y colour table, last ctx.
assert_eq!(
DEFAULT_PALETTE_SIZE_8_Y_COLOR_CDF[4],
[31028, 31270, 31504, 31705, 31927, 32153, 32392, 32768, 0]
);
// Smallest UV colour table.
assert_eq!(DEFAULT_PALETTE_SIZE_2_UV_COLOR_CDF[2], [8713, 32768, 0]);
assert_eq!(
DEFAULT_CFL_SIGN_CDF,
[1418, 2123, 13340, 18405, 26972, 28343, 32294, 32768, 0]
);
assert_eq!(
DEFAULT_CFL_ALPHA_CDF[0],
[
7637, 20719, 31401, 32481, 32657, 32688, 32692, 32696, 32700, 32704, 32708, 32712,
32716, 32720, 32724, 32768, 0
]
);
}
/// §8.3.1 init step for the palette / filter-intra / CFL group: every
/// working array matches its §9.4 default verbatim.
#[test]
fn init_from_defaults_copies_palette_group() {
let ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.filter_intra_mode, DEFAULT_FILTER_INTRA_MODE_CDF);
assert_eq!(ctx.filter_intra, DEFAULT_FILTER_INTRA_CDF);
assert_eq!(ctx.palette_y_mode, DEFAULT_PALETTE_Y_MODE_CDF);
assert_eq!(ctx.palette_uv_mode, DEFAULT_PALETTE_UV_MODE_CDF);
assert_eq!(ctx.palette_y_size, DEFAULT_PALETTE_Y_SIZE_CDF);
assert_eq!(ctx.palette_uv_size, DEFAULT_PALETTE_UV_SIZE_CDF);
assert_eq!(
ctx.palette_size_2_y_color,
DEFAULT_PALETTE_SIZE_2_Y_COLOR_CDF
);
assert_eq!(
ctx.palette_size_8_y_color,
DEFAULT_PALETTE_SIZE_8_Y_COLOR_CDF
);
assert_eq!(
ctx.palette_size_2_uv_color,
DEFAULT_PALETTE_SIZE_2_UV_COLOR_CDF
);
assert_eq!(
ctx.palette_size_8_uv_color,
DEFAULT_PALETTE_SIZE_8_UV_COLOR_CDF
);
assert_eq!(ctx.cfl_sign, DEFAULT_CFL_SIGN_CDF);
assert_eq!(ctx.cfl_alpha, DEFAULT_CFL_ALPHA_CDF);
}
/// §8.3.2 `palette_color_idx_*` array selection by `PaletteSize`:
/// a size-K palette selects the size-K colour table (row length
/// `K + 1`), and out-of-range sizes return `None`.
#[test]
fn palette_color_cdf_selected_by_size() {
let mut ctx = TileCdfContext::new_from_defaults();
for size in 2..=PALETTE_COLORS {
assert_eq!(
ctx.palette_y_color_cdf(size, 0).unwrap().len(),
size + 1,
"Y size-{size} colour row length"
);
assert_eq!(
ctx.palette_uv_color_cdf(size, 0).unwrap().len(),
size + 1,
"UV size-{size} colour row length"
);
}
assert!(ctx.palette_y_color_cdf(1, 0).is_none());
assert!(ctx.palette_y_color_cdf(9, 0).is_none());
assert!(ctx.palette_uv_color_cdf(1, 0).is_none());
assert!(ctx.palette_uv_color_cdf(9, 0).is_none());
// The selected row matches the §9.4 default for that ctx.
assert_eq!(
ctx.palette_y_color_cdf(4, 3).unwrap(),
&DEFAULT_PALETTE_SIZE_4_Y_COLOR_CDF[3]
);
}
/// §8.3.2 `has_palette_y` / `has_palette_uv` context formulas.
#[test]
fn palette_mode_contexts() {
assert_eq!(palette_y_mode_ctx(false, false), 0);
assert_eq!(palette_y_mode_ctx(true, false), 1);
assert_eq!(palette_y_mode_ctx(false, true), 1);
assert_eq!(palette_y_mode_ctx(true, true), 2);
assert_eq!(palette_uv_mode_ctx(0), 0);
assert_eq!(palette_uv_mode_ctx(4), 1);
}
/// §8.3.2 `Palette_Color_Context[ ColorContextHash ]` mapping: the
/// `-1` sentinels yield `None`, the rest map per the spec table.
#[test]
fn palette_color_context_map() {
// { -1, -1, 0, -1, -1, 4, 3, 2, 1 }
assert_eq!(palette_color_ctx(0), None);
assert_eq!(palette_color_ctx(1), None);
assert_eq!(palette_color_ctx(2), Some(0));
assert_eq!(palette_color_ctx(3), None);
assert_eq!(palette_color_ctx(4), None);
assert_eq!(palette_color_ctx(5), Some(4));
assert_eq!(palette_color_ctx(6), Some(3));
assert_eq!(palette_color_ctx(7), Some(2));
assert_eq!(palette_color_ctx(8), Some(1));
// Out of range.
assert_eq!(palette_color_ctx(9), None);
// Every valid result is a PALETTE_COLOR_CONTEXTS index.
for h in 0..=PALETTE_MAX_COLOR_CONTEXT_HASH {
if let Some(c) = palette_color_ctx(h) {
assert!(c < PALETTE_COLOR_CONTEXTS);
}
}
assert_eq!(PALETTE_COLOR_HASH_MULTIPLIERS, [1, 2, 2]);
}
/// §5.11.50 `get_palette_color_context` — spec example: every
/// neighbour holds palette index 0. `scores[0] = 2 + 1 + 2 = 5`;
/// after the partial selection sort `scores = [5, 0, 0, ...]`. Hash
/// = `5 * 1 + 0 * 2 + 0 * 2 = 5` which maps to `palette_color_ctx`
/// 4. `ColorOrder` keeps the natural ascending identity since `0`
/// is already at slot 0.
#[test]
fn palette_color_context_all_same_neighbor() {
let pcc = palette_color_context_from_neighbors(Some(0), Some(0), Some(0), 4).unwrap();
assert_eq!(pcc.color_context_hash, 5);
assert_eq!(palette_color_ctx(pcc.color_context_hash), Some(4));
assert_eq!(pcc.color_order, [0, 1, 2, 3, 4, 5, 6, 7]);
}
/// §5.11.50 — left = 0, above = 1, no above-left: `scores =
/// [2, 2, 0, ...]`. The partial selection sort picks the tying
/// `scores[1]` as the max at i = 1 (already in place) so the
/// sorted-scores layout is `[2, 2, 0, ...]` and `ColorOrder[0..3]
/// = [0, 1, 2]`. Hash = `2 * 1 + 2 * 2 + 0 * 2 = 6` -> ctx 3.
#[test]
fn palette_color_context_left_and_above_distinct() {
let pcc = palette_color_context_from_neighbors(Some(0), None, Some(1), 4).unwrap();
assert_eq!(pcc.color_context_hash, 6);
assert_eq!(palette_color_ctx(pcc.color_context_hash), Some(3));
assert_eq!(&pcc.color_order[..3], &[0, 1, 2]);
}
/// §5.11.50 — left = above = 1 (different from above-left = 0):
/// `scores[1] = 4`, `scores[0] = 1`. After the partial sort the
/// index `1` is promoted to slot 0; the remaining indices fall in
/// their original ascending order so the runner-up at slot 1 is
/// the original index `0`. Hash = `4 * 1 + 1 * 2 + 0 * 2 = 6` ->
/// ctx 3.
#[test]
fn palette_color_context_partial_sort_swap() {
let pcc = palette_color_context_from_neighbors(Some(1), Some(0), Some(1), 4).unwrap();
assert_eq!(pcc.color_context_hash, 6);
assert_eq!(palette_color_ctx(pcc.color_context_hash), Some(3));
// The §5.11.50 shift moves the winner from slot 1 to slot 0
// and pushes the original slot-0 entry down to slot 1.
assert_eq!(&pcc.color_order[..4], &[1, 0, 2, 3]);
}
/// §5.11.50 — three distinct neighbours: left = 2, above-left = 1,
/// above = 3. `scores[1] = 1`, `scores[2] = 2`, `scores[3] = 2`.
/// The partial sort promotes the first-encountered tied max
/// (index 2) to slot 0, then the second tied max (now at slot 3
/// after the shift) to slot 1; index 1 (the +1 above-left) stays
/// in its ascending position. Hash = `2 * 1 + 2 * 2 + 1 * 2 = 8`
/// -> ctx 1.
#[test]
fn palette_color_context_three_distinct_neighbors() {
let pcc = palette_color_context_from_neighbors(Some(2), Some(1), Some(3), 5).unwrap();
assert_eq!(pcc.color_context_hash, 8);
assert_eq!(palette_color_ctx(pcc.color_context_hash), Some(1));
assert_eq!(&pcc.color_order[..5], &[2, 3, 1, 0, 4]);
}
/// §5.11.50 — the no-neighbour case (the top-left corner of the
/// palette block, `r == 0` and `c == 0`): every `scores[i] = 0`,
/// the partial sort is a no-op, `ColorOrder` stays the identity,
/// and `ColorContextHash = 0` (which is one of the `-1` slots of
/// `Palette_Color_Context` and therefore unreachable from the
/// §5.11.49 walk — the spec guarantees the function isn't called
/// at the top-left position).
#[test]
fn palette_color_context_no_neighbors() {
let pcc = palette_color_context_from_neighbors(None, None, None, 2).unwrap();
assert_eq!(pcc.color_context_hash, 0);
assert_eq!(palette_color_ctx(pcc.color_context_hash), None);
assert_eq!(pcc.color_order, [0, 1, 2, 3, 4, 5, 6, 7]);
}
/// §5.11.50 — the `n` argument out of `2..=PALETTE_COLORS` is a
/// caller bug (the size cdf only emits values in that range). The
/// helper rejects both `n == 0` / `n == 1` (below the minimum
/// palette size of 2) and `n > PALETTE_COLORS` with `None`.
#[test]
fn palette_color_context_palette_size_out_of_range() {
assert!(palette_color_context_from_neighbors(Some(0), None, None, 0).is_none());
assert!(palette_color_context_from_neighbors(Some(0), None, None, 1).is_none());
assert!(
palette_color_context_from_neighbors(Some(0), None, None, PALETTE_COLORS + 1).is_none()
);
for n in 2..=PALETTE_COLORS {
assert!(palette_color_context_from_neighbors(Some(0), None, None, n).is_some());
}
}
/// §5.11.50 — every neighbour value must itself be a valid
/// palette index (`< PALETTE_COLORS`). The §5.11.49 walk decodes
/// `palette_color_idx_*` against a cdf row of width at most
/// `PALETTE_COLORS`, so a higher value would be a caller bug; the
/// helper rejects it rather than indexing `scores[]` out of range.
#[test]
fn palette_color_context_out_of_range_neighbor_rejected() {
assert!(
palette_color_context_from_neighbors(Some(PALETTE_COLORS as u8), None, None, 4)
.is_none()
);
assert!(
palette_color_context_from_neighbors(None, Some(PALETTE_COLORS as u8), None, 4)
.is_none()
);
assert!(
palette_color_context_from_neighbors(None, None, Some(PALETTE_COLORS as u8), 4)
.is_none()
);
}
/// §5.11.50 — every spec-realisable `(left, above_left, above)`
/// combination at every palette size produces a `ColorContextHash`
/// in `0..=PALETTE_MAX_COLOR_CONTEXT_HASH` and a `ColorOrder` that
/// is a permutation of `0..PALETTE_COLORS`. The §5.11.50 guards
/// constrain which combinations the §5.11.49 walk reaches:
///
/// * the spec function is never called at the top-left position
/// (`r == 0 && c == 0`); the §5.11.49 caller sets
/// `ColorMap[0][0]` directly via `color_index_map_*`, so the
/// all-`None` case is forbidden;
/// * `above_left` requires `r > 0 && c > 0` — it cannot be
/// present without `left` (which only requires `c > 0`) and
/// `above` (which only requires `r > 0`) also being present.
///
/// Every reachable hash must therefore map to a `Some(_)` ctx
/// through [`palette_color_ctx`], which cross-checks the spec
/// note that the `-1` entries of [`PALETTE_COLOR_CONTEXT`] are
/// unreachable from the §5.11.50 derivation.
#[test]
fn palette_color_context_hash_in_range_and_reachable_ctxs_resolve() {
let mut reachable: [bool; PALETTE_MAX_COLOR_CONTEXT_HASH + 1] =
[false; PALETTE_MAX_COLOR_CONTEXT_HASH + 1];
// (left_present, above_present, above_left_present) — the
// spec-realisable subset. Above-left requires both row and
// column > 0; the all-`None` combo is excluded.
let presence = [
(false, true, false),
(true, false, false),
(true, true, true),
];
for n in 2..=PALETTE_COLORS {
let palette: Vec<u8> = (0..n as u8).collect();
for &(has_l, has_a, has_al) in &presence {
let l_opts: Vec<Option<u8>> = if has_l {
palette.iter().map(|&v| Some(v)).collect()
} else {
vec![None]
};
let a_opts: Vec<Option<u8>> = if has_a {
palette.iter().map(|&v| Some(v)).collect()
} else {
vec![None]
};
let al_opts: Vec<Option<u8>> = if has_al {
palette.iter().map(|&v| Some(v)).collect()
} else {
vec![None]
};
for &l in &l_opts {
for &al in &al_opts {
for &a in &a_opts {
let pcc = palette_color_context_from_neighbors(l, al, a, n).unwrap();
assert!(
pcc.color_context_hash <= PALETTE_MAX_COLOR_CONTEXT_HASH,
"hash {} out of range for n={} neighbours=({:?},{:?},{:?})",
pcc.color_context_hash,
n,
l,
al,
a
);
reachable[pcc.color_context_hash] = true;
// `ColorOrder` is a permutation of 0..PALETTE_COLORS.
let mut seen = [false; PALETTE_COLORS];
for &v in &pcc.color_order {
assert!((v as usize) < PALETTE_COLORS);
assert!(!seen[v as usize]);
seen[v as usize] = true;
}
}
}
}
}
}
// Every reachable hash maps to a `Some(_)` ctx.
for (h, &is_reachable) in reachable.iter().enumerate() {
if is_reachable {
assert!(
palette_color_ctx(h).is_some(),
"reachable hash {} mapped to None",
h
);
}
}
// The `-1` entries of `PALETTE_COLOR_CONTEXT` (hashes 0, 1,
// 3, 4) must all be unreachable from the spec-realisable
// subset.
for &h in &[0usize, 1, 3, 4] {
assert!(!reachable[h], "hash {} should be unreachable", h);
}
}
/// §5.11.50 `get_palette_color_context( colorMap, r, c, n )` — the
/// 2-D entry point. Build a 4×4 `colorMap` and read at an interior
/// position; the helper should produce the same result as feeding
/// the three neighbours to
/// [`palette_color_context_from_neighbors`].
#[test]
fn palette_color_context_2d_entry_point() {
// colorMap:
// row 0: [0, 1, 2, 3]
// row 1: [1, 0, 1, 0]
// row 2: [2, 1, 0, 1]
// row 3: [3, 2, 1, 0]
let color_map: [u8; 16] = [0, 1, 2, 3, 1, 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 0];
// Position (2, 2): above = colorMap[1][2] = 1, above-left =
// colorMap[1][1] = 0, left = colorMap[2][1] = 1.
let pcc_2d = get_palette_color_context(&color_map, 4, 2, 2, 4).unwrap();
let pcc_ref = palette_color_context_from_neighbors(Some(1), Some(0), Some(1), 4).unwrap();
assert_eq!(pcc_2d, pcc_ref);
// Position (0, 0): no neighbours at all.
let pcc_origin = get_palette_color_context(&color_map, 4, 0, 0, 4).unwrap();
assert_eq!(pcc_origin.color_context_hash, 0);
assert_eq!(pcc_origin.color_order, [0, 1, 2, 3, 4, 5, 6, 7]);
// Position (0, 1): only the left neighbour (colorMap[0][0] = 0)
// is read; `r > 0` is false so above / above-left are skipped.
let pcc_top = get_palette_color_context(&color_map, 4, 0, 1, 4).unwrap();
let pcc_top_ref = palette_color_context_from_neighbors(Some(0), None, None, 4).unwrap();
assert_eq!(pcc_top, pcc_top_ref);
// Position (1, 0): only the above neighbour (colorMap[0][0] =
// 0) is read; `c > 0` is false so left / above-left are skipped.
let pcc_lhs = get_palette_color_context(&color_map, 4, 1, 0, 4).unwrap();
let pcc_lhs_ref = palette_color_context_from_neighbors(None, None, Some(0), 4).unwrap();
assert_eq!(pcc_lhs, pcc_lhs_ref);
}
/// §5.11.50 — `get_palette_color_context` boundary-rejection
/// cases: zero stride, out-of-range `n`, out-of-range column.
#[test]
fn palette_color_context_2d_out_of_range() {
let color_map: [u8; 4] = [0, 1, 2, 3];
assert!(get_palette_color_context(&color_map, 0, 0, 0, 4).is_none());
assert!(get_palette_color_context(&color_map, 4, 0, 4, 4).is_none());
assert!(get_palette_color_context(&color_map, 4, 0, 0, 1).is_none());
// Out-of-buffer (1, 0) at stride 4 needs at least 8 entries.
assert!(get_palette_color_context(&color_map, 4, 1, 0, 4).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// `palette_color_idx_y` default cdf selected via
/// [`palette_color_ctx`] applied to the
/// [`get_palette_color_context`] output. Confirms the helper +
/// cdf-selector + symbol-decoder chain produces an in-range
/// `palette_color_idx_y` and that the §8.2 adaptation runs.
#[test]
fn palette_color_idx_y_end_to_end_via_get_palette_color_context() {
let mut ctx = TileCdfContext::new_from_defaults();
// Build a small color map and pick an interior position where
// the derivation yields a hash that maps to a real ctx
// (left=0, above-left=0, above=0 -> hash 5 -> ctx 4).
let color_map: [u8; 9] = [0, 0, 0, 0, 0, 0, 0, 0, 0];
let pcc = get_palette_color_context(&color_map, 3, 1, 1, 4).unwrap();
let palette_color_ctx_v = palette_color_ctx(pcc.color_context_hash).unwrap();
assert_eq!(palette_color_ctx_v, 4);
let cdf = ctx
.palette_y_color_cdf(4, palette_color_ctx_v)
.unwrap()
.to_vec();
assert_eq!(cdf, DEFAULT_PALETTE_SIZE_4_Y_COLOR_CDF[4]);
// Decode a single symbol: feed the cdf's range a deterministic
// stream and confirm the symbol lands in `0..4`.
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let row = ctx.palette_y_color_cdf(4, palette_color_ctx_v).unwrap();
let sym = dec.read_symbol(row).unwrap();
assert!((sym as usize) < 4, "palette_color_idx_y is in 0..4");
// disable_cdf_update was true ⇒ row untouched.
assert_eq!(
ctx.palette_y_color_cdf(4, palette_color_ctx_v).unwrap(),
&DEFAULT_PALETTE_SIZE_4_Y_COLOR_CDF[4]
);
}
/// §8.3.2 `cfl_alpha_u` / `cfl_alpha_v` context formulas. The spec
/// notes `cfl_alpha_u` ctx == `cfl_alpha_signs - 2`; check that
/// identity across every joint-sign value that decodes a U component
/// (`signU != CFL_SIGN_ZERO`), with the §5.11.45 joint decomposition
/// `signU = (cfl_alpha_signs + 1) / 3`, `signV = (cfl_alpha_signs + 1) % 3`.
#[test]
fn cfl_alpha_contexts() {
// Spot-check both formulas against the §8.3.2 tables.
// cfl_alpha_u: (signU - 1) * 3 + signV.
assert_eq!(cfl_alpha_u_ctx(1, 0), 0);
assert_eq!(cfl_alpha_u_ctx(2, 2), 5);
// cfl_alpha_v: (signV - 1) * 3 + signU.
assert_eq!(cfl_alpha_v_ctx(0, 1), 0);
assert_eq!(cfl_alpha_v_ctx(2, 2), 5);
// The §8.3.2 note: for cfl_alpha_u, ctx == cfl_alpha_signs - 2.
for joint in 0..CFL_JOINT_SIGNS {
let sign_u = (joint + 1) / 3;
let sign_v = (joint + 1) % 3;
if sign_u != 0 {
let ctx = cfl_alpha_u_ctx(sign_u, sign_v);
assert_eq!(ctx, joint - 2, "cfl_alpha_u ctx == cfl_alpha_signs - 2");
assert!(ctx < CFL_ALPHA_CONTEXTS);
}
if sign_v != 0 {
assert!(cfl_alpha_v_ctx(sign_u, sign_v) < CFL_ALPHA_CONTEXTS);
}
}
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// `cfl_alpha_u` (16-value) default CDF selected by §8.3.2
/// `cfl_alpha_u_ctx`, confirming the selected row matches the §9.4
/// default and a valid §8.2.6 decode lands in range and adapts.
#[test]
fn decode_cfl_alpha_through_default_cdf() {
// signU = 1, signV = 0 ⇒ ctx = 0.
let ctx_idx = cfl_alpha_u_ctx(1, 0);
assert_eq!(ctx_idx, 0);
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.cfl_alpha;
let row = ctx.cfl_alpha_cdf(ctx_idx);
assert_eq!(row, &DEFAULT_CFL_ALPHA_CDF[0]);
let sym = dec.read_symbol(row).unwrap();
assert!(sym < CFL_ALPHABET_SIZE as u32, "cfl_alpha is in 0..16");
// §8.3 update ran (disable_cdf_update == false): the row changed
// but the §9.4 source is immutable.
assert_ne!(ctx.cfl_alpha, before, "read_symbol must adapt the CDF");
assert_eq!(DEFAULT_CFL_ALPHA_CDF[0][0], 7637);
}
/// §7.11.5 predict-chroma-from-luma — flat luma (or `alpha == 0`)
/// leaves the chroma `DC_PRED` base unchanged, because every
/// `L[i][j]` equals `lumaAvg` ⇒ the scaled high-frequency term is
/// zero. Exercises the walker method directly with a hand-seeded
/// `CurrFrame[0]` (luma) and `CurrFrame[1]` (chroma DC base).
#[test]
fn predict_chroma_from_luma_flat_luma_is_identity() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// 4:2:0: luma plane 16×16, chroma plane 8×8.
walker.ensure_curr_frame_plane(0, 16, 16);
walker.ensure_curr_frame_plane(1, 8, 8);
// Flat luma == 100.
if let Some(p) = walker.curr_frame[0].as_mut() {
p.samples.iter_mut().for_each(|s| *s = 100);
}
// Chroma DC base == 64.
if let Some(p) = walker.curr_frame[1].as_mut() {
p.samples.iter_mut().for_each(|s| *s = 64);
}
walker.max_luma_w = 16;
walker.max_luma_h = 16;
// Non-zero alpha; flat luma ⇒ still identity.
walker.predict_chroma_from_luma_into_curr_frame(1, 0, 0, TX_4X4, 1, 1, 8, 8);
let chroma = walker.curr_frame(1).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(
chroma[i * 8 + j],
64,
"flat luma keeps DC base at ({i},{j})"
);
}
}
}
/// §7.11.5 predict-chroma-from-luma — a non-flat luma gradient with
/// a non-zero alpha rewrites each chroma sample as `Clip1(dc +
/// Round2Signed(alpha * (L[i][j] - lumaAvg), 6))`. The test mirrors
/// the spec formula independently and asserts the walker output
/// matches sample-for-sample, including the `alpha`-sign flip.
#[test]
fn predict_chroma_from_luma_gradient_matches_spec_formula() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
walker.ensure_curr_frame_plane(0, 16, 16);
walker.ensure_curr_frame_plane(1, 8, 8);
// Luma gradient: sample(y, x) = 10 * y + x (in 0..255).
if let Some(p) = walker.curr_frame[0].as_mut() {
for y in 0..16u32 {
for x in 0..16u32 {
p.samples[(y * 16 + x) as usize] = (10 * y + x) as i32;
}
}
}
let dc_base = 70i32;
if let Some(p) = walker.curr_frame[1].as_mut() {
p.samples.iter_mut().for_each(|s| *s = dc_base);
}
walker.max_luma_w = 16;
walker.max_luma_h = 16;
let alpha = -3i32;
let (sub_x, sub_y) = (1u8, 1u8);
let (w, h) = (4usize, 4usize);
// Independent reference: §7.11.5 `L` / `lumaAvg`.
let luma_at = |y: u32, x: u32| -> i64 { (10 * (y.min(15)) + x.min(15)) as i64 };
let mut l = [[0i64; 4]; 4];
let mut luma_avg: i64 = 0;
for (i, lrow) in l.iter_mut().enumerate() {
let luma_y = ((i as u32) << sub_y).min(16 - (1 << sub_y));
for (j, lij) in lrow.iter_mut().enumerate() {
let luma_x = ((j as u32) << sub_x).min(16 - (1 << sub_x));
let mut t = 0i64;
for dy in 0..=(sub_y as u32) {
for dx in 0..=(sub_x as u32) {
t += luma_at(luma_y + dy, luma_x + dx);
}
}
let v = t << (3 - (sub_x as u32) - (sub_y as u32));
*lij = v;
luma_avg += v;
}
}
// Round2(luma_avg, log2_w + log2_h) = Round2(.., 4).
let luma_avg = round2_signed(luma_avg, 4);
walker.predict_chroma_from_luma_into_curr_frame(1, 0, 0, TX_4X4, sub_x, sub_y, alpha, 8);
let chroma = walker.curr_frame(1).unwrap();
let mut any_changed = false;
for i in 0..h {
for j in 0..w {
let scaled = round2_signed((alpha as i64) * (l[i][j] - luma_avg), 6);
let expected = ((dc_base as i64) + scaled).clamp(0, 255) as i32;
assert_eq!(
chroma[i * 8 + j],
expected,
"CfL sample ({i},{j}) must match the §7.11.5 formula"
);
if expected != dc_base {
any_changed = true;
}
}
}
assert!(
any_changed,
"gradient luma must perturb at least one sample"
);
}
/// §3 `INTRA_FILTER_SCALE_BITS` pins to 4 and the §7.11.2.3
/// `Intra_Filter_Taps` table is well-formed (5 modes × 8 positions ×
/// 7 taps), with a couple of anchor entries locked against the spec
/// listing.
#[test]
fn intra_filter_taps_table_well_formed() {
assert_eq!(INTRA_FILTER_SCALE_BITS, 4);
assert_eq!(INTRA_FILTER_TAPS.len(), INTRA_FILTER_MODES);
for mode in &INTRA_FILTER_TAPS {
assert_eq!(mode.len(), 8);
for pos in mode {
assert_eq!(pos.len(), 7);
}
}
// Mode 0, position 0 / mode 4, position 7 — spec anchors.
assert_eq!(INTRA_FILTER_TAPS[0][0], [-6, 10, 0, 0, 0, 12, 0]);
assert_eq!(INTRA_FILTER_TAPS[4][7], [-7, 0, 0, 1, 12, 1, 9]);
}
/// §7.11.2.3 recursive intra prediction — an independent
/// re-derivation of the per-`4×2` sub-block filter on an `8×8` block
/// must match `predict_intra_recursive` sample-for-sample, exercising
/// the `pred`-feedback paths (`i2 > 0` reads previously-filled rows,
/// `j4 > 0` reads the left column of `pred`).
#[test]
fn predict_intra_recursive_matches_spec_formula() {
let (w, h) = (8usize, 8usize);
let span = w + h;
// Head-extended edge buffers: offset 1 = corner, offset 2+k =
// AboveRow[k] / LeftCol[k].
let corner = 100u16;
let mut above_ext = vec![0u16; 2 * span + 2];
let mut left_ext = vec![0u16; 2 * span + 2];
above_ext[0] = corner;
above_ext[1] = corner;
left_ext[0] = corner;
left_ext[1] = corner;
for k in 0..span {
above_ext[2 + k] = (120 + k) as u16;
left_ext[2 + k] = (90 + 2 * k) as u16;
}
let mode = 3usize;
let bit_depth = 8u8;
let mut got = vec![0u16; w * h];
predict_intra_recursive(w, h, mode, bit_depth, &above_ext, &left_ext, &mut got).unwrap();
// Independent reference, directly from §7.11.2.3.
let above = |k: i32| -> i32 { above_ext[(k + 2) as usize] as i32 };
let left = |k: i32| -> i32 { left_ext[(k + 2) as usize] as i32 };
let taps = &INTRA_FILTER_TAPS[mode];
let mut want = vec![0u16; w * h];
let w4 = w >> 2;
let h2 = h >> 1;
for i2 in 0..h2 {
for j4 in 0..w4 {
let mut p = [0i32; 7];
for (i, pi) in p.iter_mut().enumerate() {
if i < 5 {
if i2 == 0 {
*pi = above(((j4 << 2) + i) as i32 - 1);
} else if j4 == 0 && i == 0 {
*pi = left(((i2 << 1) as i32) - 1);
} else {
*pi = want[((i2 << 1) - 1) * w + ((j4 << 2) + i - 1)] as i32;
}
} else if j4 == 0 {
*pi = left(((i2 << 1) as i32) + (i as i32) - 5);
} else {
*pi = want[((i2 << 1) + i - 5) * w + ((j4 << 2) - 1)] as i32;
}
}
for i1 in 0..2usize {
for j1 in 0..4usize {
let mut pr = 0i64;
for i in 0..7usize {
pr += (taps[(i1 << 2) + j1][i] as i64) * (p[i] as i64);
}
let v = round2_signed(pr, INTRA_FILTER_SCALE_BITS).clamp(0, 255) as u16;
want[((i2 << 1) + i1) * w + (j4 << 2) + j1] = v;
}
}
}
}
assert_eq!(
got, want,
"recursive intra must match the §7.11.2.3 formula"
);
}
/// §7.11.2.3 recursive intra prediction caller-bug guards —
/// out-of-range mode / undersized `pred` / degenerate dimensions.
#[test]
fn predict_intra_recursive_caller_bug_guards() {
let above = vec![0u16; 64];
let left = vec![0u16; 64];
let mut pred = vec![0u16; 64];
// mode >= INTRA_FILTER_MODES.
assert!(
predict_intra_recursive(8, 8, INTRA_FILTER_MODES, 8, &above, &left, &mut pred).is_err()
);
// w < 4.
assert!(predict_intra_recursive(2, 8, 0, 8, &above, &left, &mut pred).is_err());
// undersized pred.
let mut short = vec![0u16; 8];
assert!(predict_intra_recursive(8, 8, 0, 8, &above, &left, &mut short).is_err());
}
// -----------------------------------------------------------------
// Round 20 — transform-size group tests.
// -----------------------------------------------------------------
/// §9.4 default tables: every row terminates with `1 << 15` and a
/// zero adaptation counter (the §8.2.6 contract). Locks all five
/// transcribed transform-size tables row-by-row.
#[test]
fn tx_size_default_tables_well_formed() {
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for r in &DEFAULT_TX_8X8_CDF {
check(r);
}
for r in &DEFAULT_TX_16X16_CDF {
check(r);
}
for r in &DEFAULT_TX_32X32_CDF {
check(r);
}
for r in &DEFAULT_TX_64X64_CDF {
check(r);
}
for r in &DEFAULT_TXFM_SPLIT_CDF {
check(r);
}
// §3 dimensions held by every default table.
assert_eq!(DEFAULT_TX_8X8_CDF.len(), TX_SIZE_CONTEXTS);
assert_eq!(DEFAULT_TX_16X16_CDF.len(), TX_SIZE_CONTEXTS);
assert_eq!(DEFAULT_TX_32X32_CDF.len(), TX_SIZE_CONTEXTS);
assert_eq!(DEFAULT_TX_64X64_CDF.len(), TX_SIZE_CONTEXTS);
assert_eq!(DEFAULT_TXFM_SPLIT_CDF.len(), TXFM_PARTITION_CONTEXTS);
assert_eq!(DEFAULT_TX_8X8_CDF[0].len(), MAX_TX_DEPTH + 1);
assert_eq!(DEFAULT_TX_16X16_CDF[0].len(), MAX_TX_DEPTH + 2);
assert_eq!(DEFAULT_TX_32X32_CDF[0].len(), MAX_TX_DEPTH + 2);
assert_eq!(DEFAULT_TX_64X64_CDF[0].len(), MAX_TX_DEPTH + 2);
assert_eq!(DEFAULT_TXFM_SPLIT_CDF[0].len(), 3);
}
/// Spot-check the first / last byte of each §9.4 transcribed
/// transform-size table. If a digit was mis-keyed during the
/// transcription, the equality breaks.
#[test]
fn tx_size_table_byte_anchors() {
// First / last cumulative-frequency in each table.
assert_eq!(DEFAULT_TX_8X8_CDF[0][0], 19968);
assert_eq!(DEFAULT_TX_8X8_CDF[2][0], 24320);
assert_eq!(DEFAULT_TX_16X16_CDF[0][0], 12272);
assert_eq!(DEFAULT_TX_16X16_CDF[0][1], 30172);
assert_eq!(DEFAULT_TX_16X16_CDF[2][1], 30848);
assert_eq!(DEFAULT_TX_32X32_CDF[0][0], 12986);
assert_eq!(DEFAULT_TX_32X32_CDF[2][1], 25602);
assert_eq!(DEFAULT_TX_64X64_CDF[0][0], 5782);
assert_eq!(DEFAULT_TX_64X64_CDF[2][1], 22759);
// First / last row of Default_Txfm_Split_Cdf.
assert_eq!(DEFAULT_TXFM_SPLIT_CDF[0][0], 28581);
assert_eq!(
DEFAULT_TXFM_SPLIT_CDF[TXFM_PARTITION_CONTEXTS - 1][0],
16088
);
}
/// §8.3.1: a fresh context copies every transform-size default
/// in (independent of the source — the §9.4 array is not aliased).
#[test]
fn tx_size_init_from_defaults_copies_tables() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.tx_8x8, DEFAULT_TX_8X8_CDF);
assert_eq!(ctx.tx_16x16, DEFAULT_TX_16X16_CDF);
assert_eq!(ctx.tx_32x32, DEFAULT_TX_32X32_CDF);
assert_eq!(ctx.tx_64x64, DEFAULT_TX_64X64_CDF);
assert_eq!(ctx.txfm_split, DEFAULT_TXFM_SPLIT_CDF);
// Working-copy independence: mutating the context must not
// touch the §9.4 source.
ctx.txfm_split_cdf(0)[0] = 12345;
assert_ne!(ctx.txfm_split[0][0], DEFAULT_TXFM_SPLIT_CDF[0][0]);
assert_eq!(DEFAULT_TXFM_SPLIT_CDF[0][0], 28581);
}
/// §8.3.2 `tx_depth` selection: each `max_tx_depth` value picks the
/// expected `Default_Tx_*_Cdf` row and the row length matches the
/// spec's per-table symbol count (`MAX_TX_DEPTH + 1` for the
/// `maxTxDepth == 1` group, `MAX_TX_DEPTH + 2` for every other).
#[test]
fn tx_depth_cdf_selected_by_max_tx_depth() {
let mut ctx = TileCdfContext::new_from_defaults();
// maxTxDepth == 1 ⇒ TileTx8x8Cdf, row width MAX_TX_DEPTH + 1.
let row1 = ctx.tx_depth_cdf(1, 1).unwrap();
assert_eq!(row1.len(), MAX_TX_DEPTH + 1);
assert_eq!(row1, &DEFAULT_TX_8X8_CDF[1]);
// maxTxDepth == 2 ⇒ TileTx16x16Cdf.
let row2 = ctx.tx_depth_cdf(2, 2).unwrap();
assert_eq!(row2.len(), MAX_TX_DEPTH + 2);
assert_eq!(row2, &DEFAULT_TX_16X16_CDF[2]);
// maxTxDepth == 3 ⇒ TileTx32x32Cdf.
let row3 = ctx.tx_depth_cdf(3, 0).unwrap();
assert_eq!(row3, &DEFAULT_TX_32X32_CDF[0]);
// maxTxDepth == 4 ⇒ TileTx64x64Cdf.
let row4 = ctx.tx_depth_cdf(4, 2).unwrap();
assert_eq!(row4, &DEFAULT_TX_64X64_CDF[2]);
// maxTxDepth == 0 ⇒ tx_depth is not read; selection returns None.
assert!(ctx.tx_depth_cdf(0, 0).is_none());
// out-of-range max_tx_depth ⇒ None.
assert!(ctx.tx_depth_cdf(5, 0).is_none());
}
/// §8.3.2 `tx_depth` context formula:
/// `ctx = (above_w >= max_tx_width) + (left_h >= max_tx_height)`.
/// Cover all four neighbour-vs-max combinations and check the
/// strict-less-than semantics ("strictly less" doesn't count).
#[test]
fn tx_depth_context_formula() {
// both neighbours strictly smaller ⇒ 0.
assert_eq!(tx_depth_ctx(0, 0, 16, 16), 0);
// above hits the threshold ⇒ 1 (>= is the spec's relation).
assert_eq!(tx_depth_ctx(16, 0, 16, 16), 1);
// left hits ⇒ 1.
assert_eq!(tx_depth_ctx(0, 16, 16, 16), 1);
// both hit ⇒ 2.
assert_eq!(tx_depth_ctx(16, 16, 16, 16), 2);
// both strictly greater ⇒ also 2 (the >= still holds).
assert_eq!(tx_depth_ctx(32, 64, 16, 16), 2);
// Result is bounded by TX_SIZE_CONTEXTS.
for aw in [0u32, 8, 16, 32, 64] {
for lh in [0u32, 8, 16, 32, 64] {
assert!(tx_depth_ctx(aw, lh, 16, 16) < TX_SIZE_CONTEXTS);
}
}
}
/// §8.3.2 `txfm_split` context:
/// `ctx = (txSzSqrUp != maxTxSz) * 3 + (TX_SIZES - 1 - maxTxSz) * 6 + above + left`.
/// Walk the spec formula term-by-term for several reachable
/// `(above, left, txSzSqrUp, maxTxSz)` tuples, then check the
/// out-of-range and overflow guards.
#[test]
fn txfm_split_context_formula() {
// maxTxSz == TX_4X4 (= 0), tx_sz_sqr_up == 0, no neighbours ⇒
// ctx = 0 + (5 - 1 - 0) * 6 + 0 + 0 = 24.
assert_eq!(txfm_split_ctx(false, false, 0, 0), None);
// ^ 24 >= TXFM_PARTITION_CONTEXTS (21), so the helper returns
// None — only reachable tuples should land in-range.
// A reachable in-range tuple: maxTxSz == TX_64X64 (= 4),
// txSzSqrUp == 0 (split), no neighbours ⇒
// ctx = 1 * 3 + (5 - 1 - 4) * 6 + 0 + 0 = 3.
assert_eq!(txfm_split_ctx(false, false, 0, 4), Some(3));
// Same shape but both neighbours present ⇒ ctx = 3 + 2 = 5.
assert_eq!(txfm_split_ctx(true, true, 0, 4), Some(5));
// maxTxSz == TX_64X64, tx_sz_sqr_up == 4 (no split) ⇒ ctx = 0.
assert_eq!(txfm_split_ctx(false, false, 4, 4), Some(0));
// maxTxSz == TX_64X64, tx_sz_sqr_up == 4 (no split), both nbrs ⇒
// ctx = 0 + 0 + 1 + 1 = 2.
assert_eq!(txfm_split_ctx(true, true, 4, 4), Some(2));
// maxTxSz == TX_32X32 (= 3), split ⇒
// ctx = 3 + (5 - 1 - 3) * 6 + 0 + 0 = 9.
assert_eq!(txfm_split_ctx(false, false, 0, 3), Some(9));
// maxTxSz == TX_32X32, no split, both nbrs ⇒
// ctx = 0 + 6 + 1 + 1 = 8.
assert_eq!(txfm_split_ctx(true, true, 3, 3), Some(8));
// Out-of-range max_tx_sz.
assert_eq!(txfm_split_ctx(false, false, 0, TX_SIZES as u32), None);
// Every reachable in-range result is < TXFM_PARTITION_CONTEXTS.
for &max_tx_sz in &[0u32, 1, 2, 3, 4] {
for &split in &[0u32, 1] {
let sqr_up = if split == 1 { 0 } else { max_tx_sz };
for above in [false, true] {
for left in [false, true] {
if let Some(ctx) = txfm_split_ctx(above, left, sqr_up, max_tx_sz) {
assert!(
ctx < TXFM_PARTITION_CONTEXTS,
"ctx {ctx} out of range for max={max_tx_sz} split={split}"
);
}
}
}
}
}
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// `tx_depth` 3-value default CDF selected by §8.3.2 `tx_depth_ctx`,
/// confirming the selected row matches the §9.4 default and a valid
/// §8.2.6 decode lands in range and adapts.
#[test]
fn decode_tx_depth_through_default_cdf() {
// aboveW == 16, leftH == 16, both equal maxTxWidth/Height = 16
// ⇒ ctx = 2 (both >= threshold).
let ctx_idx = tx_depth_ctx(16, 16, 16, 16);
assert_eq!(ctx_idx, 2);
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.tx_16x16;
// maxTxDepth == 2 ⇒ TileTx16x16Cdf[ ctx_idx ].
let row = ctx.tx_depth_cdf(2, ctx_idx).unwrap();
assert_eq!(row, &DEFAULT_TX_16X16_CDF[2]);
let sym = dec.read_symbol(row).unwrap();
// tx_depth in 0..MAX_TX_DEPTH + 1 (the row carries 3 symbols).
assert!(sym <= MAX_TX_DEPTH as u32, "tx_depth in 0..=MAX_TX_DEPTH");
// §8.3 update ran (the working copy mutated) but the §9.4
// source is immutable.
assert_ne!(ctx.tx_16x16, before, "read_symbol must adapt the CDF");
assert_eq!(DEFAULT_TX_16X16_CDF[2][0], 18677);
}
/// End-to-end: drive the §8.2 `SymbolDecoder` through a `txfm_split`
/// (binary, 3 entries with the trailing counter) default CDF row
/// selected by §8.3.2 `txfm_split_ctx`, confirming the row matches
/// the §9.4 default and the symbol is 0 or 1.
#[test]
fn decode_txfm_split_through_default_cdf() {
// maxTxSz = TX_64X64, no-split, both neighbours present ⇒ ctx = 2.
let ctx_idx = txfm_split_ctx(true, true, 4, 4).unwrap();
assert_eq!(ctx_idx, 2);
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.txfm_split;
let row = ctx.txfm_split_cdf(ctx_idx);
assert_eq!(row, &DEFAULT_TXFM_SPLIT_CDF[2]);
let sym = dec.read_symbol(row).unwrap();
assert!(sym <= 1, "txfm_split is binary");
assert_ne!(ctx.txfm_split, before, "read_symbol must adapt the CDF");
assert_eq!(DEFAULT_TXFM_SPLIT_CDF[2][0], 20847);
}
// -----------------------------------------------------------------
// Round 21 — inter-frame transform-type group tests.
// -----------------------------------------------------------------
/// §9.4 default tables: every row terminates with `1 << 15` and a
/// zero adaptation counter (§8.2.6 contract). Locks each
/// transcribed inter-tx-type default.
#[test]
fn inter_tx_type_default_tables_well_formed() {
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for r in &DEFAULT_INTER_TX_TYPE_SET1_CDF {
check(r);
}
check(&DEFAULT_INTER_TX_TYPE_SET2_CDF);
for r in &DEFAULT_INTER_TX_TYPE_SET3_CDF {
check(r);
}
// §3 dimensions.
assert_eq!(
DEFAULT_INTER_TX_TYPE_SET1_CDF.len(),
INTER_TX_TYPE_SET1_SIZES
);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET1_CDF[0].len(), TX_TYPES + 1);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET2_CDF.len(), TX_TYPES_SET2 + 1);
assert_eq!(
DEFAULT_INTER_TX_TYPE_SET3_CDF.len(),
INTER_TX_TYPE_SET3_SIZES
);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET3_CDF[0].len(), TX_TYPES_SET3 + 1);
}
/// Spot-check the first / last cumulative frequency in each §9.4
/// inter-tx-type transcribed table. A mis-keyed digit during
/// transcription breaks the equality.
#[test]
fn inter_tx_type_table_byte_anchors() {
// Default_Inter_Tx_Type_Set1_Cdf.
assert_eq!(DEFAULT_INTER_TX_TYPE_SET1_CDF[0][0], 4458);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET1_CDF[0][14], 30631);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET1_CDF[1][0], 1645);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET1_CDF[1][14], 30749);
// Default_Inter_Tx_Type_Set2_Cdf.
assert_eq!(DEFAULT_INTER_TX_TYPE_SET2_CDF[0], 770);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET2_CDF[10], 30529);
// Default_Inter_Tx_Type_Set3_Cdf.
assert_eq!(DEFAULT_INTER_TX_TYPE_SET3_CDF[0][0], 16384);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET3_CDF[1][0], 4167);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET3_CDF[2][0], 1998);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET3_CDF[3][0], 748);
}
/// §8.3.1: a fresh context copies every inter-tx-type default in
/// (independent of the source — the §9.4 array is not aliased).
#[test]
fn inter_tx_type_init_from_defaults_copies_tables() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.inter_tx_type_set1, DEFAULT_INTER_TX_TYPE_SET1_CDF);
assert_eq!(ctx.inter_tx_type_set2, DEFAULT_INTER_TX_TYPE_SET2_CDF);
assert_eq!(ctx.inter_tx_type_set3, DEFAULT_INTER_TX_TYPE_SET3_CDF);
// Working-copy independence: mutating the context must not
// touch the §9.4 source.
ctx.inter_tx_type_cdf(TX_SET_INTER_2, 0).unwrap()[0] = 12345;
assert_ne!(ctx.inter_tx_type_set2[0], DEFAULT_INTER_TX_TYPE_SET2_CDF[0]);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET2_CDF[0], 770);
}
/// §8.3.2 `inter_tx_type` selection: each `(set, tx_size_sqr)`
/// picks the expected `Default_Inter_Tx_Type_Set*_Cdf` row, the
/// row length matches the spec's per-set symbol count, and the
/// unreachable / `TX_SET_DCTONLY` paths return `None`.
#[test]
fn inter_tx_type_cdf_selected_by_set() {
let mut ctx = TileCdfContext::new_from_defaults();
// TX_SET_INTER_1, Tx_Size_Sqr = TX_4X4 = 0.
let row1 = ctx.inter_tx_type_cdf(TX_SET_INTER_1, 0).unwrap();
assert_eq!(row1.len(), TX_TYPES + 1);
assert_eq!(row1, &DEFAULT_INTER_TX_TYPE_SET1_CDF[0]);
// TX_SET_INTER_1, Tx_Size_Sqr = TX_8X8 = 1.
let row1b = ctx.inter_tx_type_cdf(TX_SET_INTER_1, 1).unwrap();
assert_eq!(row1b, &DEFAULT_INTER_TX_TYPE_SET1_CDF[1]);
// TX_SET_INTER_2: tx_size_sqr is ignored (single row).
let row2 = ctx.inter_tx_type_cdf(TX_SET_INTER_2, 0).unwrap();
assert_eq!(row2.len(), TX_TYPES_SET2 + 1);
assert_eq!(row2, &DEFAULT_INTER_TX_TYPE_SET2_CDF);
// Same row regardless of tx_size_sqr.
let row2b = ctx.inter_tx_type_cdf(TX_SET_INTER_2, 4).unwrap();
assert_eq!(row2b, &DEFAULT_INTER_TX_TYPE_SET2_CDF);
// TX_SET_INTER_3, all four reachable Tx_Size_Sqr values.
for sqr in 0..INTER_TX_TYPE_SET3_SIZES as u32 {
let row = ctx.inter_tx_type_cdf(TX_SET_INTER_3, sqr).unwrap();
assert_eq!(row.len(), TX_TYPES_SET3 + 1);
assert_eq!(row, &DEFAULT_INTER_TX_TYPE_SET3_CDF[sqr as usize]);
}
// TX_SET_DCTONLY: §5.11.47 forces TxType = DCT_DCT and skips
// the inter_tx_type read; the selector returns None.
assert!(ctx.inter_tx_type_cdf(TX_SET_DCTONLY, 0).is_none());
// Out-of-range tx_size_sqr for Set1 / Set3.
assert!(ctx
.inter_tx_type_cdf(TX_SET_INTER_1, INTER_TX_TYPE_SET1_SIZES as u32)
.is_none());
assert!(ctx
.inter_tx_type_cdf(TX_SET_INTER_3, INTER_TX_TYPE_SET3_SIZES as u32)
.is_none());
// Out-of-range set.
assert!(ctx.inter_tx_type_cdf(99, 0).is_none());
}
/// §5.11.48 `get_tx_set()` (inter branch). Walk every reachable
/// `(tx_sz_sqr, tx_sz_sqr_up, reduced_tx_set)` and confirm the
/// helper returns the spec-prescribed set.
#[test]
fn inter_tx_type_set_get_tx_set_inter_branch() {
// TX_4X4 / TX_8X8 with txSzSqrUp == txSzSqr, !reduced ⇒
// TX_SET_INTER_1.
assert_eq!(inter_tx_type_set(0, 0, false), TX_SET_INTER_1);
assert_eq!(inter_tx_type_set(1, 1, false), TX_SET_INTER_1);
// TX_16X16 (txSzSqr == 2), !reduced ⇒ TX_SET_INTER_2.
assert_eq!(inter_tx_type_set(2, 2, false), TX_SET_INTER_2);
// TX_32X32 (txSzSqrUp == 3), !reduced ⇒ TX_SET_INTER_3 (the
// `txSzSqrUp == TX_32X32` branch fires before the
// `txSzSqr == TX_16X16` branch).
assert_eq!(inter_tx_type_set(3, 3, false), TX_SET_INTER_3);
// Reduced-tx-set forces TX_SET_INTER_3 for any sqrUp <= 32x32.
assert_eq!(inter_tx_type_set(0, 0, true), TX_SET_INTER_3);
assert_eq!(inter_tx_type_set(2, 2, true), TX_SET_INTER_3);
assert_eq!(inter_tx_type_set(3, 3, true), TX_SET_INTER_3);
// txSzSqrUp > TX_32X32 ⇒ TX_SET_DCTONLY regardless of
// reduced_tx_set / txSzSqr.
assert_eq!(inter_tx_type_set(0, 4, false), TX_SET_DCTONLY);
assert_eq!(inter_tx_type_set(3, 4, true), TX_SET_DCTONLY);
// The rectangular-tx case (txSzSqr != txSzSqrUp): e.g.
// a TX_4X8 block has txSzSqr = TX_4X4 (0), txSzSqrUp = TX_8X8 (1).
// No reduced_tx_set ⇒ TX_SET_INTER_1.
assert_eq!(inter_tx_type_set(0, 1, false), TX_SET_INTER_1);
// A TX_16X32 / TX_32X16: txSzSqr = TX_16X16 (2),
// txSzSqrUp = TX_32X32 (3) ⇒ TX_SET_INTER_3 (sqrUp == 32x32
// wins over sqr == 16x16).
assert_eq!(inter_tx_type_set(2, 3, false), TX_SET_INTER_3);
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through an
/// `inter_tx_type` default CDF selected by the §8.3.2 selection,
/// confirming the chosen row matches the §9.4 source, the decode
/// lands in range and the working copy adapts.
#[test]
fn decode_inter_tx_type_through_default_cdf() {
// Pick TX_SET_INTER_3 (2-symbol row, easiest to bound the
// decoded symbol). Use a TX_8X8 block (Tx_Size_Sqr = 1).
let set = inter_tx_type_set(1, 1, true);
assert_eq!(set, TX_SET_INTER_3);
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.inter_tx_type_set3;
let row = ctx.inter_tx_type_cdf(set, 1).unwrap();
assert_eq!(row, &DEFAULT_INTER_TX_TYPE_SET3_CDF[1]);
let sym = dec.read_symbol(row).unwrap();
assert!(
sym <= 1,
"inter_tx_type via Set3 codes one of {{ IDTX, DCT_DCT }}"
);
assert_ne!(
ctx.inter_tx_type_set3, before,
"read_symbol must adapt the CDF"
);
assert_eq!(DEFAULT_INTER_TX_TYPE_SET3_CDF[1][0], 4167);
}
// -----------------------------------------------------------------
// Round 137 — intra-frame transform-type group tests.
// -----------------------------------------------------------------
/// §9.4 default tables: every row terminates with `1 << 15` and a
/// zero adaptation counter (§8.2.6 contract). Locks each
/// transcribed intra-tx-type default and confirms its §3 dimensions.
#[test]
fn intra_tx_type_default_tables_well_formed() {
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for plane in &DEFAULT_INTRA_TX_TYPE_SET1_CDF {
for row in plane {
check(row);
}
}
for plane in &DEFAULT_INTRA_TX_TYPE_SET2_CDF {
for row in plane {
check(row);
}
}
// §3 dimensions.
assert_eq!(
DEFAULT_INTRA_TX_TYPE_SET1_CDF.len(),
INTRA_TX_TYPE_SET1_SIZES
);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[0].len(), INTRA_MODES);
assert_eq!(
DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][0].len(),
TX_TYPES_INTRA_SET1 + 1
);
assert_eq!(
DEFAULT_INTRA_TX_TYPE_SET2_CDF.len(),
INTRA_TX_TYPE_SET2_SIZES
);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET2_CDF[0].len(), INTRA_MODES);
assert_eq!(
DEFAULT_INTRA_TX_TYPE_SET2_CDF[0][0].len(),
TX_TYPES_INTRA_SET2 + 1
);
}
/// Spot-check the first / last cumulative frequency in each §9.4
/// intra-tx-type transcribed table. A mis-keyed digit during
/// transcription breaks the equality.
#[test]
fn intra_tx_type_table_byte_anchors() {
// Default_Intra_Tx_Type_Set1_Cdf — TX_4X4 (size 0).
// Row 0 (DC_PRED): { 1535, 8035, 9461, 12751, 23467, 27825, 32768, 0 }
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][0][0], 1535);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][0][5], 27825);
// Row 12 (PAETH_PRED, last): { 3409, 5436, 10599, 15599, 19687, 24040, 32768, 0 }
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][12][0], 3409);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][12][5], 24040);
// Default_Intra_Tx_Type_Set1_Cdf — TX_8X8 (size 1).
// Row 0 (DC_PRED): { 1870, 13742, 14530, 16498, 23770, 27698, 32768, 0 }
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[1][0][0], 1870);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[1][0][5], 27698);
// Row 12 (PAETH_PRED): { 3511, 6332, 11165, 15335, 19323, 23594, 32768, 0 }
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[1][12][0], 3511);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[1][12][5], 23594);
// Default_Intra_Tx_Type_Set2_Cdf — TX_4X4 (size 0) and TX_8X8
// (size 1) are the §9.4 flat 5-way distribution.
for (size_idx, plane) in DEFAULT_INTRA_TX_TYPE_SET2_CDF.iter().enumerate().take(2) {
for (y, row) in plane.iter().enumerate() {
assert_eq!(
row,
&[6554, 13107, 19661, 26214, 32768, 0],
"Set2 size {size_idx} row {y} must be the §9.4 flat distribution"
);
}
}
// Default_Intra_Tx_Type_Set2_Cdf — TX_16X16 (size 2).
// Row 0 (DC_PRED): { 1127, 12814, 22772, 27483, 32768, 0 }
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET2_CDF[2][0][0], 1127);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET2_CDF[2][0][3], 27483);
// Row 12 (PAETH_PRED): { 1968, 5556, 12023, 18547, 32768, 0 }
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET2_CDF[2][12][0], 1968);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET2_CDF[2][12][3], 18547);
}
/// §8.3.1: a fresh context copies every intra-tx-type default in
/// (independent of the source — the §9.4 array is not aliased).
#[test]
fn intra_tx_type_init_from_defaults_copies_tables() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.intra_tx_type_set1, DEFAULT_INTRA_TX_TYPE_SET1_CDF);
assert_eq!(ctx.intra_tx_type_set2, DEFAULT_INTRA_TX_TYPE_SET2_CDF);
// Working-copy independence: mutating the context must not
// touch the §9.4 source.
ctx.intra_tx_type_cdf(TX_SET_INTRA_1, 0, 0).unwrap()[0] = 12345;
assert_ne!(
ctx.intra_tx_type_set1[0][0],
DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][0]
);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][0][0], 1535);
}
/// §8.3.2 `intra_tx_type` selection: each `(set, tx_size_sqr,
/// intra_dir)` triple picks the expected
/// `Default_Intra_Tx_Type_Set*_Cdf` row, the row length matches the
/// spec's per-set symbol count, and the unreachable /
/// `TX_SET_DCTONLY` paths return `None`.
#[test]
fn intra_tx_type_cdf_selected_by_set() {
let mut ctx = TileCdfContext::new_from_defaults();
// TX_SET_INTRA_1, Tx_Size_Sqr = TX_4X4 (=0), intraDir = DC_PRED (=0).
let row1 = ctx.intra_tx_type_cdf(TX_SET_INTRA_1, 0, 0).unwrap();
assert_eq!(row1.len(), TX_TYPES_INTRA_SET1 + 1);
assert_eq!(row1, &DEFAULT_INTRA_TX_TYPE_SET1_CDF[0][0]);
// TX_SET_INTRA_1, Tx_Size_Sqr = TX_8X8 (=1), intraDir = PAETH_PRED (=12).
let row1b = ctx.intra_tx_type_cdf(TX_SET_INTRA_1, 1, 12).unwrap();
assert_eq!(row1b, &DEFAULT_INTRA_TX_TYPE_SET1_CDF[1][12]);
// TX_SET_INTRA_2 across its three reachable sizes.
for sqr in 0..INTRA_TX_TYPE_SET2_SIZES as u32 {
for (dir, expected_row) in DEFAULT_INTRA_TX_TYPE_SET2_CDF[sqr as usize]
.iter()
.enumerate()
{
let row = ctx.intra_tx_type_cdf(TX_SET_INTRA_2, sqr, dir).unwrap();
assert_eq!(row.len(), TX_TYPES_INTRA_SET2 + 1);
assert_eq!(row, expected_row);
}
}
// TX_SET_DCTONLY: §5.11.47 forces TxType = DCT_DCT and skips
// the intra_tx_type read; the selector returns None.
assert!(ctx.intra_tx_type_cdf(TX_SET_DCTONLY, 0, 0).is_none());
// Out-of-range tx_size_sqr for Set1 (only TX_4X4 / TX_8X8) /
// Set2 (only TX_4X4 / TX_8X8 / TX_16X16).
assert!(ctx
.intra_tx_type_cdf(TX_SET_INTRA_1, INTRA_TX_TYPE_SET1_SIZES as u32, 0)
.is_none());
assert!(ctx
.intra_tx_type_cdf(TX_SET_INTRA_2, INTRA_TX_TYPE_SET2_SIZES as u32, 0)
.is_none());
// Out-of-range intra_dir.
assert!(ctx
.intra_tx_type_cdf(TX_SET_INTRA_1, 0, INTRA_MODES)
.is_none());
assert!(ctx
.intra_tx_type_cdf(TX_SET_INTRA_2, 2, INTRA_MODES)
.is_none());
// Out-of-range set (the inter set IDs share the same numeric
// space — but the intra selector should not return a row for an
// unreachable set ⇒ Set3 isn't an intra set; the inter values
// are themselves intra `1` / `2`, so a stray `set == 3` is
// invalid for the intra branch).
assert!(ctx.intra_tx_type_cdf(99, 0, 0).is_none());
}
/// §5.11.48 `get_tx_set()` (intra branch). Walk every reachable
/// `(tx_sz_sqr, tx_sz_sqr_up, reduced_tx_set)` and confirm the
/// helper returns the spec-prescribed set.
#[test]
fn intra_tx_type_set_get_tx_set_intra_branch() {
// TX_4X4 / TX_8X8 with txSzSqrUp == txSzSqr, !reduced ⇒
// TX_SET_INTRA_1.
assert_eq!(intra_tx_type_set(0, 0, false), TX_SET_INTRA_1);
assert_eq!(intra_tx_type_set(1, 1, false), TX_SET_INTRA_1);
// TX_16X16 (txSzSqr == 2), !reduced ⇒ TX_SET_INTRA_2.
assert_eq!(intra_tx_type_set(2, 2, false), TX_SET_INTRA_2);
// TX_32X32 (txSzSqrUp == 3), !reduced ⇒ TX_SET_DCTONLY (the
// §5.11.48 intra branch is stricter than the inter one — any
// `txSzSqrUp == TX_32X32` returns `TX_SET_DCTONLY`).
assert_eq!(intra_tx_type_set(3, 3, false), TX_SET_DCTONLY);
// Reduced-tx-set forces TX_SET_INTRA_2 for sqrUp < 32x32.
assert_eq!(intra_tx_type_set(0, 0, true), TX_SET_INTRA_2);
assert_eq!(intra_tx_type_set(1, 1, true), TX_SET_INTRA_2);
assert_eq!(intra_tx_type_set(2, 2, true), TX_SET_INTRA_2);
// txSzSqrUp >= TX_32X32 ⇒ TX_SET_DCTONLY regardless of
// reduced_tx_set / txSzSqr.
assert_eq!(intra_tx_type_set(3, 3, true), TX_SET_DCTONLY);
assert_eq!(intra_tx_type_set(0, 4, false), TX_SET_DCTONLY);
assert_eq!(intra_tx_type_set(3, 4, true), TX_SET_DCTONLY);
// The rectangular-tx case: a TX_4X8 block has txSzSqr = TX_4X4
// (0), txSzSqrUp = TX_8X8 (1). No reduced_tx_set ⇒
// TX_SET_INTRA_1.
assert_eq!(intra_tx_type_set(0, 1, false), TX_SET_INTRA_1);
// A TX_16X32 / TX_32X16: txSzSqr = TX_16X16 (2),
// txSzSqrUp = TX_32X32 (3) ⇒ TX_SET_DCTONLY (the `txSzSqrUp ==
// TX_32X32` gate fires before the `txSzSqr == TX_16X16` branch
// on the intra branch).
assert_eq!(intra_tx_type_set(2, 3, false), TX_SET_DCTONLY);
}
/// §8.3.2 `intraDir` derivation: with `use_filter_intra == 0` the
/// caller's `YMode` is passed through; with `use_filter_intra == 1`
/// the `Filter_Intra_Mode_To_Intra_Dir` table maps each filter mode
/// to a directional anchor (DC_PRED / V_PRED / H_PRED / D157_PRED /
/// DC_PRED).
#[test]
fn intra_dir_derivation() {
// Non-filter-intra: pass-through YMode.
for y in 0..INTRA_MODES {
assert_eq!(intra_dir(false, y, 0), Some(y));
}
// Out-of-range YMode (pass-through bound) returns None.
assert!(intra_dir(false, INTRA_MODES, 0).is_none());
// Filter-intra: Filter_Intra_Mode_To_Intra_Dir lookup.
// The spec listing reads { DC_PRED, V_PRED, H_PRED, D157_PRED,
// DC_PRED }, i.e. { 0, 1, 2, 6, 0 }.
assert_eq!(intra_dir(true, 99, 0), Some(0)); // FILTER_DC_PRED → DC_PRED
assert_eq!(intra_dir(true, 99, 1), Some(1)); // FILTER_V_PRED → V_PRED
assert_eq!(intra_dir(true, 99, 2), Some(2)); // FILTER_H_PRED → H_PRED
assert_eq!(intra_dir(true, 99, 3), Some(6)); // FILTER_D157_PRED → D157_PRED
assert_eq!(intra_dir(true, 99, 4), Some(0)); // FILTER_PAETH_PRED → DC_PRED
// Out-of-range filter_intra_mode returns None.
assert!(intra_dir(true, 0, INTRA_FILTER_MODES).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through an
/// `intra_tx_type` default CDF selected by the §8.3.2 selection,
/// confirming the chosen row matches the §9.4 source, the decode
/// lands in range and the working copy adapts.
#[test]
fn decode_intra_tx_type_through_default_cdf() {
// Pick TX_SET_INTRA_2 (5-symbol row), TX_16X16 (Tx_Size_Sqr =
// 2). intraDir = DC_PRED (the §9.4 row { 1127, 12814, 22772,
// 27483, 32768, 0 }).
let set = intra_tx_type_set(2, 2, false);
assert_eq!(set, TX_SET_INTRA_2);
let dir = intra_dir(false, 0, 0).unwrap();
assert_eq!(dir, 0);
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.intra_tx_type_set2;
let row = ctx.intra_tx_type_cdf(set, 2, dir).unwrap();
assert_eq!(row, &DEFAULT_INTRA_TX_TYPE_SET2_CDF[2][0]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < TX_TYPES_INTRA_SET2,
"intra_tx_type via Set2 codes one of \
{{ IDTX, DCT_DCT, ADST_ADST, ADST_DCT, DCT_ADST }}"
);
assert_ne!(
ctx.intra_tx_type_set2, before,
"read_symbol must adapt the CDF"
);
assert_eq!(DEFAULT_INTRA_TX_TYPE_SET2_CDF[2][0][0], 1127);
}
// Round 22 — inter-frame interpolation-filter group tests.
/// §8.3.1 / §9.4: the `Default_Interp_Filter_Cdf` table well-formed.
/// Each row is `[INTERP_FILTERS + 1]` (3 cumulative frequencies +
/// adaptation counter), the second-to-last entry is `1 << 15`, and
/// the last entry is 0. Outer dim matches `INTERP_FILTER_CONTEXTS`.
#[test]
fn interp_filter_default_table_well_formed() {
assert_eq!(DEFAULT_INTERP_FILTER_CDF.len(), INTERP_FILTER_CONTEXTS);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[0].len(), INTERP_FILTERS + 1);
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for r in &DEFAULT_INTERP_FILTER_CDF {
check(r);
}
}
/// Spot-check the §9.4 `Default_Interp_Filter_Cdf` values byte-for-byte.
/// A mis-keyed digit during transcription breaks the equality.
#[test]
fn interp_filter_default_byte_exact_values() {
// Row 0: { 31935, 32720, 32768, 0 } — strongest bias to filter 0.
assert_eq!(DEFAULT_INTERP_FILTER_CDF[0][0], 31935);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[0][1], 32720);
// Row 2: { 422, 2938, 32768, 0 } — strong bias to filter 2.
assert_eq!(DEFAULT_INTERP_FILTER_CDF[2][0], 422);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[2][1], 2938);
// Row 7: { 20889, 25637, 32768, 0 } — mixed-neighbour anchor.
assert_eq!(DEFAULT_INTERP_FILTER_CDF[7][0], 20889);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[7][1], 25637);
// Row 8: { 31910, 32724, 32768, 0 } — vertical-dir row 0.
assert_eq!(DEFAULT_INTERP_FILTER_CDF[8][0], 31910);
// Row 14: { 601, 943, 32768, 0 } — anchor near the end.
assert_eq!(DEFAULT_INTERP_FILTER_CDF[14][0], 601);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[14][1], 943);
// Row 15: { 14969, 21398, 32768, 0 } — last row.
assert_eq!(DEFAULT_INTERP_FILTER_CDF[15][0], 14969);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[15][1], 21398);
}
/// §8.3.1: a fresh context copies the interp-filter default in
/// (the §9.4 source is not aliased).
#[test]
fn interp_filter_init_from_defaults_copies_table() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.interp_filter, DEFAULT_INTERP_FILTER_CDF);
// Working-copy independence: mutating the context must not
// touch the §9.4 source.
ctx.interp_filter_cdf(0).unwrap()[0] = 12345;
assert_ne!(ctx.interp_filter[0][0], DEFAULT_INTERP_FILTER_CDF[0][0]);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[0][0], 31935);
}
/// §8.3.2 `interp_filter` ctx formula: walk the four §8.3.2
/// branches against §5.11.x scope inputs and assert the returned
/// `ctx` matches the spec literal exactly.
#[test]
fn interp_filter_context_formula() {
// Base (dir=0, single-ref): ((0 & 1) * 2 + 0) * 4 = 0.
// leftType == aboveType branch: ctx += leftType.
assert_eq!(interp_filter_ctx(0, 0, 0, false).unwrap(), 0);
assert_eq!(interp_filter_ctx(1, 1, 0, false).unwrap(), 1);
assert_eq!(interp_filter_ctx(2, 2, 0, false).unwrap(), 2);
// leftType == 3 (NONE) branch: ctx += aboveType.
assert_eq!(
interp_filter_ctx(0, INTERP_FILTER_NONE, 0, false).unwrap(),
0,
);
assert_eq!(
interp_filter_ctx(2, INTERP_FILTER_NONE, 0, false).unwrap(),
2,
);
// aboveType == 3 (NONE) branch: ctx += leftType.
assert_eq!(
interp_filter_ctx(INTERP_FILTER_NONE, 1, 0, false).unwrap(),
1,
);
// Distinct, both available: ctx += 3.
assert_eq!(interp_filter_ctx(0, 1, 0, false).unwrap(), 3);
assert_eq!(interp_filter_ctx(2, 0, 0, false).unwrap(), 3);
// dir=1, single-ref: ((1 & 1) * 2 + 0) * 4 = 8.
assert_eq!(interp_filter_ctx(0, 0, 1, false).unwrap(), 8);
assert_eq!(interp_filter_ctx(1, 1, 1, false).unwrap(), 9);
assert_eq!(interp_filter_ctx(0, 1, 1, false).unwrap(), 11); // ctx += 3
assert_eq!(
interp_filter_ctx(INTERP_FILTER_NONE, INTERP_FILTER_NONE, 1, false).unwrap(),
11,
);
// dir=0, compound (RefFrame[1] > INTRA_FRAME):
// ((0 & 1) * 2 + 1) * 4 = 4.
assert_eq!(interp_filter_ctx(0, 0, 0, true).unwrap(), 4);
assert_eq!(interp_filter_ctx(0, 1, 0, true).unwrap(), 7);
// dir=1, compound: ((1 & 1) * 2 + 1) * 4 = 12. Max bucket.
assert_eq!(interp_filter_ctx(0, 0, 1, true).unwrap(), 12);
assert_eq!(interp_filter_ctx(2, 2, 1, true).unwrap(), 14);
assert_eq!(interp_filter_ctx(0, 1, 1, true).unwrap(), 15);
// Out-of-range: dir > 1, type > INTERP_FILTERS.
assert_eq!(interp_filter_ctx(0, 0, 2, false), None);
assert_eq!(interp_filter_ctx(INTERP_FILTERS + 1, 0, 0, false), None);
assert_eq!(interp_filter_ctx(0, INTERP_FILTERS + 1, 0, false), None);
}
/// §8.3.2 `interp_filter` ctx coverage: every reachable ctx in
/// `0..INTERP_FILTER_CONTEXTS` is hit by some valid input.
#[test]
fn interp_filter_context_full_coverage() {
let mut hit = [false; INTERP_FILTER_CONTEXTS];
for dir in 0..=1 {
for is_comp in [false, true] {
for above in 0..=INTERP_FILTERS {
for left in 0..=INTERP_FILTERS {
if let Some(ctx) = interp_filter_ctx(above, left, dir, is_comp) {
hit[ctx] = true;
}
}
}
}
}
for (i, h) in hit.iter().enumerate() {
assert!(h, "ctx {i} unreachable");
}
}
/// §8.3.2 `interp_filter` selector returns the right §9.4 row for
/// each `ctx`, with the row length matching the spec
/// (`INTERP_FILTERS + 1`).
#[test]
fn interp_filter_selector_returns_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for (i, want) in DEFAULT_INTERP_FILTER_CDF.iter().enumerate() {
let row = ctx.interp_filter_cdf(i).unwrap();
assert_eq!(row.len(), INTERP_FILTERS + 1);
assert_eq!(row, want);
}
// Out-of-range ctx returns None.
assert!(ctx.interp_filter_cdf(INTERP_FILTER_CONTEXTS).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through an
/// `interp_filter` default CDF selected by the §8.3.2 selection,
/// confirming the chosen row matches the §9.4 source, the decode
/// lands in range and the working copy adapts.
#[test]
fn decode_interp_filter_through_default_cdf() {
// ctx=2 (Default_Interp_Filter_Cdf[2] = {422, 2938, 32768, 0}):
// a strongly-toward-filter-2 row. Drive the decoder with a
// window that lands in the high-symbol region, then assert
// the working copy mutated and the §9.4 source stayed put.
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.interp_filter;
let chosen = interp_filter_ctx(2, 2, 0, false).unwrap();
assert_eq!(chosen, 2, "leftType==aboveType, base=0");
let row = tile_ctx.interp_filter_cdf(chosen).unwrap();
assert_eq!(row, &DEFAULT_INTERP_FILTER_CDF[2]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < INTERP_FILTERS,
"interp_filter must code a symbol in 0..INTERP_FILTERS"
);
assert_ne!(
tile_ctx.interp_filter, before,
"read_symbol must adapt the working CDF"
);
assert_eq!(DEFAULT_INTERP_FILTER_CDF[2], [422, 2938, 32768, 0]);
}
// Round 23 — motion-mode group tests.
/// §8.3.1 / §9.4: the `Default_Motion_Mode_Cdf` table is
/// well-formed. Outer dim matches `BLOCK_SIZES`; each row is
/// `[MOTION_MODES + 1]` (3 cumulative frequencies + adaptation
/// counter); the second-to-last entry is `1 << 15` and the last is
/// 0.
#[test]
fn motion_mode_default_table_well_formed() {
assert_eq!(DEFAULT_MOTION_MODE_CDF.len(), BLOCK_SIZES);
assert_eq!(DEFAULT_MOTION_MODE_CDF[0].len(), MOTION_MODES + 1);
// §3 constant pinned: SIMPLE/OBMC/LOCALWARP ⇒ 3 motion modes.
assert_eq!(MOTION_MODES, 3);
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for r in &DEFAULT_MOTION_MODE_CDF {
check(r);
}
}
/// Spot-check the §9.4 `Default_Motion_Mode_Cdf` values byte-for-byte.
/// A mis-keyed digit during transcription breaks the equality.
#[test]
fn motion_mode_default_byte_exact_values() {
// Rows 0..=2: the spec-flagged-unreachable rows initialise to
// the flat `{ 10923, 21845, 32768, 0 }` — three roughly equal
// buckets (≈ 10923 = 32768/3).
assert_eq!(DEFAULT_MOTION_MODE_CDF[0], [10923, 21845, 32768, 0]);
assert_eq!(DEFAULT_MOTION_MODE_CDF[1], [10923, 21845, 32768, 0]);
assert_eq!(DEFAULT_MOTION_MODE_CDF[2], [10923, 21845, 32768, 0]);
// Row 3: { 7651, 24760, 32768, 0 } — first reachable row.
assert_eq!(DEFAULT_MOTION_MODE_CDF[3][0], 7651);
assert_eq!(DEFAULT_MOTION_MODE_CDF[3][1], 24760);
// Row 9: { 26260, 29116, 32768, 0 } — strong-SIMPLE bias anchor.
assert_eq!(DEFAULT_MOTION_MODE_CDF[9][0], 26260);
assert_eq!(DEFAULT_MOTION_MODE_CDF[9][1], 29116);
// Row 15: { 32507, 32558, 32768, 0 } — heaviest SIMPLE bias.
assert_eq!(DEFAULT_MOTION_MODE_CDF[15][0], 32507);
assert_eq!(DEFAULT_MOTION_MODE_CDF[15][1], 32558);
// Rows 16..=17: spec-unreachable, flat-init.
assert_eq!(DEFAULT_MOTION_MODE_CDF[16], [10923, 21845, 32768, 0]);
assert_eq!(DEFAULT_MOTION_MODE_CDF[17], [10923, 21845, 32768, 0]);
// Row 21: { 29742, 31203, 32768, 0 } — last row anchor.
assert_eq!(DEFAULT_MOTION_MODE_CDF[21][0], 29742);
assert_eq!(DEFAULT_MOTION_MODE_CDF[21][1], 31203);
}
/// §8.3.1: a fresh context copies the motion-mode default in (the
/// §9.4 source is not aliased).
#[test]
fn motion_mode_init_from_defaults_copies_table() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.motion_mode, DEFAULT_MOTION_MODE_CDF);
// Working-copy independence: mutating the context must not
// touch the §9.4 source.
ctx.motion_mode_cdf(3).unwrap()[0] = 12345;
assert_ne!(ctx.motion_mode[3][0], DEFAULT_MOTION_MODE_CDF[3][0]);
assert_eq!(DEFAULT_MOTION_MODE_CDF[3][0], 7651);
}
/// §8.3.2 `motion_mode` selector returns the right §9.4 row for
/// every `MiSize`, with row length matching the spec
/// (`MOTION_MODES + 1`).
#[test]
fn motion_mode_selector_returns_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for (i, want) in DEFAULT_MOTION_MODE_CDF.iter().enumerate() {
let row = ctx.motion_mode_cdf(i).unwrap();
assert_eq!(row.len(), MOTION_MODES + 1);
assert_eq!(row, want);
}
// Out-of-range `MiSize` returns None.
assert!(ctx.motion_mode_cdf(BLOCK_SIZES).is_none());
assert!(ctx.motion_mode_cdf(BLOCK_SIZES + 7).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// `motion_mode` default CDF selected by the §8.3.2 selection
/// (`MiSize = 15`, the heaviest-SIMPLE-bias row), confirming the
/// chosen row matches the §9.4 source, the decode lands in range
/// and the working copy adapts.
#[test]
fn decode_motion_mode_through_default_cdf() {
// Default_Motion_Mode_Cdf[15] = { 32507, 32558, 32768, 0 } —
// very strong SIMPLE bias. Drive the decoder with a typical
// start-of-tile window and assert the working copy mutates and
// the §9.4 source stays put.
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.motion_mode;
let row = tile_ctx.motion_mode_cdf(15).unwrap();
assert_eq!(row, &DEFAULT_MOTION_MODE_CDF[15]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < MOTION_MODES,
"motion_mode must code a symbol in 0..MOTION_MODES"
);
assert_ne!(
tile_ctx.motion_mode, before,
"read_symbol must adapt the working CDF"
);
assert_eq!(DEFAULT_MOTION_MODE_CDF[15], [32507, 32558, 32768, 0]);
}
// Round 24 — compound-prediction group tests.
/// §8.3.1 / §9.4: the three compound-prediction default tables are
/// well-formed. Outer dims match the §3 constants; each binary row
/// is `[3]` (`comp_group_idx` / `compound_idx`) or
/// `[COMPOUND_TYPES + 1]` (`compound_type`); the second-to-last
/// entry is `1 << 15` and the last is a fresh-0 adaptation counter.
#[test]
fn compound_default_tables_well_formed() {
// §3 constants pinned.
assert_eq!(COMPOUND_TYPES, 2);
assert_eq!(COMP_GROUP_IDX_CONTEXTS, 6);
assert_eq!(COMPOUND_IDX_CONTEXTS, 6);
assert_eq!(DEFAULT_COMP_GROUP_IDX_CDF.len(), COMP_GROUP_IDX_CONTEXTS);
assert_eq!(DEFAULT_COMPOUND_IDX_CDF.len(), COMPOUND_IDX_CONTEXTS);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF.len(), BLOCK_SIZES);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[0].len(), COMPOUND_TYPES + 1);
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
};
for r in &DEFAULT_COMP_GROUP_IDX_CDF {
assert_eq!(r.len(), 3);
check(r);
}
for r in &DEFAULT_COMPOUND_IDX_CDF {
assert_eq!(r.len(), 3);
check(r);
}
for r in &DEFAULT_COMPOUND_TYPE_CDF {
check(r);
}
}
/// Spot-check the §9.4 compound default values byte-for-byte. A
/// mis-keyed digit during transcription breaks the equality.
#[test]
fn compound_default_byte_exact_values() {
// Default_Compound_Idx_Cdf — 6 binary context rows.
assert_eq!(DEFAULT_COMPOUND_IDX_CDF[0], [18244, 32768, 0]);
assert_eq!(DEFAULT_COMPOUND_IDX_CDF[2], [7053, 32768, 0]);
assert_eq!(DEFAULT_COMPOUND_IDX_CDF[5], [4644, 32768, 0]);
// Default_Comp_Group_Idx_Cdf — 6 binary context rows.
assert_eq!(DEFAULT_COMP_GROUP_IDX_CDF[0], [26607, 32768, 0]);
assert_eq!(DEFAULT_COMP_GROUP_IDX_CDF[2], [18840, 32768, 0]);
assert_eq!(DEFAULT_COMP_GROUP_IDX_CDF[5], [22674, 32768, 0]);
// Default_Compound_Type_Cdf — the §9.4 note marks rows 0..=2,
// 10..=17 and 20..=21 unreachable; those carry the flat
// { 16384, 32768, 0 } placeholder.
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[0], [16384, 32768, 0]);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[2], [16384, 32768, 0]);
// Row 3: { 23431, 32768, 0 } — first reachable row.
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[3], [23431, 32768, 0]);
// Row 9: { 6172, 32768, 0 } — last of the 3..=9 reachable run.
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[9], [6172, 32768, 0]);
// Rows 10..=17: spec-unreachable, flat-init.
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[10], [16384, 32768, 0]);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[17], [16384, 32768, 0]);
// Rows 18 / 19: { 11820, .. } / { 7701, .. } — second reachable run.
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[18], [11820, 32768, 0]);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[19], [7701, 32768, 0]);
// Rows 20..=21: spec-unreachable, flat-init.
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[20], [16384, 32768, 0]);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[21], [16384, 32768, 0]);
}
/// §8.3.1: a fresh context copies the three compound-prediction
/// defaults in (the §9.4 sources are not aliased).
#[test]
fn compound_init_from_defaults_copies_tables() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.comp_group_idx, DEFAULT_COMP_GROUP_IDX_CDF);
assert_eq!(ctx.compound_idx, DEFAULT_COMPOUND_IDX_CDF);
assert_eq!(ctx.compound_type, DEFAULT_COMPOUND_TYPE_CDF);
// Working-copy independence: mutating the context must not touch
// the §9.4 sources.
ctx.comp_group_idx_cdf(0)[0] = 12345;
ctx.compound_idx_cdf(0)[0] = 23456;
ctx.compound_type_cdf(3).unwrap()[0] = 34567;
assert_ne!(ctx.comp_group_idx[0][0], DEFAULT_COMP_GROUP_IDX_CDF[0][0]);
assert_ne!(ctx.compound_idx[0][0], DEFAULT_COMPOUND_IDX_CDF[0][0]);
assert_ne!(ctx.compound_type[3][0], DEFAULT_COMPOUND_TYPE_CDF[3][0]);
assert_eq!(DEFAULT_COMP_GROUP_IDX_CDF[0][0], 26607);
assert_eq!(DEFAULT_COMPOUND_IDX_CDF[0][0], 18244);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[3][0], 23431);
}
/// §8.3.2 compound selectors return the right §9.4 rows. The two
/// binary selectors take a precomputed `ctx`; the `compound_type`
/// selector takes `MiSize` and returns `None` out of range.
#[test]
fn compound_selectors_return_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for (i, want) in DEFAULT_COMP_GROUP_IDX_CDF.iter().enumerate() {
let row = ctx.comp_group_idx_cdf(i);
assert_eq!(row.len(), 3);
assert_eq!(row, want);
}
for (i, want) in DEFAULT_COMPOUND_IDX_CDF.iter().enumerate() {
let row = ctx.compound_idx_cdf(i);
assert_eq!(row.len(), 3);
assert_eq!(row, want);
}
for (i, want) in DEFAULT_COMPOUND_TYPE_CDF.iter().enumerate() {
let row = ctx.compound_type_cdf(i).unwrap();
assert_eq!(row.len(), COMPOUND_TYPES + 1);
assert_eq!(row, want);
}
// Out-of-range `MiSize` returns None.
assert!(ctx.compound_type_cdf(BLOCK_SIZES).is_none());
assert!(ctx.compound_type_cdf(BLOCK_SIZES + 7).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// `compound_type` default CDF selected by the §8.3.2 selection
/// (`MiSize = 9`, the strongest-bias reachable row), confirming the
/// chosen row matches the §9.4 source, the decode lands in range and
/// the working copy adapts.
#[test]
fn decode_compound_type_through_default_cdf() {
// Default_Compound_Type_Cdf[9] = { 6172, 32768, 0 }.
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.compound_type;
let row = tile_ctx.compound_type_cdf(9).unwrap();
assert_eq!(row, &DEFAULT_COMPOUND_TYPE_CDF[9]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < COMPOUND_TYPES,
"compound_type must code a symbol in 0..COMPOUND_TYPES"
);
assert_ne!(
tile_ctx.compound_type, before,
"read_symbol must adapt the working CDF"
);
assert_eq!(DEFAULT_COMPOUND_TYPE_CDF[9], [6172, 32768, 0]);
}
/// End-to-end: drive the §8.2 `SymbolDecoder` through a binary
/// `comp_group_idx` default CDF (ctx = 2, the lowest-bias context),
/// confirming the row matches §9.4, the decode is in range and the
/// working copy adapts.
#[test]
fn decode_comp_group_idx_through_default_cdf() {
// Default_Comp_Group_Idx_Cdf[2] = { 18840, 32768, 0 }.
let bytes = [0x40u8, 0x00u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.comp_group_idx;
let row = tile_ctx.comp_group_idx_cdf(2);
assert_eq!(row, &DEFAULT_COMP_GROUP_IDX_CDF[2]);
let sym = dec.read_symbol(row).unwrap();
assert!((sym as usize) < 2, "comp_group_idx is a binary symbol");
assert_ne!(
tile_ctx.comp_group_idx, before,
"read_symbol must adapt the working CDF"
);
assert_eq!(DEFAULT_COMP_GROUP_IDX_CDF[2], [18840, 32768, 0]);
}
// Round 134 — inter-frame intra-mode group tests.
/// §8.3.1 / §9.4: the three inter-frame intra-mode default tables are
/// well-formed. Outer dims match the §3 constants; each row carries
/// the right number of cumulative frequencies + the adaptation
/// counter; the second-to-last entry is `1 << 15` and the last is a
/// fresh-0 adaptation counter; the cumulative frequencies are
/// strictly increasing up to `32768`.
#[test]
fn inter_intra_mode_default_tables_well_formed() {
// §3 constants pinned.
assert_eq!(BLOCK_SIZE_GROUPS, 4);
assert_eq!(UV_INTRA_MODES_CFL_NOT_ALLOWED, 13);
assert_eq!(UV_INTRA_MODES_CFL_ALLOWED, 14);
assert_eq!(INTRA_MODES, 13);
// Outer dimensions.
assert_eq!(DEFAULT_Y_MODE_CDF.len(), BLOCK_SIZE_GROUPS);
assert_eq!(DEFAULT_Y_MODE_CDF[0].len(), INTRA_MODES + 1);
assert_eq!(DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF.len(), INTRA_MODES);
assert_eq!(
DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[0].len(),
UV_INTRA_MODES_CFL_NOT_ALLOWED + 1
);
assert_eq!(DEFAULT_UV_MODE_CFL_ALLOWED_CDF.len(), INTRA_MODES);
assert_eq!(
DEFAULT_UV_MODE_CFL_ALLOWED_CDF[0].len(),
UV_INTRA_MODES_CFL_ALLOWED + 1
);
let check = |row: &[u16]| {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
// Cumulative frequencies strictly increase to 32768.
for w in row[..n].windows(2) {
assert!(w[0] < w[1], "cdf must be strictly increasing: {row:?}");
}
};
for r in &DEFAULT_Y_MODE_CDF {
check(r);
}
for r in &DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF {
check(r);
}
for r in &DEFAULT_UV_MODE_CFL_ALLOWED_CDF {
check(r);
}
}
/// Spot-check the §9.4 inter-frame intra-mode default values
/// byte-for-byte. A mis-keyed digit during transcription breaks the
/// equality.
#[test]
fn inter_intra_mode_default_byte_exact_values() {
// Default_Y_Mode_Cdf — first / last context rows.
assert_eq!(
DEFAULT_Y_MODE_CDF[0],
[
22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349,
32768, 0
]
);
assert_eq!(
DEFAULT_Y_MODE_CDF[3],
[
20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049,
32768, 0
]
);
// Leading-frequency anchors across the four contexts.
assert_eq!(DEFAULT_Y_MODE_CDF[1][0], 18673);
assert_eq!(DEFAULT_Y_MODE_CDF[2][0], 19770);
// Default_Uv_Mode_Cfl_Not_Allowed_Cdf — first / last YMode rows.
assert_eq!(
DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[0],
[
22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961,
32768, 0
]
);
assert_eq!(
DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[12],
[
12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606,
32768, 0
]
);
assert_eq!(DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[1][0], 9513);
assert_eq!(DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[6][6], 28551);
// Default_Uv_Mode_Cfl_Allowed_Cdf — first / last YMode rows.
assert_eq!(
DEFAULT_UV_MODE_CFL_ALLOWED_CDF[0],
[
10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455,
24212, 32768, 0
]
);
assert_eq!(
DEFAULT_UV_MODE_CFL_ALLOWED_CDF[12],
[
3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048,
32768, 0
]
);
assert_eq!(DEFAULT_UV_MODE_CFL_ALLOWED_CDF[1][0], 4532);
assert_eq!(DEFAULT_UV_MODE_CFL_ALLOWED_CDF[8][8], 18898);
}
/// §8.3.2 `Size_Group[ BLOCK_SIZES ]` table + [`size_group`] helper:
/// pinned byte-for-byte and confirmed to map into `0..BLOCK_SIZE_GROUPS`.
#[test]
fn size_group_table_pinned() {
assert_eq!(SIZE_GROUP.len(), BLOCK_SIZES);
assert_eq!(
SIZE_GROUP,
[0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2]
);
for (mi_size, &want) in SIZE_GROUP.iter().enumerate() {
let g = size_group(mi_size);
assert!(g < BLOCK_SIZE_GROUPS, "Size_Group must index a y_mode ctx");
assert_eq!(g, want);
}
}
/// §3 constants `MI_SIZE` / `MI_SIZE_LOG2` (av1-spec p.20) and the
/// `MI_SIZE == 1 << MI_SIZE_LOG2` identity that the §5.x decoder
/// relies on for `>> MI_SIZE_LOG2` ⇄ `* MI_SIZE` interchange.
#[test]
fn mi_size_constants_match_spec() {
assert_eq!(MI_SIZE, 4);
assert_eq!(MI_SIZE_LOG2, 2);
assert_eq!(MI_SIZE, 1usize << MI_SIZE_LOG2);
}
/// §9.3 `Mi_Width_Log2[ BLOCK_SIZES ]` (av1-spec p.400) — pinned
/// verbatim and confirmed full-width.
#[test]
fn mi_width_log2_table_pinned() {
assert_eq!(MI_WIDTH_LOG2.len(), BLOCK_SIZES);
assert_eq!(
MI_WIDTH_LOG2,
[0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 0, 2, 1, 3, 2, 4]
);
for (mi_size, &want) in MI_WIDTH_LOG2.iter().enumerate() {
assert_eq!(mi_width_log2(mi_size), want);
// Range bound — a `0..=5` log2 is the §3 ceiling for
// `Block_Width <= 128` (`MI_SIZE << 5 == 128`).
assert!(want <= 5);
}
}
/// §9.3 `Mi_Height_Log2[ BLOCK_SIZES ]` (av1-spec p.400) — pinned
/// verbatim and confirmed full-width.
#[test]
fn mi_height_log2_table_pinned() {
assert_eq!(MI_HEIGHT_LOG2.len(), BLOCK_SIZES);
assert_eq!(
MI_HEIGHT_LOG2,
[0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 2, 0, 3, 1, 4, 2]
);
for (mi_size, &want) in MI_HEIGHT_LOG2.iter().enumerate() {
assert_eq!(mi_height_log2(mi_size), want);
assert!(want <= 5);
}
}
/// §9.3 `Num_4x4_Blocks_Wide[ BLOCK_SIZES ]` (av1-spec p.400) —
/// pinned verbatim and confirmed equal to `1 << MI_WIDTH_LOG2[ x ]`
/// per the §9.3 derivation.
#[test]
fn num_4x4_blocks_wide_table_pinned() {
assert_eq!(NUM_4X4_BLOCKS_WIDE.len(), BLOCK_SIZES);
assert_eq!(
NUM_4X4_BLOCKS_WIDE,
[1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16]
);
for (mi_size, &want) in NUM_4X4_BLOCKS_WIDE.iter().enumerate() {
assert_eq!(num_4x4_blocks_wide(mi_size), want);
assert_eq!(
want,
1usize << MI_WIDTH_LOG2[mi_size],
"Num_4x4_Blocks_Wide[ x ] must equal 1 << Mi_Width_Log2[ x ] (§9.3)",
);
}
}
/// §9.3 `Num_4x4_Blocks_High[ BLOCK_SIZES ]` (av1-spec p.400) —
/// pinned verbatim and confirmed equal to `1 << MI_HEIGHT_LOG2[ x ]`
/// per the §9.3 derivation.
#[test]
fn num_4x4_blocks_high_table_pinned() {
assert_eq!(NUM_4X4_BLOCKS_HIGH.len(), BLOCK_SIZES);
assert_eq!(
NUM_4X4_BLOCKS_HIGH,
[1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4]
);
for (mi_size, &want) in NUM_4X4_BLOCKS_HIGH.iter().enumerate() {
assert_eq!(num_4x4_blocks_high(mi_size), want);
assert_eq!(
want,
1usize << MI_HEIGHT_LOG2[mi_size],
"Num_4x4_Blocks_High[ x ] must equal 1 << Mi_Height_Log2[ x ] (§9.3)",
);
}
}
/// §9.3 `Block_Width[ x ] = 4 * Num_4x4_Blocks_Wide[ x ]` (av1-spec
/// p.401). Confirms the [`block_width`] helper matches the spec
/// identity for every `MiSize`, and that the §3 enum's first 16
/// square/rectangular block sizes resolve to the canonical luma
/// widths (4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 32, 64, 64, 64,
/// 128, 128) plus the seven 1:4 / 4:1 aspect entries.
#[test]
fn block_width_matches_spec_identity() {
let expect = [
4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 32, 64, 64, 64, 128, 128, 4, 16, 8, 32, 16, 64,
];
for mi_size in 0..BLOCK_SIZES {
let w = block_width(mi_size);
assert_eq!(w, expect[mi_size]);
assert_eq!(w, 4 * NUM_4X4_BLOCKS_WIDE[mi_size]);
assert_eq!(w, NUM_4X4_BLOCKS_WIDE[mi_size] << MI_SIZE_LOG2);
// §3 ceiling: `Block_Width <= 128`.
assert!(w <= 128);
assert!(w >= 4);
}
}
/// §9.3 `Block_Height[ x ] = 4 * Num_4x4_Blocks_High[ x ]` (av1-spec
/// p.401). Companion to [`block_width_matches_spec_identity`].
#[test]
fn block_height_matches_spec_identity() {
let expect = [
4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 64, 32, 64, 128, 64, 128, 16, 4, 32, 8, 64, 16,
];
for mi_size in 0..BLOCK_SIZES {
let h = block_height(mi_size);
assert_eq!(h, expect[mi_size]);
assert_eq!(h, 4 * NUM_4X4_BLOCKS_HIGH[mi_size]);
assert_eq!(h, NUM_4X4_BLOCKS_HIGH[mi_size] << MI_SIZE_LOG2);
assert!(h <= 128);
assert!(h >= 4);
}
}
/// §3 `BLOCK_4X4 = 0`, `BLOCK_8X8 = 3`, `BLOCK_16X16 = 6`,
/// `BLOCK_32X32 = 9`, `BLOCK_64X64 = 12`, `BLOCK_128X128 = 15` —
/// spot-check the square diagonal of the §9.3 tables resolves to the
/// expected `n×n` luma dimensions. (The full enum ordering is
/// transcribed in `block_{width,height}_matches_spec_identity`.)
#[test]
fn square_diagonal_resolves_n_times_n() {
for (mi_size, n) in [(0, 4), (3, 8), (6, 16), (9, 32), (12, 64), (15, 128)] {
assert_eq!(block_width(mi_size), n);
assert_eq!(block_height(mi_size), n);
assert_eq!(num_4x4_blocks_wide(mi_size), n / MI_SIZE);
assert_eq!(num_4x4_blocks_high(mi_size), n / MI_SIZE);
}
}
/// §5.11.46 `bsizeCtx = Mi_Width_Log2[ MiSize ] +
/// Mi_Height_Log2[ MiSize ] - 2` — confirm the new §9.3 tables
/// reproduce the palette block-size context for every `MiSize`
/// inside the palette syntax gate. The §5.11.46 gate restricts
/// palette blocks to `MiSize >= BLOCK_8X8` and `Block_Width <= 64
/// && Block_Height <= 64` (av1-spec lines around the
/// `Block_Width[ MiSize ] <= 64` checks at p.93). Within that
/// window `bsizeCtx` is in `0..PALETTE_BLOCK_SIZE_CONTEXTS`.
#[test]
fn bsize_ctx_derivation_matches_palette_helper() {
for mi_size in 0..BLOCK_SIZES {
let bw = block_width(mi_size);
let bh = block_height(mi_size);
// §5.11.46 gate: square/rectangular blocks 8..=64 only.
if bw < 8 || bh < 8 || bw > 64 || bh > 64 {
continue;
}
let lw = mi_width_log2(mi_size);
let lh = mi_height_log2(mi_size);
assert!(lw + lh >= 2);
let bsize_ctx = lw + lh - 2;
assert!(
bsize_ctx < PALETTE_BLOCK_SIZE_CONTEXTS,
"MiSize {mi_size} ({bw}x{bh}) bsize_ctx {bsize_ctx} out of range",
);
}
}
/// §5.11.49 caller-side derivation: a `MiSize` directly drives the
/// `blockWidth` / `blockHeight` arguments that
/// [`palette_tokens_plane`] consumes. This pins the §9.3-table →
/// `palette_tokens_plane` data flow for the §5.11.49 caller wiring
/// targeted by the next implementer round.
#[test]
fn palette_tokens_args_from_mi_size_pins_data_flow() {
// BLOCK_8X8 (palette minimum, §5.11.46) and BLOCK_64X64
// (palette maximum, §5.11.46 — block_w/h <= 64).
for mi_size in [3usize, 12] {
let block_w = block_width(mi_size);
let block_h = block_height(mi_size);
assert!((8..=64).contains(&block_w));
assert!((8..=64).contains(&block_h));
// Stride at least matches block_w (caller-bug precondition in
// §5.11.49 `palette_tokens_plane` is `stride >= block_w`).
let stride = block_w;
let mut color_map = vec![0u8; stride * block_h];
// Seed (0,0) only — the rest is filled by the walker once the
// SymbolDecoder is wired. We're testing data-flow only, not
// decoding.
color_map[0] = 0;
assert_eq!(color_map.len(), stride * block_h);
}
}
/// §3 enum value: `BLOCK_8X8 == 3` and the §9.3 entry-3 row matches
/// an 8×8 block (`block_width == block_height == 8`).
#[test]
fn block_8x8_const_matches_size_3_row() {
assert_eq!(BLOCK_8X8, 3);
assert_eq!(block_width(BLOCK_8X8), 8);
assert_eq!(block_height(BLOCK_8X8), 8);
assert_eq!(num_4x4_blocks_wide(BLOCK_8X8), 2);
assert_eq!(num_4x4_blocks_high(BLOCK_8X8), 2);
assert_eq!(mi_width_log2(BLOCK_8X8), 1);
assert_eq!(mi_height_log2(BLOCK_8X8), 1);
}
/// §5.11.49 Y-plane caller derivation, block sitting entirely
/// on-screen: `onscreenWidth == blockWidth`, same for height.
#[test]
fn palette_tokens_args_y_block_fully_on_screen() {
// BLOCK_16X16 (idx 6), top-left of a 64×64 mi-grid (256×256
// luma samples). The block is well-inside the frame so the
// Min() clips to blockWidth/blockHeight.
let args = palette_tokens_args(6, 0, 0, 64, 64, PalettePlane::Y, 0, 0).unwrap();
assert_eq!(args.block_w, 16);
assert_eq!(args.block_h, 16);
assert_eq!(args.onscreen_w, 16);
assert_eq!(args.onscreen_h, 16);
}
/// §5.11.49 Y-plane caller derivation, block straddling the
/// frame's right edge: `onscreenWidth = (MiCols - MiCol) * MI_SIZE
/// < blockWidth`, but height is unaffected.
#[test]
fn palette_tokens_args_y_right_edge_clips_onscreen_w() {
// BLOCK_16X16 (16×16 luma). Frame is 6 mi-cols wide (24 luma).
// Block placed at mi_col=4 → block runs from luma col 16..32
// but the frame ends at col 24, so onscreen_w = 24 - 16 = 8.
let args = palette_tokens_args(6, 0, 4, 16, 6, PalettePlane::Y, 0, 0).unwrap();
assert_eq!(args.block_w, 16);
assert_eq!(args.block_h, 16);
assert_eq!(args.onscreen_w, 8);
assert_eq!(args.onscreen_h, 16);
// §5.11.49 invariant: 1 <= onscreen <= block on both axes.
assert!(args.onscreen_w >= 1 && args.onscreen_w <= args.block_w);
assert!(args.onscreen_h >= 1 && args.onscreen_h <= args.block_h);
}
/// §5.11.49 Y-plane caller derivation, block straddling the
/// frame's bottom edge: symmetric to the right-edge case.
#[test]
fn palette_tokens_args_y_bottom_edge_clips_onscreen_h() {
// BLOCK_16X16, frame is 5 mi-rows tall (20 luma) × 16 wide.
// Block at mi_row=2 → row 8..24, frame ends at 20 → onscreen_h = 12.
let args = palette_tokens_args(6, 2, 0, 5, 16, PalettePlane::Y, 0, 0).unwrap();
assert_eq!(args.block_w, 16);
assert_eq!(args.block_h, 16);
assert_eq!(args.onscreen_w, 16);
assert_eq!(args.onscreen_h, 12);
}
/// §5.11.49 UV branch (4:2:0): blockWidth >>= 1, blockHeight >>= 1,
/// no `<4`-bump because BLOCK_8X8 → 4×4 after shift (`< 4` test is
/// strict).
#[test]
fn palette_tokens_args_uv_420_min_block_no_bump() {
let args = palette_tokens_args(BLOCK_8X8, 0, 0, 8, 8, PalettePlane::Uv, 1, 1).unwrap();
assert_eq!(args.block_w, 4);
assert_eq!(args.block_h, 4);
assert_eq!(args.onscreen_w, 4);
assert_eq!(args.onscreen_h, 4);
}
/// §5.11.49 UV branch (4:2:0), bigger block: a 64×64 luma block
/// becomes 32×32 in chroma with no bump.
#[test]
fn palette_tokens_args_uv_420_large_no_bump() {
// BLOCK_64X64 = index 12 (4 * 2^4 = 64 on both axes).
let args = palette_tokens_args(12, 0, 0, 32, 32, PalettePlane::Uv, 1, 1).unwrap();
assert_eq!(args.block_w, 32);
assert_eq!(args.block_h, 32);
assert_eq!(args.onscreen_w, 32);
assert_eq!(args.onscreen_h, 32);
}
/// §5.11.49 UV branch `<4`-bump: BLOCK_4X16 (idx 16, luma 4×16) is
/// accepted by `MiSize >= BLOCK_8X8` (numerical enum, 16 >= 3) but
/// 4 >> 1 = 2 chroma samples wide. The spec then bumps:
/// `blockWidth += 2; onscreenWidth += 2` to 4, preserving the
/// `>= 4`-on-each-side invariant the walker relies on.
#[test]
fn palette_tokens_args_uv_420_width_lt_4_bump() {
// BLOCK_4X16 = index 16 (luma 4×16). UV 4:2:0 → 2×8 pre-bump,
// 4×8 post-bump (width bumped, height not).
let args = palette_tokens_args(16, 0, 0, 16, 16, PalettePlane::Uv, 1, 1).unwrap();
assert_eq!(args.block_w, 4);
assert_eq!(args.block_h, 8);
assert_eq!(args.onscreen_w, 4);
assert_eq!(args.onscreen_h, 8);
}
/// §5.11.49 UV branch `<4`-bump on the height axis: BLOCK_16X4
/// (idx 17, luma 16×4) → 8×2 pre-bump, 8×4 post-bump.
#[test]
fn palette_tokens_args_uv_420_height_lt_4_bump() {
let args = palette_tokens_args(17, 0, 0, 16, 16, PalettePlane::Uv, 1, 1).unwrap();
assert_eq!(args.block_w, 8);
assert_eq!(args.block_h, 4);
assert_eq!(args.onscreen_w, 8);
assert_eq!(args.onscreen_h, 4);
}
/// §5.11.49 UV branch (4:2:2): subsampling_x=1, subsampling_y=0.
/// BLOCK_8X8 luma → 4×8 chroma, no bump.
#[test]
fn palette_tokens_args_uv_422() {
let args = palette_tokens_args(BLOCK_8X8, 0, 0, 8, 8, PalettePlane::Uv, 1, 0).unwrap();
assert_eq!(args.block_w, 4);
assert_eq!(args.block_h, 8);
assert_eq!(args.onscreen_w, 4);
assert_eq!(args.onscreen_h, 8);
}
/// §5.11.49 UV branch (4:4:4): subsampling_x=0, subsampling_y=0.
/// UV dimensions equal Y dimensions.
#[test]
fn palette_tokens_args_uv_444_matches_y() {
let y = palette_tokens_args(6, 0, 0, 16, 16, PalettePlane::Y, 0, 0).unwrap();
let uv = palette_tokens_args(6, 0, 0, 16, 16, PalettePlane::Uv, 0, 0).unwrap();
assert_eq!(y, uv);
}
/// §5.11.49 UV branch: the right-edge `onscreen_w` clip carries
/// through the subsampling shift. BLOCK_16X16 at the right edge
/// (luma onscreen_w = 8) → chroma onscreen_w = 4 (no bump).
#[test]
fn palette_tokens_args_uv_420_right_edge_clip() {
let args = palette_tokens_args(6, 0, 4, 16, 6, PalettePlane::Uv, 1, 1).unwrap();
assert_eq!(args.block_w, 8);
assert_eq!(args.block_h, 8);
assert_eq!(args.onscreen_w, 4);
assert_eq!(args.onscreen_h, 8);
}
/// §5.11.49 invariant on the on-screen rectangle: the walker
/// requires `1 <= onscreen <= block` on each axis, post-bump. The
/// helper must preserve this across every legal `(MiSize, plane,
/// subsampling_*)` combination so the walker's caller-bug guards
/// never trigger on a conformant caller.
#[test]
fn palette_tokens_args_invariants_hold_for_every_palette_eligible_size() {
// Iterate every MiSize >= BLOCK_8X8 with both block dims <= 64
// (the §5.11.46 palette gate). Frame is 32×32 mi-grid so even
// the largest 64×64 block fits.
for mi_size in BLOCK_8X8..BLOCK_SIZES {
if block_width(mi_size) > 64 || block_height(mi_size) > 64 {
continue;
}
for sub_x in 0..=1 {
for sub_y in 0..=1 {
for plane in [PalettePlane::Y, PalettePlane::Uv] {
let args = palette_tokens_args(mi_size, 0, 0, 32, 32, plane, sub_x, sub_y)
.unwrap_or_else(|| {
panic!(
"palette_tokens_args returned None for \
mi_size={mi_size} sub_x={sub_x} \
sub_y={sub_y} plane={plane:?}"
)
});
assert!(args.onscreen_w >= 1);
assert!(args.onscreen_h >= 1);
assert!(args.onscreen_w <= args.block_w);
assert!(args.onscreen_h <= args.block_h);
// Walker stride assumption (block_w <= 64
// post-bump): the §5.11.49 vertical border-fill
// uses a `[u8; 64]` snapshot.
assert!(args.block_w <= 64);
assert!(args.block_h <= 64);
}
}
}
}
}
/// §5.11.49 caller-side derivation: rejects sub-`BLOCK_8X8` sizes
/// (the §5.11.46 palette gate) and out-of-table indices.
#[test]
fn palette_tokens_args_rejects_palette_gate_violations() {
// BLOCK_4X4 (idx 0) < BLOCK_8X8 → None.
assert!(palette_tokens_args(0, 0, 0, 16, 16, PalettePlane::Y, 0, 0).is_none());
// BLOCK_4X8 (idx 1) < BLOCK_8X8 → None.
assert!(palette_tokens_args(1, 0, 0, 16, 16, PalettePlane::Y, 0, 0).is_none());
// BLOCK_8X4 (idx 2) < BLOCK_8X8 → None.
assert!(palette_tokens_args(2, 0, 0, 16, 16, PalettePlane::Y, 0, 0).is_none());
// mi_size >= BLOCK_SIZES → None.
assert!(palette_tokens_args(BLOCK_SIZES, 0, 0, 16, 16, PalettePlane::Y, 0, 0).is_none());
assert!(
palette_tokens_args(BLOCK_SIZES + 5, 0, 0, 16, 16, PalettePlane::Y, 0, 0).is_none()
);
// BLOCK_128X128 (idx 15, 128×128) exceeds the <=64 palette
// cap → None.
assert!(palette_tokens_args(15, 0, 0, 64, 64, PalettePlane::Y, 0, 0).is_none());
// BLOCK_64X128 (idx 13, 64×128) exceeds the height cap → None.
assert!(palette_tokens_args(13, 0, 0, 64, 64, PalettePlane::Y, 0, 0).is_none());
}
/// §5.11.49 caller-side derivation: rejects caller bugs that would
/// otherwise produce a zero-size on-screen rectangle.
#[test]
fn palette_tokens_args_rejects_caller_bugs() {
// mi_row out of bounds.
assert!(palette_tokens_args(BLOCK_8X8, 8, 0, 8, 8, PalettePlane::Y, 0, 0).is_none());
// mi_col out of bounds.
assert!(palette_tokens_args(BLOCK_8X8, 0, 8, 8, 8, PalettePlane::Y, 0, 0).is_none());
// Zero mi-grid.
assert!(palette_tokens_args(BLOCK_8X8, 0, 0, 0, 8, PalettePlane::Y, 0, 0).is_none());
assert!(palette_tokens_args(BLOCK_8X8, 0, 0, 8, 0, PalettePlane::Y, 0, 0).is_none());
// Out-of-range subsampling flags.
assert!(palette_tokens_args(BLOCK_8X8, 0, 0, 8, 8, PalettePlane::Uv, 2, 0).is_none());
assert!(palette_tokens_args(BLOCK_8X8, 0, 0, 8, 8, PalettePlane::Uv, 0, 2).is_none());
}
/// End-to-end shape: `palette_tokens_args` output drops straight
/// into `palette_tokens_plane`'s argument list without any
/// `InvalidPaletteWalkArgs` failure when the inputs are conformant.
/// Uses the §8.2 SymbolDecoder + the §9.4 default palette CDFs so
/// the end-to-end data-flow is exercised on a real walker call.
#[test]
fn palette_tokens_args_feeds_walker_without_caller_bug() {
// BLOCK_8X8 Y plane, fully on-screen at frame origin.
let args = palette_tokens_args(BLOCK_8X8, 0, 0, 8, 8, PalettePlane::Y, 0, 0).unwrap();
// Compose a tiny byte buffer that init_symbol accepts; we only
// care that the walker's argument-validation passes (the
// decode itself can produce any indices).
let bytes = [0x80u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let stride = args.block_w;
let mut color_map = vec![0u8; stride * args.block_h];
// PaletteSizeY = 2 (smallest legal palette per §5.11.46).
let r = palette_tokens_plane(
&mut dec,
&mut tile_ctx,
PalettePlane::Y,
2,
args.block_w,
args.block_h,
args.onscreen_w,
args.onscreen_h,
0,
&mut color_map,
stride,
);
// The walker must not reject our args. The decode itself may
// bubble UnexpectedEnd or PaletteColorContextUnmapped depending
// on the random byte stream; we only assert the caller-bug
// path did not fire.
if let Err(crate::Error::InvalidPaletteWalkArgs) = r {
panic!("palette_tokens_args produced caller-bug-rejecting args");
}
}
/// §8.3.1: a fresh context copies the three inter-frame intra-mode
/// defaults in (the §9.4 sources are not aliased).
#[test]
fn inter_intra_mode_init_from_defaults_copies_tables() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.y_mode, DEFAULT_Y_MODE_CDF);
assert_eq!(
ctx.uv_mode_cfl_not_allowed,
DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF
);
assert_eq!(ctx.uv_mode_cfl_allowed, DEFAULT_UV_MODE_CFL_ALLOWED_CDF);
// Working-copy independence: mutating the context must not touch
// the §9.4 sources.
ctx.y_mode_cdf(0).unwrap()[0] = 12345;
ctx.uv_mode_cdf(false, 0).unwrap()[0] = 23456;
ctx.uv_mode_cdf(true, 0).unwrap()[0] = 34567;
assert_ne!(ctx.y_mode[0][0], DEFAULT_Y_MODE_CDF[0][0]);
assert_ne!(
ctx.uv_mode_cfl_not_allowed[0][0],
DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[0][0]
);
assert_ne!(
ctx.uv_mode_cfl_allowed[0][0],
DEFAULT_UV_MODE_CFL_ALLOWED_CDF[0][0]
);
assert_eq!(DEFAULT_Y_MODE_CDF[0][0], 22801);
assert_eq!(DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[0][0], 22631);
assert_eq!(DEFAULT_UV_MODE_CFL_ALLOWED_CDF[0][0], 10407);
}
/// §8.3.2 inter-frame intra-mode selectors return the right §9.4 rows.
/// `y_mode_cdf` takes `ctx = Size_Group[MiSize]`; `uv_mode_cdf` picks
/// the cfl-allowed / cfl-not-allowed variant by the resolved flag and
/// indexes by `YMode`. Both reject out-of-range indices with `None`.
#[test]
fn inter_intra_mode_selectors_return_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for (i, want) in DEFAULT_Y_MODE_CDF.iter().enumerate() {
let row = ctx.y_mode_cdf(i).unwrap();
assert_eq!(row.len(), INTRA_MODES + 1);
assert_eq!(row, want);
}
assert!(ctx.y_mode_cdf(BLOCK_SIZE_GROUPS).is_none());
assert!(ctx.y_mode_cdf(BLOCK_SIZE_GROUPS + 3).is_none());
for (i, want) in DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF.iter().enumerate() {
let row = ctx.uv_mode_cdf(false, i).unwrap();
assert_eq!(row.len(), UV_INTRA_MODES_CFL_NOT_ALLOWED + 1);
assert_eq!(row, want);
}
for (i, want) in DEFAULT_UV_MODE_CFL_ALLOWED_CDF.iter().enumerate() {
let row = ctx.uv_mode_cdf(true, i).unwrap();
assert_eq!(row.len(), UV_INTRA_MODES_CFL_ALLOWED + 1);
assert_eq!(row, want);
}
// Out-of-range `YMode` returns None for both variants.
assert!(ctx.uv_mode_cdf(false, INTRA_MODES).is_none());
assert!(ctx.uv_mode_cdf(true, INTRA_MODES + 5).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a `y_mode`
/// default CDF selected by the §8.3.2 selection
/// (`ctx = Size_Group[ MiSize ]` for a 64×64 `MiSize`, the
/// largest-block context 3), confirming the chosen row matches the
/// §9.4 source, the decode lands in range and the working copy adapts.
#[test]
fn decode_y_mode_through_default_cdf() {
// MiSize = 12 (BLOCK_64X64) → Size_Group[12] = 3.
assert_eq!(size_group(12), 3);
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.y_mode;
let ctx = size_group(12);
let row = tile_ctx.y_mode_cdf(ctx).unwrap();
assert_eq!(row, &DEFAULT_Y_MODE_CDF[3]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < INTRA_MODES,
"y_mode must code a symbol in 0..INTRA_MODES"
);
assert_ne!(
tile_ctx.y_mode, before,
"read_symbol must adapt the working CDF"
);
assert_eq!(DEFAULT_Y_MODE_CDF[3][0], 20155);
}
/// End-to-end: drive the §8.2 `SymbolDecoder` through both `uv_mode`
/// variants (`cfl_allowed` true / false) for the same `YMode`,
/// confirming each selector picks the correct §9.4 table, the decode
/// lands in the matching value range and the working copy adapts.
#[test]
fn decode_uv_mode_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
// cfl-not-allowed: 13 coded values, YMode = 0.
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.uv_mode_cfl_not_allowed;
let row = tile_ctx.uv_mode_cdf(false, 0).unwrap();
assert_eq!(row, &DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[0]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < UV_INTRA_MODES_CFL_NOT_ALLOWED,
"uv_mode (cfl-not-allowed) must code 0..UV_INTRA_MODES_CFL_NOT_ALLOWED"
);
assert_ne!(
tile_ctx.uv_mode_cfl_not_allowed, before,
"read_symbol must adapt the working CDF"
);
}
// cfl-allowed: 14 coded values (UV_CFL_PRED included), YMode = 0.
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.uv_mode_cfl_allowed;
let row = tile_ctx.uv_mode_cdf(true, 0).unwrap();
assert_eq!(row, &DEFAULT_UV_MODE_CFL_ALLOWED_CDF[0]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < UV_INTRA_MODES_CFL_ALLOWED,
"uv_mode (cfl-allowed) must code 0..UV_INTRA_MODES_CFL_ALLOWED"
);
assert_ne!(
tile_ctx.uv_mode_cfl_allowed, before,
"read_symbol must adapt the working CDF"
);
}
assert_eq!(DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF[0][0], 22631);
assert_eq!(DEFAULT_UV_MODE_CFL_ALLOWED_CDF[0][0], 10407);
}
// Round 135 — angle-delta group tests.
/// §8.3.1 / §9.4: the angle-delta default table is well-formed. The
/// §3 constants are pinned, the outer dimension matches
/// `DIRECTIONAL_MODES`, each row carries `2 * MAX_ANGLE_DELTA + 1 = 7`
/// cumulative frequencies + the adaptation counter, the
/// second-to-last entry is `1 << 15` and the last is a fresh-0
/// counter, and the cumulative frequencies strictly increase to
/// `32768`.
#[test]
fn angle_delta_default_table_well_formed() {
// §3 constants pinned.
assert_eq!(DIRECTIONAL_MODES, 8);
assert_eq!(MAX_ANGLE_DELTA, 3);
assert_eq!(V_PRED, 1);
assert_eq!(2 * MAX_ANGLE_DELTA + 1, 7);
// Outer / inner dimensions.
assert_eq!(DEFAULT_ANGLE_DELTA_CDF.len(), DIRECTIONAL_MODES);
assert_eq!(
DEFAULT_ANGLE_DELTA_CDF[0].len(),
(2 * MAX_ANGLE_DELTA + 1) + 1
);
for row in &DEFAULT_ANGLE_DELTA_CDF {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0");
for w in row[..n].windows(2) {
assert!(w[0] < w[1], "cdf must be strictly increasing: {row:?}");
}
}
}
/// Spot-check the §9.4 angle-delta default values byte-for-byte. A
/// mis-keyed digit during transcription breaks the equality.
#[test]
fn angle_delta_default_byte_exact_values() {
// First / last directional-mode rows.
assert_eq!(
DEFAULT_ANGLE_DELTA_CDF[0],
[2180, 5032, 7567, 22776, 26989, 30217, 32768, 0]
);
assert_eq!(
DEFAULT_ANGLE_DELTA_CDF[7],
[3605, 10428, 12459, 17676, 21244, 30655, 32768, 0]
);
// A couple of interior rows + leading-frequency anchors.
assert_eq!(
DEFAULT_ANGLE_DELTA_CDF[3],
[4581, 11226, 15147, 17138, 21834, 28397, 32768, 0]
);
assert_eq!(DEFAULT_ANGLE_DELTA_CDF[1][0], 2301);
assert_eq!(DEFAULT_ANGLE_DELTA_CDF[2][0], 3780);
assert_eq!(DEFAULT_ANGLE_DELTA_CDF[4][0], 1737);
assert_eq!(DEFAULT_ANGLE_DELTA_CDF[5][2], 12485);
assert_eq!(DEFAULT_ANGLE_DELTA_CDF[6][4], 22561);
}
/// §8.3.1: a fresh context copies the angle-delta default in (the
/// §9.4 source is not aliased), and mutating the working copy leaves
/// the source untouched.
#[test]
fn angle_delta_init_from_defaults_copies_table() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.angle_delta, DEFAULT_ANGLE_DELTA_CDF);
// V_PRED maps to row 0.
ctx.angle_delta_cdf(V_PRED).unwrap()[0] = 12345;
assert_ne!(ctx.angle_delta[0][0], DEFAULT_ANGLE_DELTA_CDF[0][0]);
assert_eq!(DEFAULT_ANGLE_DELTA_CDF[0][0], 2180);
}
/// §8.3.2 `angle_delta_y` / `angle_delta_uv` selector: takes the
/// directional `YMode` / `UVMode` and rebases by `V_PRED`, returning
/// `TileAngleDeltaCdf[ mode - V_PRED ]`. Confirms every directional
/// mode maps to the right §9.4 row and that non-directional modes
/// (below `V_PRED`, or at/above `V_PRED + DIRECTIONAL_MODES`) return
/// `None`.
#[test]
fn angle_delta_selector_returns_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
// mode = V_PRED .. V_PRED + DIRECTIONAL_MODES - 1 → rows 0..8.
for (i, want) in DEFAULT_ANGLE_DELTA_CDF.iter().enumerate() {
let mode = V_PRED + i;
let row = ctx.angle_delta_cdf(mode).unwrap();
assert_eq!(row.len(), (2 * MAX_ANGLE_DELTA + 1) + 1);
assert_eq!(row, want);
}
// DC_PRED (0, below V_PRED) is non-directional → None.
assert!(ctx.angle_delta_cdf(0).is_none());
// SMOOTH_PRED (9 == V_PRED + DIRECTIONAL_MODES) and beyond → None.
assert!(ctx.angle_delta_cdf(V_PRED + DIRECTIONAL_MODES).is_none());
assert!(ctx
.angle_delta_cdf(V_PRED + DIRECTIONAL_MODES + 3)
.is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through an
/// angle-delta default CDF selected by the §8.3.2 selection (a
/// directional `YMode`, here `D45_PRED == V_PRED + 2`), confirming the
/// chosen row matches the §9.4 source, the decoded symbol lands in
/// `0..(2 * MAX_ANGLE_DELTA + 1)` and the working copy adapts.
#[test]
fn decode_angle_delta_through_default_cdf() {
// D45_PRED = 3 → angle-delta row 3 - V_PRED = 2.
let mode = V_PRED + 2;
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.angle_delta;
let row = tile_ctx.angle_delta_cdf(mode).unwrap();
assert_eq!(row, &DEFAULT_ANGLE_DELTA_CDF[2]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < (2 * MAX_ANGLE_DELTA + 1),
"angle_delta must code a symbol in 0..(2 * MAX_ANGLE_DELTA + 1)"
);
assert_ne!(
tile_ctx.angle_delta, before,
"read_symbol must adapt the working CDF"
);
assert_eq!(DEFAULT_ANGLE_DELTA_CDF[2][0], 3780);
}
// -----------------------------------------------------------------
// Round 136 — coefficient-token entry sub-group tests.
// -----------------------------------------------------------------
/// §3 coefficient-entry constants are pinned to their spec values.
#[test]
fn coeff_entry_constants_pinned() {
assert_eq!(PLANE_TYPES, 2);
assert_eq!(TX_SIZES, 5);
assert_eq!(COEFF_CDF_Q_CTXS, 4);
assert_eq!(TXB_SKIP_CONTEXTS, 13);
assert_eq!(EOB_COEF_CONTEXTS, 9);
assert_eq!(DC_SIGN_CONTEXTS, 3);
}
/// Every coefficient-entry default table carries the declared §9.4
/// dimensions, and every cumulative-frequency row is well-formed: the
/// last data entry is `1 << 15 == 32768`, the trailing adaptation
/// counter is `0`, and the cumulative frequencies strictly increase.
#[test]
fn coeff_entry_default_tables_well_formed() {
fn check_row(row: &[u16]) {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768: {row:?}");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0: {row:?}");
for w in row[..n].windows(2) {
assert!(w[0] < w[1], "cdf must strictly increase: {row:?}");
}
}
// Txb_Skip: [4][5][13][3].
assert_eq!(DEFAULT_TXB_SKIP_CDF.len(), COEFF_CDF_Q_CTXS);
for q in &DEFAULT_TXB_SKIP_CDF {
assert_eq!(q.len(), TX_SIZES);
for tx in q {
assert_eq!(tx.len(), TXB_SKIP_CONTEXTS);
for row in tx {
check_row(row);
}
}
}
// Eob_Pt_{16..256}: [4][2][2][N]; Eob_Pt_{512,1024}: [4][2][N].
macro_rules! check_eob_two {
($t:expr, $inner:expr) => {{
assert_eq!($t.len(), COEFF_CDF_Q_CTXS);
for q in &$t {
assert_eq!(q.len(), PLANE_TYPES);
for pt in q {
assert_eq!(pt.len(), 2);
for row in pt {
assert_eq!(row.len(), $inner);
check_row(row);
}
}
}
}};
}
check_eob_two!(DEFAULT_EOB_PT_16_CDF, 6);
check_eob_two!(DEFAULT_EOB_PT_32_CDF, 7);
check_eob_two!(DEFAULT_EOB_PT_64_CDF, 8);
check_eob_two!(DEFAULT_EOB_PT_128_CDF, 9);
check_eob_two!(DEFAULT_EOB_PT_256_CDF, 10);
macro_rules! check_eob_one {
($t:expr, $inner:expr) => {{
assert_eq!($t.len(), COEFF_CDF_Q_CTXS);
for q in &$t {
assert_eq!(q.len(), PLANE_TYPES);
for row in q {
assert_eq!(row.len(), $inner);
check_row(row);
}
}
}};
}
check_eob_one!(DEFAULT_EOB_PT_512_CDF, 11);
check_eob_one!(DEFAULT_EOB_PT_1024_CDF, 12);
// Eob_Extra: [4][5][2][9][3].
assert_eq!(DEFAULT_EOB_EXTRA_CDF.len(), COEFF_CDF_Q_CTXS);
for q in &DEFAULT_EOB_EXTRA_CDF {
assert_eq!(q.len(), TX_SIZES);
for tx in q {
assert_eq!(tx.len(), PLANE_TYPES);
for pt in tx {
assert_eq!(pt.len(), EOB_COEF_CONTEXTS);
for row in pt {
check_row(row);
}
}
}
}
// Dc_Sign: [4][2][3][3].
assert_eq!(DEFAULT_DC_SIGN_CDF.len(), COEFF_CDF_Q_CTXS);
for q in &DEFAULT_DC_SIGN_CDF {
assert_eq!(q.len(), PLANE_TYPES);
for pt in q {
assert_eq!(pt.len(), DC_SIGN_CONTEXTS);
for row in pt {
check_row(row);
}
}
}
}
/// Spot-check the §9.4 coefficient-entry default values byte-for-byte.
/// A mis-keyed digit during transcription breaks the equality.
#[test]
fn coeff_entry_default_byte_exact_values() {
// Txb_Skip first/last anchors (q0,tx0,ctx0) and (q0,tx0,ctx12).
assert_eq!(DEFAULT_TXB_SKIP_CDF[0][0][0], [31849, 32768, 0]);
assert_eq!(DEFAULT_TXB_SKIP_CDF[0][0][12], [32117, 32768, 0]);
// A non-trivial interior row (q3, tx4, ctx6 — the last triplet of
// the table, a placeholder 16384 context).
assert_eq!(DEFAULT_TXB_SKIP_CDF[3][4][12], [16384, 32768, 0]);
// Eob_Pt_16: first (q0,pt0,inter0) and last (q3,pt1,inter1) rows.
assert_eq!(
DEFAULT_EOB_PT_16_CDF[0][0][0],
[840, 1039, 1980, 4895, 32768, 0]
);
assert_eq!(
DEFAULT_EOB_PT_16_CDF[3][1][1],
[7297, 10767, 19273, 28194, 32768, 0]
);
// Eob_Pt_1024: longest EOB-position rows (12 wide).
assert_eq!(
DEFAULT_EOB_PT_1024_CDF[0][0],
[393, 421, 751, 1623, 3160, 6352, 13345, 18047, 22571, 25830, 32768, 0]
);
// Eob_Extra first anchor.
assert_eq!(DEFAULT_EOB_EXTRA_CDF[0][0][0][0], [16961, 32768, 0]);
// Dc_Sign: the `128 * N` fixed-point form, identical across the
// four q-contexts in the spec.
assert_eq!(DEFAULT_DC_SIGN_CDF[0][0][0], [128 * 125, 32768, 0]);
assert_eq!(DEFAULT_DC_SIGN_CDF[0][1][2], [128 * 135, 32768, 0]);
assert_eq!(DEFAULT_DC_SIGN_CDF[3][0][0], DEFAULT_DC_SIGN_CDF[0][0][0]);
assert_eq!(128 * 125u16, 16000);
}
/// §8.3.1 `init_coeff_cdfs` q-context derivation from `base_q_idx`,
/// covering each boundary (`<=20`, `<=60`, `<=120`, else).
#[test]
fn coeff_cdf_q_ctx_mapping() {
assert_eq!(coeff_cdf_q_ctx(0), 0);
assert_eq!(coeff_cdf_q_ctx(20), 0);
assert_eq!(coeff_cdf_q_ctx(21), 1);
assert_eq!(coeff_cdf_q_ctx(60), 1);
assert_eq!(coeff_cdf_q_ctx(61), 2);
assert_eq!(coeff_cdf_q_ctx(120), 2);
assert_eq!(coeff_cdf_q_ctx(121), 3);
assert_eq!(coeff_cdf_q_ctx(255), 3);
// Every result is a valid COEFF_CDF_Q_CTXS index.
for q in 0u8..=255 {
assert!(coeff_cdf_q_ctx(q) < COEFF_CDF_Q_CTXS);
}
}
/// §8.3.1 `init_coeff_cdfs`: each working coefficient-entry array is
/// set to a copy of the `Default_*_Cdf[ idx ]` slice for the derived
/// `idx`, and mutating the working copy leaves the §9.4 source intact.
#[test]
fn coeff_entry_init_coeff_cdfs_selects_q_ctx() {
// A fresh context is seeded from idx 0.
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.txb_skip, DEFAULT_TXB_SKIP_CDF[0]);
assert_eq!(ctx.dc_sign, DEFAULT_DC_SIGN_CDF[0]);
// init_coeff_cdfs re-selects the slice for a high base_q_idx.
ctx.init_coeff_cdfs(200); // -> idx 3
assert_eq!(ctx.txb_skip, DEFAULT_TXB_SKIP_CDF[3]);
assert_eq!(ctx.eob_pt_16, DEFAULT_EOB_PT_16_CDF[3]);
assert_eq!(ctx.eob_pt_32, DEFAULT_EOB_PT_32_CDF[3]);
assert_eq!(ctx.eob_pt_64, DEFAULT_EOB_PT_64_CDF[3]);
assert_eq!(ctx.eob_pt_128, DEFAULT_EOB_PT_128_CDF[3]);
assert_eq!(ctx.eob_pt_256, DEFAULT_EOB_PT_256_CDF[3]);
assert_eq!(ctx.eob_pt_512, DEFAULT_EOB_PT_512_CDF[3]);
assert_eq!(ctx.eob_pt_1024, DEFAULT_EOB_PT_1024_CDF[3]);
assert_eq!(ctx.eob_extra, DEFAULT_EOB_EXTRA_CDF[3]);
assert_eq!(ctx.dc_sign, DEFAULT_DC_SIGN_CDF[3]);
// And for a mid base_q_idx.
ctx.init_coeff_cdfs(50); // -> idx 1
assert_eq!(ctx.txb_skip, DEFAULT_TXB_SKIP_CDF[1]);
assert_eq!(ctx.eob_extra, DEFAULT_EOB_EXTRA_CDF[1]);
// Mutating the working copy leaves the §9.4 source untouched.
ctx.txb_skip_cdf(0, 0).unwrap()[0] = 12345;
assert_ne!(ctx.txb_skip[0][0], DEFAULT_TXB_SKIP_CDF[1][0][0]);
assert_eq!(DEFAULT_TXB_SKIP_CDF[1][0][0], [30371, 32768, 0]);
}
/// §8.3.2 selectors return the correct §9.4 rows and reject
/// out-of-range indices.
#[test]
fn coeff_entry_selectors_return_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults(); // idx 0
assert_eq!(
ctx.txb_skip_cdf(2, 5).unwrap(),
&DEFAULT_TXB_SKIP_CDF[0][2][5]
);
assert!(ctx.txb_skip_cdf(TX_SIZES, 0).is_none());
assert!(ctx.txb_skip_cdf(0, TXB_SKIP_CONTEXTS).is_none());
assert_eq!(
ctx.eob_pt_16_cdf(1, 1).unwrap(),
&DEFAULT_EOB_PT_16_CDF[0][1][1]
);
assert_eq!(
ctx.eob_pt_32_cdf(0, 0).unwrap(),
&DEFAULT_EOB_PT_32_CDF[0][0][0]
);
assert_eq!(
ctx.eob_pt_64_cdf(1, 0).unwrap(),
&DEFAULT_EOB_PT_64_CDF[0][1][0]
);
assert_eq!(
ctx.eob_pt_128_cdf(0, 1).unwrap(),
&DEFAULT_EOB_PT_128_CDF[0][0][1]
);
assert_eq!(
ctx.eob_pt_256_cdf(1, 1).unwrap(),
&DEFAULT_EOB_PT_256_CDF[0][1][1]
);
assert!(ctx.eob_pt_16_cdf(PLANE_TYPES, 0).is_none());
assert!(ctx.eob_pt_256_cdf(0, 2).is_none());
assert_eq!(
ctx.eob_pt_512_cdf(1).unwrap(),
&DEFAULT_EOB_PT_512_CDF[0][1]
);
assert_eq!(
ctx.eob_pt_1024_cdf(0).unwrap(),
&DEFAULT_EOB_PT_1024_CDF[0][0]
);
assert!(ctx.eob_pt_512_cdf(PLANE_TYPES).is_none());
assert_eq!(
ctx.eob_extra_cdf(4, 1, 8).unwrap(),
&DEFAULT_EOB_EXTRA_CDF[0][4][1][8]
);
assert!(ctx.eob_extra_cdf(TX_SIZES, 0, 0).is_none());
assert!(ctx.eob_extra_cdf(0, 0, EOB_COEF_CONTEXTS).is_none());
assert_eq!(
ctx.dc_sign_cdf(1, 2).unwrap(),
&DEFAULT_DC_SIGN_CDF[0][1][2]
);
assert!(ctx.dc_sign_cdf(PLANE_TYPES, 0).is_none());
assert!(ctx.dc_sign_cdf(0, DC_SIGN_CONTEXTS).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through a
/// coefficient-entry default CDF selected by the §8.3.2 selection,
/// confirming the chosen row matches the §9.4 source, the decoded
/// symbol is in range, and the working copy adapts.
#[test]
fn decode_coeff_entry_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
// txb_skip (all_zero): a binary symbol.
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.txb_skip;
let row = ctx.txb_skip_cdf(0, 0).unwrap();
assert_eq!(row, &DEFAULT_TXB_SKIP_CDF[0][0][0]);
let sym = dec.read_symbol(row).unwrap();
assert!((sym as usize) < 2, "all_zero must code 0 or 1");
assert_ne!(ctx.txb_skip, before, "read_symbol must adapt the CDF");
}
// eob_pt_16: a 5-class symbol.
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.eob_pt_16;
let row = ctx.eob_pt_16_cdf(0, 0).unwrap();
assert_eq!(row, &DEFAULT_EOB_PT_16_CDF[0][0][0]);
let sym = dec.read_symbol(row).unwrap();
assert!((sym as usize) < 5, "eob_pt_16 codes 0..5");
assert_ne!(ctx.eob_pt_16, before, "read_symbol must adapt the CDF");
}
// dc_sign: a binary symbol from the `128 * N` defaults.
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.dc_sign;
let row = ctx.dc_sign_cdf(0, 0).unwrap();
assert_eq!(row, &DEFAULT_DC_SIGN_CDF[0][0][0]);
let sym = dec.read_symbol(row).unwrap();
assert!((sym as usize) < 2, "dc_sign must code 0 or 1");
assert_ne!(ctx.dc_sign, before, "read_symbol must adapt the CDF");
}
}
// -----------------------------------------------------------------
// Round 138 — `coeff_base_eob` sub-group tests.
// -----------------------------------------------------------------
/// §3 `SIG_COEF_CONTEXTS_EOB` pins to its spec value.
#[test]
fn coeff_base_eob_constants_pinned() {
assert_eq!(SIG_COEF_CONTEXTS_EOB, 4);
}
/// `Default_Coeff_Base_Eob_Cdf` carries the declared §9.4 dimensions
/// and every cumulative-frequency row is well-formed: the last data
/// entry is `1 << 15 == 32768`, the trailing adaptation counter is
/// `0`, and the cumulative frequencies strictly increase.
#[test]
fn coeff_base_eob_default_table_well_formed() {
fn check_row(row: &[u16]) {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768: {row:?}");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0: {row:?}");
for w in row[..n].windows(2) {
assert!(w[0] < w[1], "cdf must strictly increase: {row:?}");
}
}
assert_eq!(DEFAULT_COEFF_BASE_EOB_CDF.len(), COEFF_CDF_Q_CTXS);
for q in &DEFAULT_COEFF_BASE_EOB_CDF {
assert_eq!(q.len(), TX_SIZES);
for tx in q {
assert_eq!(tx.len(), PLANE_TYPES);
for pt in tx {
assert_eq!(pt.len(), SIG_COEF_CONTEXTS_EOB);
for row in pt {
assert_eq!(row.len(), 4);
check_row(row);
}
}
}
}
}
/// Spot-check the §9.4 `Default_Coeff_Base_Eob_Cdf` initial values
/// byte-for-byte. A mis-keyed digit during transcription breaks the
/// equality. Anchors include the (q0, tx0) first / last context and
/// the flat-distribution placeholder row that pads every q-context's
/// last `(txSz, chroma)` slice.
#[test]
fn coeff_base_eob_default_byte_exact_values() {
// (q0, tx0, pt0): first context and last context of the row.
assert_eq!(
DEFAULT_COEFF_BASE_EOB_CDF[0][0][0][0],
[17837, 29055, 32768, 0]
);
assert_eq!(
DEFAULT_COEFF_BASE_EOB_CDF[0][0][0][3],
[24926, 28948, 32768, 0]
);
// (q0, tx0, pt1): chroma plane.
assert_eq!(
DEFAULT_COEFF_BASE_EOB_CDF[0][0][1][0],
[21365, 30026, 32768, 0]
);
// Every q-context's (tx4, pt1) slice is the same flat
// {10923, 21845, 32768, 0} placeholder for all four ctx values
// (the §9.4 sentinel for an unreachable chroma row at the
// largest TX size).
let flat = [10923u16, 21845, 32768, 0];
for (q, q_slice) in DEFAULT_COEFF_BASE_EOB_CDF.iter().enumerate() {
for (ctx, row) in q_slice[4][1].iter().enumerate() {
assert_eq!(
*row, flat,
"(q={q}, tx=4, pt=1, ctx={ctx}) must be the flat placeholder"
);
}
}
// (q3, tx3, pt1, ctx3): a non-trivial interior anchor — the
// last (luma, ctx) row of the second-largest TX size at the
// highest q-context.
assert_eq!(
DEFAULT_COEFF_BASE_EOB_CDF[3][3][1][3],
[31767, 32712, 32768, 0]
);
// (q3, tx4, pt0, ctx0): the largest TX size, luma, first ctx.
assert_eq!(
DEFAULT_COEFF_BASE_EOB_CDF[3][4][0][0],
[12358, 24977, 32768, 0]
);
}
/// §8.3.1 `init_coeff_cdfs`: the working `coeff_base_eob` array is
/// set to a copy of `Default_Coeff_Base_Eob_Cdf[ idx ]` for the
/// derived `idx`, and mutating the working copy leaves the §9.4
/// source intact.
#[test]
fn coeff_base_eob_init_coeff_cdfs_selects_q_ctx() {
// A fresh context is seeded from idx 0.
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.coeff_base_eob, DEFAULT_COEFF_BASE_EOB_CDF[0]);
// init_coeff_cdfs re-selects the slice for a high base_q_idx.
ctx.init_coeff_cdfs(200); // -> idx 3
assert_eq!(ctx.coeff_base_eob, DEFAULT_COEFF_BASE_EOB_CDF[3]);
// And for a mid base_q_idx.
ctx.init_coeff_cdfs(50); // -> idx 1
assert_eq!(ctx.coeff_base_eob, DEFAULT_COEFF_BASE_EOB_CDF[1]);
// Mutating the working copy leaves the §9.4 source untouched.
ctx.coeff_base_eob_cdf(0, 0, 0).unwrap()[0] = 12345;
assert_ne!(
ctx.coeff_base_eob[0][0][0],
DEFAULT_COEFF_BASE_EOB_CDF[1][0][0][0]
);
assert_eq!(
DEFAULT_COEFF_BASE_EOB_CDF[1][0][0][0],
[17560, 29888, 32768, 0]
);
}
/// §8.3.2 `coeff_base_eob` selector returns the correct §9.4 row
/// for the in-range `(txSzCtx, ptype, ctx)` triple and rejects each
/// out-of-range axis.
#[test]
fn coeff_base_eob_selector_returns_default_row() {
let mut ctx = TileCdfContext::new_from_defaults(); // idx 0
// In-range: (tx=2, pt=1, ctx=2).
assert_eq!(
ctx.coeff_base_eob_cdf(2, 1, 2).unwrap(),
&DEFAULT_COEFF_BASE_EOB_CDF[0][2][1][2]
);
// In-range: (tx=4, pt=0, ctx=0).
assert_eq!(
ctx.coeff_base_eob_cdf(4, 0, 0).unwrap(),
&DEFAULT_COEFF_BASE_EOB_CDF[0][4][0][0]
);
// Out-of-range on each axis.
assert!(ctx.coeff_base_eob_cdf(TX_SIZES, 0, 0).is_none());
assert!(ctx.coeff_base_eob_cdf(0, PLANE_TYPES, 0).is_none());
assert!(ctx
.coeff_base_eob_cdf(0, 0, SIG_COEF_CONTEXTS_EOB)
.is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through the
/// `coeff_base_eob` default CDF selected by the §8.3.2 selection,
/// confirming the chosen row matches the §9.4 source, the decoded
/// symbol is in range (`0..3`, since the base level is
/// `coeff_base_eob + 1` and the spec restricts it to 1, 2, or 3),
/// and the working copy adapts.
#[test]
fn decode_coeff_base_eob_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.coeff_base_eob;
let row = ctx.coeff_base_eob_cdf(0, 0, 0).unwrap();
assert_eq!(row, &DEFAULT_COEFF_BASE_EOB_CDF[0][0][0][0]);
let sym = dec.read_symbol(row).unwrap();
assert!((sym as usize) < 3, "coeff_base_eob codes 0..3");
assert_ne!(
ctx.coeff_base_eob, before,
"read_symbol must adapt the working CDF"
);
}
// -----------------------------------------------------------------
// Round 139 — `coeff_base` sub-group tests.
// -----------------------------------------------------------------
/// §3 `SIG_COEF_CONTEXTS` pins to its spec value (42).
#[test]
fn coeff_base_constants_pinned() {
assert_eq!(SIG_COEF_CONTEXTS, 42);
}
/// `Default_Coeff_Base_Cdf` shape + per-row well-formedness: every
/// 5-entry row is `[c0, c1, c2, 32768, 0]` with `c0 < c1 < c2 < 32768`
/// (and a fresh §8.3 adaptation counter at the tail).
#[test]
fn coeff_base_default_table_well_formed() {
fn check_row(row: &[u16]) {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768: {row:?}");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0: {row:?}");
for w in row[..n].windows(2) {
assert!(w[0] < w[1], "cdf must strictly increase: {row:?}");
}
}
assert_eq!(DEFAULT_COEFF_BASE_CDF.len(), COEFF_CDF_Q_CTXS);
for q in &DEFAULT_COEFF_BASE_CDF {
assert_eq!(q.len(), TX_SIZES);
for tx in q {
assert_eq!(tx.len(), PLANE_TYPES);
for pt in tx {
assert_eq!(pt.len(), SIG_COEF_CONTEXTS);
for row in pt {
assert_eq!(row.len(), 5);
check_row(row);
}
}
}
}
}
/// Spot-check the §9.4 `Default_Coeff_Base_Cdf` initial values
/// byte-for-byte. A mis-keyed digit during transcription breaks
/// the equality. Anchors include the (q0, tx0, pt0) first row, a
/// chroma row at the same q-ctx, an interior anchor, and the
/// flat-distribution placeholder that pads the largest TX size's
/// chroma slice across every q-context.
#[test]
fn coeff_base_default_byte_exact_values() {
// (q0, tx0, pt0, ctx0): first row of the table.
assert_eq!(
DEFAULT_COEFF_BASE_CDF[0][0][0][0],
[4034, 8930, 12727, 32768, 0]
);
// (q0, tx0, pt1, ctx0): chroma plane, same TX size.
assert_eq!(
DEFAULT_COEFF_BASE_CDF[0][0][1][0],
[6302, 16444, 21761, 32768, 0]
);
// (q1, tx0, pt0, ctx0): next q-context, luma.
assert_eq!(
DEFAULT_COEFF_BASE_CDF[1][0][0][0],
[6041, 11854, 15927, 32768, 0]
);
// (q3, tx4, pt0, ctx0): largest TX size, luma, highest q-ctx.
assert_eq!(
DEFAULT_COEFF_BASE_CDF[3][4][0][0],
[4137, 10847, 15682, 32768, 0]
);
// Every q-context's (tx4, pt1) slice is the same flat
// {8192, 16384, 24576, 32768, 0} placeholder for all 42 ctx
// values (§9.4 sentinel for the unreachable largest-TX
// chroma row, exactly mirroring the r138 (tx4, pt1)
// placeholder pattern in `Default_Coeff_Base_Eob_Cdf`).
let flat = [8192u16, 16384, 24576, 32768, 0];
for (q, q_slice) in DEFAULT_COEFF_BASE_CDF.iter().enumerate() {
for (ctx, row) in q_slice[4][1].iter().enumerate() {
assert_eq!(
*row, flat,
"(q={q}, tx=4, pt=1, ctx={ctx}) must be the flat placeholder"
);
}
}
}
/// §8.3.1 `init_coeff_cdfs`: the working `coeff_base` array is
/// set to a copy of `Default_Coeff_Base_Cdf[ idx ]` for the
/// derived `idx`, and mutating the working copy leaves the §9.4
/// source intact.
#[test]
fn coeff_base_init_coeff_cdfs_selects_q_ctx() {
// A fresh context is seeded from idx 0.
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.coeff_base, DEFAULT_COEFF_BASE_CDF[0]);
// init_coeff_cdfs re-selects the slice for a high base_q_idx.
ctx.init_coeff_cdfs(200); // -> idx 3
assert_eq!(ctx.coeff_base, DEFAULT_COEFF_BASE_CDF[3]);
// And for a mid base_q_idx.
ctx.init_coeff_cdfs(50); // -> idx 1
assert_eq!(ctx.coeff_base, DEFAULT_COEFF_BASE_CDF[1]);
// Mutating the working copy leaves the §9.4 source untouched.
ctx.coeff_base_cdf(0, 0, 0).unwrap()[0] = 12345;
assert_ne!(ctx.coeff_base[0][0][0], DEFAULT_COEFF_BASE_CDF[1][0][0][0]);
assert_eq!(
DEFAULT_COEFF_BASE_CDF[1][0][0][0],
[6041, 11854, 15927, 32768, 0]
);
}
/// §8.3.2 `coeff_base` selector returns the correct §9.4 row
/// for the in-range `(txSzCtx, ptype, ctx)` triple and rejects
/// each out-of-range axis. Probes the boundary contexts at
/// `SIG_COEF_CONTEXTS - 1` and the §3 `SIG_COEF_CONTEXTS_2D`
/// split point.
#[test]
fn coeff_base_selector_returns_default_row() {
let mut ctx = TileCdfContext::new_from_defaults(); // idx 0
// In-range: (tx=2, pt=1, ctx=10).
assert_eq!(
ctx.coeff_base_cdf(2, 1, 10).unwrap(),
&DEFAULT_COEFF_BASE_CDF[0][2][1][10]
);
// In-range at the SIG_COEF_CONTEXTS_2D split point: (tx=1, pt=0, ctx=26).
assert_eq!(
ctx.coeff_base_cdf(1, 0, 26).unwrap(),
&DEFAULT_COEFF_BASE_CDF[0][1][0][26]
);
// In-range at the highest context: (tx=0, pt=0, ctx=SIG_COEF_CONTEXTS-1).
assert_eq!(
ctx.coeff_base_cdf(0, 0, SIG_COEF_CONTEXTS - 1).unwrap(),
&DEFAULT_COEFF_BASE_CDF[0][0][0][SIG_COEF_CONTEXTS - 1]
);
// Out-of-range on each axis.
assert!(ctx.coeff_base_cdf(TX_SIZES, 0, 0).is_none());
assert!(ctx.coeff_base_cdf(0, PLANE_TYPES, 0).is_none());
assert!(ctx.coeff_base_cdf(0, 0, SIG_COEF_CONTEXTS).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through the
/// `coeff_base` default CDF selected by the §8.3.2 selection,
/// confirming the chosen row matches the §9.4 source, the
/// decoded symbol is in range (`0..4`, since `coeff_base` codes
/// the four-symbol alphabet `0..3`), and the working copy adapts.
#[test]
fn decode_coeff_base_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.coeff_base;
let row = ctx.coeff_base_cdf(0, 0, 0).unwrap();
assert_eq!(row, &DEFAULT_COEFF_BASE_CDF[0][0][0][0]);
let sym = dec.read_symbol(row).unwrap();
assert!((sym as usize) < 4, "coeff_base codes 0..4");
assert_ne!(
ctx.coeff_base, before,
"read_symbol must adapt the working CDF"
);
}
// -----------------------------------------------------------------
// Round 140 — `coeff_br` sub-group tests.
// -----------------------------------------------------------------
/// §3 `LEVEL_CONTEXTS` / `BR_CDF_SIZE` pin to their spec values
/// (21 and 4 respectively).
#[test]
fn coeff_br_constants_pinned() {
assert_eq!(LEVEL_CONTEXTS, 21);
assert_eq!(BR_CDF_SIZE, 4);
}
/// `Default_Coeff_Br_Cdf` shape + per-row well-formedness: every
/// 5-entry row is `[c0, c1, c2, 32768, 0]` with `c0 < c1 < c2 < 32768`
/// (and a fresh §8.3 adaptation counter at the tail).
#[test]
fn coeff_br_default_table_well_formed() {
fn check_row(row: &[u16]) {
let n = row.len() - 1;
assert_eq!(row[n - 1], 1 << 15, "cdf[N-1] must be 32768: {row:?}");
assert_eq!(row[n], 0, "fresh adaptation counter must be 0: {row:?}");
for w in row[..n].windows(2) {
assert!(w[0] < w[1], "cdf must strictly increase: {row:?}");
}
}
assert_eq!(DEFAULT_COEFF_BR_CDF.len(), COEFF_CDF_Q_CTXS);
for q in &DEFAULT_COEFF_BR_CDF {
assert_eq!(q.len(), TX_SIZES);
for tx in q {
assert_eq!(tx.len(), PLANE_TYPES);
for pt in tx {
assert_eq!(pt.len(), LEVEL_CONTEXTS);
for row in pt {
assert_eq!(row.len(), BR_CDF_SIZE + 1);
check_row(row);
}
}
}
}
}
/// Spot-check the §9.4 `Default_Coeff_Br_Cdf` initial values
/// byte-for-byte. Anchors include the (q0, tx0, pt0) first row,
/// the chroma plane at the same q-ctx, an interior anchor, and
/// the flat-distribution placeholder that pads the largest TX
/// size across every q-context.
#[test]
fn coeff_br_default_byte_exact_values() {
// (q0, tx0, pt0, ctx0): first row of the table.
assert_eq!(
DEFAULT_COEFF_BR_CDF[0][0][0][0],
[14298, 20718, 24174, 32768, 0]
);
// (q0, tx0, pt1, ctx0): chroma plane, same TX size.
assert_eq!(
DEFAULT_COEFF_BR_CDF[0][0][1][0],
[15967, 22905, 26286, 32768, 0]
);
// (q0, tx0, pt0, ctx20 = last LEVEL_CONTEXTS row of (q0, tx0, pt0)).
assert_eq!(
DEFAULT_COEFF_BR_CDF[0][0][0][LEVEL_CONTEXTS - 1],
[3034, 5860, 8462, 32768, 0]
);
// Largest TX size, chroma slice across every q-context, should be
// the flat {8192, 16384, 24576, 32768, 0} sentinel for the
// unreachable rows (§9.4 placeholder pattern, identical to
// r138 / r139).
let flat = [8192u16, 16384, 24576, 32768, 0];
for (q, q_slice) in DEFAULT_COEFF_BR_CDF.iter().enumerate() {
for (ctx, row) in q_slice[4][1].iter().enumerate() {
assert_eq!(
*row, flat,
"(q={q}, tx=4, pt=1, ctx={ctx}) must be the flat placeholder"
);
}
}
}
/// §8.3.1 `init_coeff_cdfs`: the working `coeff_br` array is set to
/// a copy of `Default_Coeff_Br_Cdf[ idx ]` for the derived `idx`,
/// and mutating the working copy leaves the §9.4 source intact.
#[test]
fn coeff_br_init_coeff_cdfs_selects_q_ctx() {
// A fresh context is seeded from idx 0.
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.coeff_br, DEFAULT_COEFF_BR_CDF[0]);
// init_coeff_cdfs re-selects the slice for a high base_q_idx.
ctx.init_coeff_cdfs(200); // -> idx 3
assert_eq!(ctx.coeff_br, DEFAULT_COEFF_BR_CDF[3]);
// And for a mid base_q_idx.
ctx.init_coeff_cdfs(50); // -> idx 1
assert_eq!(ctx.coeff_br, DEFAULT_COEFF_BR_CDF[1]);
// Mutating the working copy leaves the §9.4 source untouched.
ctx.coeff_br_cdf(0, 0, 0).unwrap()[0] = 12345;
assert_ne!(ctx.coeff_br[0][0][0], DEFAULT_COEFF_BR_CDF[1][0][0][0]);
}
/// §8.3.2 `coeff_br` selector returns the correct §9.4 row for the
/// in-range `(txSzCtx, ptype, ctx)` triple, clamps `txSzCtx` to
/// `TX_32X32 = 3` per the spec selector
/// `TileCoeffBrCdf[ Min(txSzCtx, TX_32X32) ][ ptype ][ ctx ]`,
/// and rejects each out-of-range axis other than `txSzCtx`.
#[test]
fn coeff_br_selector_returns_default_row_and_clamps_tx() {
let mut ctx = TileCdfContext::new_from_defaults(); // idx 0
const TX_32X32: usize = 3;
// In-range: (tx=2, pt=1, ctx=10).
assert_eq!(
ctx.coeff_br_cdf(2, 1, 10).unwrap(),
&DEFAULT_COEFF_BR_CDF[0][2][1][10]
);
// At the largest unclamped index: (tx=TX_32X32, pt=0, ctx=20).
assert_eq!(
ctx.coeff_br_cdf(TX_32X32, 0, LEVEL_CONTEXTS - 1).unwrap(),
&DEFAULT_COEFF_BR_CDF[0][TX_32X32][0][LEVEL_CONTEXTS - 1]
);
// Clamped: (tx=TX_SIZES-1, pt=0, ctx=0) reads the TX_32X32 slot.
assert_eq!(
ctx.coeff_br_cdf(TX_SIZES - 1, 0, 0).unwrap(),
&DEFAULT_COEFF_BR_CDF[0][TX_32X32][0][0]
);
// An out-of-spec txSzCtx (above TX_SIZES) is still accepted; the
// clamp pins the lookup at TX_32X32.
assert_eq!(
ctx.coeff_br_cdf(99, 1, 5).unwrap(),
&DEFAULT_COEFF_BR_CDF[0][TX_32X32][1][5]
);
// Out-of-range on the ptype / ctx axes is rejected.
assert!(ctx.coeff_br_cdf(0, PLANE_TYPES, 0).is_none());
assert!(ctx.coeff_br_cdf(0, 0, LEVEL_CONTEXTS).is_none());
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through the
/// `coeff_br` default CDF selected by the §8.3.2 selection,
/// confirming the chosen row matches the §9.4 source, the
/// decoded symbol is in range (`0..BR_CDF_SIZE`), and the working
/// copy adapts.
#[test]
fn decode_coeff_br_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut ctx = TileCdfContext::new_from_defaults();
let before = ctx.coeff_br;
let row = ctx.coeff_br_cdf(0, 0, 0).unwrap();
assert_eq!(row, &DEFAULT_COEFF_BR_CDF[0][0][0][0]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < BR_CDF_SIZE,
"coeff_br codes 0..BR_CDF_SIZE"
);
assert_ne!(
ctx.coeff_br, before,
"read_symbol must adapt the working CDF"
);
}
// -----------------------------------------------------------------
// §8.3.2 get_coeff_base_ctx / get_br_ctx neighbour derivation.
// Expected ctx values are hand-computed from the §8.3.2 pseudocode.
// -----------------------------------------------------------------
/// §3 constants the §8.3.2 helpers depend on pin to their spec values.
#[test]
fn coeff_ctx_constants_pin() {
assert_eq!(SIG_COEF_CONTEXTS_2D, 26);
assert_eq!(SIG_REF_DIFF_OFFSET_NUM, 5);
assert_eq!(NUM_BASE_LEVELS, 2);
assert_eq!(COEFF_BASE_RANGE, 12);
assert_eq!(TX_SIZES_ALL, 19);
assert_eq!(TX_CLASS_2D, 0);
assert_eq!(TX_CLASS_HORIZ, 1);
assert_eq!(TX_CLASS_VERT, 2);
// Each TX_SIZES_ALL-indexed table has the right shape.
assert_eq!(ADJUSTED_TX_SIZE.len(), TX_SIZES_ALL);
assert_eq!(TX_WIDTH.len(), TX_SIZES_ALL);
assert_eq!(TX_HEIGHT.len(), TX_SIZES_ALL);
assert_eq!(TX_WIDTH_LOG2.len(), TX_SIZES_ALL);
assert_eq!(COEFF_BASE_CTX_OFFSET.len(), TX_SIZES_ALL);
// Coeff_Base_Pos_Ctx_Offset[ idx ] = SIG_COEF_CONTEXTS_2D + 5*idx.
assert_eq!(COEFF_BASE_POS_CTX_OFFSET, [26, 31, 36]);
// Width / log2 self-consistency: Tx_Width == 1 << Tx_Width_Log2.
for t in 0..TX_SIZES_ALL {
assert_eq!(TX_WIDTH[t], 1 << TX_WIDTH_LOG2[t]);
}
}
/// §8.3.2 `get_tx_class()` reduces the directional flags to the three
/// transform classes.
#[test]
fn tx_class_reduction() {
// No directional flag -> 2D.
assert_eq!(
get_tx_class(false, false, false, false, false, false),
TX_CLASS_2D
);
// Any vertical flag -> VERT (vertical takes precedence in spec).
assert_eq!(
get_tx_class(true, false, false, false, false, false),
TX_CLASS_VERT
);
assert_eq!(
get_tx_class(false, true, false, false, false, false),
TX_CLASS_VERT
);
assert_eq!(
get_tx_class(false, false, true, false, false, false),
TX_CLASS_VERT
);
// Horizontal flags (no vertical) -> HORIZ.
assert_eq!(
get_tx_class(false, false, false, true, false, false),
TX_CLASS_HORIZ
);
assert_eq!(
get_tx_class(false, false, false, false, true, false),
TX_CLASS_HORIZ
);
assert_eq!(
get_tx_class(false, false, false, false, false, true),
TX_CLASS_HORIZ
);
}
/// §8.3.2 `get_coeff_base_ctx(..., isEob=1)` buckets for TX_4X4
/// (bwl=2, height=4 -> (height<<bwl)=16, /8=2, /4=4).
#[test]
fn coeff_base_eob_buckets() {
let quant = [0i32; 16];
// c == 0 -> SIG_COEF_CONTEXTS - 4.
assert_eq!(
get_coeff_base_ctx(&quant, 0, TX_CLASS_2D, 0, 0, true),
SIG_COEF_CONTEXTS - 4
);
// c <= 2 -> SIG_COEF_CONTEXTS - 3.
assert_eq!(
get_coeff_base_ctx(&quant, 0, TX_CLASS_2D, 0, 1, true),
SIG_COEF_CONTEXTS - 3
);
assert_eq!(
get_coeff_base_ctx(&quant, 0, TX_CLASS_2D, 0, 2, true),
SIG_COEF_CONTEXTS - 3
);
// 2 < c <= 4 -> SIG_COEF_CONTEXTS - 2.
assert_eq!(
get_coeff_base_ctx(&quant, 0, TX_CLASS_2D, 0, 3, true),
SIG_COEF_CONTEXTS - 2
);
assert_eq!(
get_coeff_base_ctx(&quant, 0, TX_CLASS_2D, 0, 4, true),
SIG_COEF_CONTEXTS - 2
);
// c > 4 -> SIG_COEF_CONTEXTS - 1.
assert_eq!(
get_coeff_base_ctx(&quant, 0, TX_CLASS_2D, 0, 5, true),
SIG_COEF_CONTEXTS - 1
);
// The coeff_base_eob ctx wrapper reduces onto 0..SIG_COEF_CONTEXTS_EOB.
assert_eq!(get_coeff_base_eob_ctx(&quant, 0, TX_CLASS_2D, 0, 0), 0);
assert_eq!(get_coeff_base_eob_ctx(&quant, 0, TX_CLASS_2D, 0, 1), 1);
assert_eq!(get_coeff_base_eob_ctx(&quant, 0, TX_CLASS_2D, 0, 3), 2);
assert_eq!(get_coeff_base_eob_ctx(&quant, 0, TX_CLASS_2D, 0, 5), 3);
// The reduced ctx must address a coeff_base_eob CDF row.
let mut tile = TileCdfContext::new_from_defaults();
let eob_ctx = get_coeff_base_eob_ctx(&quant, 0, TX_CLASS_2D, 0, 5);
assert!(tile.coeff_base_eob_cdf(0, 0, eob_ctx).is_some());
}
/// §8.3.2 2D branch early return: `row == 0 && col == 0` -> ctx 0.
#[test]
fn coeff_base_2d_dc_returns_zero() {
let quant = [9i32; 64]; // neighbours irrelevant for the DC return.
assert_eq!(get_coeff_base_ctx(&quant, 1, TX_CLASS_2D, 0, 0, false), 0);
}
/// §8.3.2 2D branch with a non-trivial Coeff_Base_Ctx_Offset entry.
/// TX_8X8 (bwl=3), pos=9 -> row=1, col=1, offset[1][1][1]=6. A single
/// neighbour magnitude 2 gives mag=2, ctx=Min((2+1)>>1,4)=1 -> 1+6=7.
#[test]
fn coeff_base_2d_with_neighbour_mag() {
let mut quant = [0i32; 64];
// Sig_Ref_Diff_Offset[2D][0] = (0,1): refRow=1, refCol=2 ->
// Quant[(1<<3)+2] = Quant[10].
quant[10] = 2;
let ctx = get_coeff_base_ctx(&quant, 1, TX_CLASS_2D, 9, 0, false);
assert_eq!(ctx, 7);
assert!(ctx < SIG_COEF_CONTEXTS);
// Saturated 2D neighbourhood: all five offsets carry magnitude 3
// -> mag=15, ctx=Min((15+1)>>1,4)=Min(8,4)=4. 4 + offset[1][1][1]=6
// -> 10.
let mut sat = [0i32; 64];
for &(r, c) in &[(1usize, 2usize), (2, 1), (2, 2), (1, 3), (3, 1)] {
sat[(r << 3) + c] = 3;
}
let ctx_sat = get_coeff_base_ctx(&sat, 1, TX_CLASS_2D, 9, 0, false);
assert_eq!(ctx_sat, 10);
assert!(ctx_sat < SIG_COEF_CONTEXTS);
}
/// §8.3.2 vertical branch: idx=row, Coeff_Base_Pos_Ctx_Offset[Min(row,2)].
/// TX_8X8 (bwl=3), pos=24 -> row=3, col=0; Min(3,2)=2 -> offset 36.
/// Two saturated neighbours give mag=6, ctx=Min((6+1)>>1,4)=3 -> 39.
#[test]
fn coeff_base_vert_branch() {
let mut quant = [0i32; 64];
// Sig_Ref_Diff_Offset[VERT][0]=(0,1): refRow=3,refCol=1 -> Quant[25].
// Sig_Ref_Diff_Offset[VERT][1]=(1,0): refRow=4,refCol=0 -> Quant[32].
quant[25] = 3;
quant[32] = 3;
let ctx = get_coeff_base_ctx(&quant, 1, TX_CLASS_VERT, 24, 0, false);
assert_eq!(ctx, 39);
assert!(ctx < SIG_COEF_CONTEXTS);
assert!(ctx >= SIG_COEF_CONTEXTS_2D, "1D tail uses the >=26 range");
}
/// §8.3.2 horizontal branch with Min(col,2) clamping.
/// TX_8X8 (bwl=3). pos=3 -> row=0,col=3; Min(3,2)=2 -> offset 36.
/// Quant[4]=1 -> mag=1, ctx=Min((1+1)>>1,4)=1 -> 37.
#[test]
fn coeff_base_horiz_branch_clamped() {
let mut quant = [0i32; 64];
// Sig_Ref_Diff_Offset[HORIZ][0]=(0,1): refRow=0,refCol=4 -> Quant[4].
quant[4] = 1;
let ctx = get_coeff_base_ctx(&quant, 1, TX_CLASS_HORIZ, 3, 0, false);
assert_eq!(ctx, 37);
assert!(ctx < SIG_COEF_CONTEXTS);
// col=1 -> idx not clamped: Coeff_Base_Pos_Ctx_Offset[1] = 31.
// pos=1 -> row=0,col=1. Empty neighbourhood -> mag=0, ctx=0 -> 31.
let empty = [0i32; 64];
let ctx2 = get_coeff_base_ctx(&empty, 1, TX_CLASS_HORIZ, 1, 0, false);
assert_eq!(ctx2, 31);
}
/// §8.3.2 `get_br_ctx`: pos==0 path returns the magnitude bucket alone.
/// TX_4X4 (bwl=2,txw=4,txh=4). Quant[1]=5 -> mag=Min(5,15)=5,
/// mag=Min((5+1)>>1,6)=3. pos==0 -> ctx=3.
#[test]
fn br_ctx_pos_zero() {
let mut quant = [0i32; 16];
// Mag_Ref_Offset[2D][0]=(0,1): refRow=0,refCol=1 -> Quant[0*4+1]=Quant[1].
quant[1] = 5;
let ctx = get_br_ctx(&quant, 0, TX_CLASS_2D, 0);
assert_eq!(ctx, 3);
assert!(ctx < LEVEL_CONTEXTS);
}
/// §8.3.2 `get_br_ctx` magnitude clamp: each neighbour saturates at
/// COEFF_BASE_RANGE+NUM_BASE_LEVELS+1 = 15; three at 100 -> mag=45,
/// Min((45+1)>>1,6)=6. TX_4X4 pos==0 -> ctx=6 (the max bucket).
#[test]
fn br_ctx_saturated_clamp() {
let mut quant = [0i32; 16];
// 2D offsets from (0,0): (0,1)->Q[1], (1,0)->Q[4], (1,1)->Q[5].
quant[1] = 100;
quant[4] = 100;
quant[5] = 100;
let ctx = get_br_ctx(&quant, 0, TX_CLASS_2D, 0);
assert_eq!(ctx, 6);
assert!(ctx < LEVEL_CONTEXTS);
}
/// §8.3.2 `get_br_ctx` 2D branch: inner (row<2 && col<2) -> +7,
/// outer -> +14. TX_8X8 (bwl=3).
#[test]
fn br_ctx_2d_inner_outer() {
// Inner: pos=9 -> row=1,col=1. Quant[10]=1 (offset (0,1) -> col 2)
// -> mag=1, Min((1+1)>>1,6)=1, +7 -> 8.
let mut inner = [0i32; 64];
inner[10] = 1; // refRow=1,refCol=2 -> 1*8+2 = 10.
let c_inner = get_br_ctx(&inner, 1, TX_CLASS_2D, 9);
assert_eq!(c_inner, 8);
assert!(c_inner < LEVEL_CONTEXTS);
// Outer: pos=18 -> row=2,col=2. Empty neighbourhood -> mag=0,
// Min((0+1)>>1,6)=0, +14 -> 14.
let empty = [0i32; 64];
let c_outer = get_br_ctx(&empty, 1, TX_CLASS_2D, 18);
assert_eq!(c_outer, 14);
assert!(c_outer < LEVEL_CONTEXTS);
}
/// §8.3.2 `get_br_ctx` horizontal branch (txClass==1): col==0 -> +7,
/// else +14. TX_8X8 (bwl=3), empty neighbourhood -> mag bucket 0.
#[test]
fn br_ctx_horiz_branch() {
let empty = [0i32; 64];
// col==0: pos=8 -> row=1,col=0 -> mag bucket 0, +7 -> 7.
assert_eq!(get_br_ctx(&empty, 1, TX_CLASS_HORIZ, 8), 7);
// col!=0: pos=1 -> row=0,col=1 -> +14 -> 14.
assert_eq!(get_br_ctx(&empty, 1, TX_CLASS_HORIZ, 1), 14);
}
/// §8.3.2 `get_br_ctx` vertical branch (txClass==2 / else): row==0 ->
/// +7, else +14. TX_8X8 (bwl=3), empty neighbourhood -> mag bucket 0.
#[test]
fn br_ctx_vert_branch() {
let empty = [0i32; 64];
// row==0: pos=1 -> row=0,col=1 -> +7 -> 7.
assert_eq!(get_br_ctx(&empty, 1, TX_CLASS_VERT, 1), 7);
// row!=0: pos=8 -> row=1,col=0 -> +14 -> 14.
assert_eq!(get_br_ctx(&empty, 1, TX_CLASS_VERT, 8), 14);
// The derived ctx must address a coeff_br CDF row.
let mut tile = TileCdfContext::new_from_defaults();
let ctx = get_br_ctx(&empty, 1, TX_CLASS_VERT, 1);
assert!(tile.coeff_br_cdf(1, 0, ctx).is_some());
}
// -----------------------------------------------------------------
// Round 142 — §5.11.40 `compute_tx_type()` derivation.
// -----------------------------------------------------------------
/// `Tx_Size_Sqr_Up[ TX_SIZES_ALL ]` (§ Additional tables): each row
/// entry is a valid `TX_4X4..TX_64X64` ordinal, and the equality
/// `Tx_Width[ Tx_Size_Sqr_Up[ t ] ] == Tx_Height[ Tx_Size_Sqr_Up[ t ] ]`
/// holds because the destination is always a square size. Spot-checks
/// the rectangular entries lift to `Max(w, h)`-sided squares.
#[test]
fn tx_size_sqr_up_table_is_well_formed() {
assert_eq!(TX_SIZE_SQR_UP.len(), TX_SIZES_ALL);
for &t in &TX_SIZE_SQR_UP {
assert!(
t <= TX_64X64,
"Tx_Size_Sqr_Up entry must be a square tx-size ordinal"
);
assert_eq!(
TX_WIDTH[t], TX_HEIGHT[t],
"Tx_Size_Sqr_Up destination must be square"
);
}
// §6.10.16 ordinals: 5 = TX_4X8, 7 = TX_8X16, 13 = TX_4X16 —
// each lifts to the Max(w,h)-sided square per the spec.
assert_eq!(TX_SIZE_SQR_UP[5], TX_8X8); // 4x8 -> 8x8
assert_eq!(TX_SIZE_SQR_UP[7], TX_16X16); // 8x16 -> 16x16
assert_eq!(TX_SIZE_SQR_UP[13], TX_16X16); // 4x16 -> 16x16
assert_eq!(TX_SIZE_SQR_UP[11], TX_64X64); // 32x64 -> 64x64
}
/// §6.10.16 / §3 transform-size and transform-type ordinals match
/// the spec-listed values; the ordinal constants are the same
/// values previously used as locally-scoped `const TX_*` shadows.
#[test]
fn tx_size_and_tx_type_ordinals_match_spec() {
// §6.10.16 TxSize semantics table rows 0..4.
assert_eq!(TX_4X4, 0);
assert_eq!(TX_8X8, 1);
assert_eq!(TX_16X16, 2);
assert_eq!(TX_32X32, 3);
assert_eq!(TX_64X64, 4);
// §6.10.19 TxType enumeration rows 0..15.
assert_eq!(DCT_DCT, 0);
assert_eq!(ADST_DCT, 1);
assert_eq!(DCT_ADST, 2);
assert_eq!(ADST_ADST, 3);
assert_eq!(FLIPADST_DCT, 4);
assert_eq!(DCT_FLIPADST, 5);
assert_eq!(FLIPADST_FLIPADST, 6);
assert_eq!(ADST_FLIPADST, 7);
assert_eq!(FLIPADST_ADST, 8);
assert_eq!(IDTX, 9);
assert_eq!(V_DCT, 10);
assert_eq!(H_DCT, 11);
assert_eq!(V_ADST, 12);
assert_eq!(H_ADST, 13);
assert_eq!(V_FLIPADST, 14);
assert_eq!(H_FLIPADST, 15);
// §3 TX_SET_TYPES_* counts match the table-row counts.
assert_eq!(TX_SET_TYPES_INTRA, TX_TYPE_IN_SET_INTRA.len());
assert_eq!(TX_SET_TYPES_INTER, TX_TYPE_IN_SET_INTER.len());
}
/// `Mode_To_Txfm[ UV_INTRA_MODES_CFL_ALLOWED ]` (§ Additional tables):
/// per-row spot-check + length pin. Each entry lies in `0..TX_TYPES`
/// (so [`is_tx_type_in_set`] never returns the out-of-range `false`).
#[test]
fn mode_to_txfm_table_matches_spec() {
assert_eq!(MODE_TO_TXFM.len(), UV_INTRA_MODES_CFL_ALLOWED);
for &t in &MODE_TO_TXFM {
assert!(t < TX_TYPES);
}
// Spec § Additional tables (Mode_To_Txfm), per-row:
assert_eq!(MODE_TO_TXFM[0], DCT_DCT); // DC_PRED
assert_eq!(MODE_TO_TXFM[1], ADST_DCT); // V_PRED
assert_eq!(MODE_TO_TXFM[2], DCT_ADST); // H_PRED
assert_eq!(MODE_TO_TXFM[4], ADST_ADST); // D135_PRED
assert_eq!(MODE_TO_TXFM[9], ADST_ADST); // SMOOTH_PRED
assert_eq!(MODE_TO_TXFM[12], ADST_ADST); // PAETH_PRED
assert_eq!(MODE_TO_TXFM[13], DCT_DCT); // UV_CFL_PRED
}
/// §5.11.40 `is_tx_type_in_set` over the four inter `txSet` rows.
/// `TX_SET_DCTONLY` admits only `DCT_DCT`; `TX_SET_INTER_1` admits
/// all 16; `TX_SET_INTER_2` admits 0..=11 only; `TX_SET_INTER_3`
/// admits `{ IDTX, DCT_DCT }`.
#[test]
fn is_tx_type_in_set_inter_per_row() {
// TX_SET_DCTONLY (0): DCT_DCT only.
assert!(is_tx_type_in_set(true, TX_SET_DCTONLY, DCT_DCT));
assert!(!is_tx_type_in_set(true, TX_SET_DCTONLY, ADST_DCT));
assert!(!is_tx_type_in_set(true, TX_SET_DCTONLY, IDTX));
// TX_SET_INTER_1 (1): every type admitted.
for t in 0..TX_TYPES {
assert!(
is_tx_type_in_set(true, TX_SET_INTER_1, t),
"INTER_1 admits {t}"
);
}
// TX_SET_INTER_2 (2): 0..=11 yes, 12..=15 no.
for t in 0..12 {
assert!(
is_tx_type_in_set(true, TX_SET_INTER_2, t),
"INTER_2 admits {t}"
);
}
for t in 12..16 {
assert!(
!is_tx_type_in_set(true, TX_SET_INTER_2, t),
"INTER_2 rejects {t}"
);
}
// TX_SET_INTER_3 (3): IDTX + DCT_DCT only.
assert!(is_tx_type_in_set(true, TX_SET_INTER_3, DCT_DCT));
assert!(is_tx_type_in_set(true, TX_SET_INTER_3, IDTX));
assert!(!is_tx_type_in_set(true, TX_SET_INTER_3, ADST_DCT));
assert!(!is_tx_type_in_set(true, TX_SET_INTER_3, H_FLIPADST));
}
/// §5.11.40 `is_tx_type_in_set` over the three intra `txSet` rows.
/// `TX_SET_INTRA_1` admits 4 cross-DCT/ADST types + IDTX + V_DCT +
/// H_DCT (i.e. 7 entries); `TX_SET_INTRA_2` drops V_DCT + H_DCT (5
/// entries). Out-of-range tx_set / tx_type returns `false`.
#[test]
fn is_tx_type_in_set_intra_per_row() {
// TX_SET_DCTONLY: DCT_DCT only.
assert!(is_tx_type_in_set(false, TX_SET_DCTONLY, DCT_DCT));
assert!(!is_tx_type_in_set(false, TX_SET_DCTONLY, IDTX));
// TX_SET_INTRA_1: 4 cross-set + { IDTX, V_DCT, H_DCT }.
for t in [DCT_DCT, ADST_DCT, DCT_ADST, ADST_ADST, IDTX, V_DCT, H_DCT] {
assert!(
is_tx_type_in_set(false, TX_SET_INTRA_1, t),
"INTRA_1 admits {t}"
);
}
for t in [FLIPADST_DCT, DCT_FLIPADST, V_ADST, H_FLIPADST] {
assert!(
!is_tx_type_in_set(false, TX_SET_INTRA_1, t),
"INTRA_1 rejects {t}"
);
}
// TX_SET_INTRA_2: drops V_DCT / H_DCT vs INTRA_1.
assert!(is_tx_type_in_set(false, TX_SET_INTRA_2, IDTX));
assert!(!is_tx_type_in_set(false, TX_SET_INTRA_2, V_DCT));
assert!(!is_tx_type_in_set(false, TX_SET_INTRA_2, H_DCT));
// Out-of-range inputs return false rather than panicking.
assert!(!is_tx_type_in_set(false, 99, DCT_DCT));
assert!(!is_tx_type_in_set(true, 99, DCT_DCT));
assert!(!is_tx_type_in_set(false, TX_SET_INTRA_1, TX_TYPES));
}
/// §5.11.40 `compute_tx_type` short-circuits: `Lossless` returns
/// `DCT_DCT` ignoring everything; `txSzSqrUp > TX_32X32` (e.g.
/// `TX_64X64`, ordinal 4) returns `DCT_DCT` regardless of plane /
/// is_inter / tx_set.
#[test]
fn compute_tx_type_lossless_and_large_fallback() {
let tx_types = |_: u32, _: u32| ADST_ADST; // would-be luma result
// Lossless: must collapse to DCT_DCT even for plane 0.
assert_eq!(
compute_tx_type(
0,
TX_8X8,
true,
false,
TX_SET_INTRA_1,
0,
0,
0,
0,
0,
0,
0,
tx_types
),
DCT_DCT
);
// TX_64X64 -> txSzSqrUp == TX_64X64 > TX_32X32 -> DCT_DCT.
assert_eq!(
compute_tx_type(
0,
TX_64X64,
false,
true,
TX_SET_INTER_1,
0,
0,
0,
0,
0,
0,
0,
tx_types
),
DCT_DCT
);
// TX_32X64 (ordinal 11) also lifts to TX_64X64.
assert_eq!(
compute_tx_type(
0,
11,
false,
true,
TX_SET_INTER_1,
0,
0,
0,
0,
0,
0,
0,
tx_types
),
DCT_DCT
);
// Out-of-range tx_sz: treat as fallback (no panic).
assert_eq!(
compute_tx_type(
0,
99,
false,
true,
TX_SET_INTER_1,
0,
0,
0,
0,
0,
0,
0,
tx_types
),
DCT_DCT
);
}
/// §5.11.40 `compute_tx_type` luma branch (`plane == 0`): returns
/// the `TxTypes[blockY][blockX]` cache entry verbatim (no
/// admission filter, no `Max(MiRow, ...)` lift). The chroma
/// `is_tx_type_in_set` admission filter does not apply.
#[test]
fn compute_tx_type_plane_zero_returns_cache_entry() {
// The closure encodes a 2D cache; the function must read
// exactly (block_y, block_x) without applying subsampling /
// MI-grid lift.
let cache = |y: u32, x: u32| ((y * 4 + x) as usize) % TX_TYPES;
let result = compute_tx_type(
0,
TX_8X8,
false,
true,
TX_SET_INTER_1,
10,
20, // MiRow / MiCol (ignored on luma)
3,
2,
1,
1, // subsampling (ignored on luma)
5, // uv_mode (ignored on luma)
cache,
);
assert_eq!(result, (2 * 4 + 3) % TX_TYPES);
}
/// §5.11.40 `compute_tx_type` inter chroma branch: reads
/// `TxTypes[Max(MiRow, blockY<<ssY)][Max(MiCol, blockX<<ssX)]`
/// then admits per `is_tx_type_in_set(true, txSet, txType)` else
/// returns `DCT_DCT`. Covers (a) the `Max` lift firing when
/// `MiCol > blockX<<ssX`, (b) the admission filter passing, and
/// (c) the admission filter falling back.
#[test]
fn compute_tx_type_inter_chroma_max_lift_and_admission() {
// (a) Max lift: subsampling_x = 1, block_x = 1, MiCol = 4. The
// spec computes `Max(MiCol, 1 << 1) = Max(4, 2) = 4`, so the
// cache lookup must be at column 4 (not 2). Encode the cache
// so column-4 reads return ADST_DCT but column-2 reads return
// a clearly-disadmitted ADST_FLIPADST.
let cache = |y: u32, x: u32| {
if y == 4 && x == 4 {
ADST_DCT // admitted under TX_SET_INTER_1 / INTER_2
} else if y == 4 && x == 2 {
ADST_FLIPADST // not selected if Max lift works
} else {
FLIPADST_FLIPADST
}
};
let r = compute_tx_type(
1,
TX_8X8,
false,
true,
TX_SET_INTER_1,
4,
4, // MiRow = MiCol = 4
1,
1,
1,
1, // block_x = block_y = 1; subsampling = 1
0,
cache,
);
assert_eq!(r, ADST_DCT);
// (b) Admission pass: TX_SET_INTER_3 admits { IDTX, DCT_DCT }.
// Cache returns IDTX; result must be IDTX (not DCT_DCT).
let idtx_cache = |_: u32, _: u32| IDTX;
let r2 = compute_tx_type(
2,
TX_8X8,
false,
true,
TX_SET_INTER_3,
0,
0,
0,
0,
0,
0,
0,
idtx_cache,
);
assert_eq!(r2, IDTX);
// (c) Admission fail: TX_SET_INTER_3 rejects ADST_DCT, so the
// §5.11.40 `!is_tx_type_in_set` fallback returns DCT_DCT.
let adst_cache = |_: u32, _: u32| ADST_DCT;
let r3 = compute_tx_type(
2,
TX_8X8,
false,
true,
TX_SET_INTER_3,
0,
0,
0,
0,
0,
0,
0,
adst_cache,
);
assert_eq!(r3, DCT_DCT);
}
/// §5.11.40 `compute_tx_type` intra chroma branch:
/// `Mode_To_Txfm[UVMode]` then `is_tx_type_in_set(false, txSet,
/// txType)`. The `TxTypes` closure is *not* invoked. Covers a
/// successful admission path, a fallback path, and an
/// out-of-range `uv_mode` (returns DCT_DCT).
#[test]
fn compute_tx_type_intra_chroma_uv_mode_path() {
// The closure must NOT be invoked on the intra chroma branch
// — drive it through a panic-on-call closure to prove it.
let never_call = |_: u32, _: u32| -> usize {
panic!("luma TxTypes cache must not be read on intra-chroma branch")
};
// (a) UVMode = D135_PRED (4) -> ADST_ADST. INTRA_1 admits it.
let r1 = compute_tx_type(
1,
TX_8X8,
false,
false,
TX_SET_INTRA_1,
0,
0,
0,
0,
0,
0,
4,
never_call,
);
assert_eq!(r1, ADST_ADST);
// (b) UVMode = V_PRED (1) -> ADST_DCT. Under TX_SET_DCTONLY
// the admission filter rejects -> DCT_DCT fallback.
let r2 = compute_tx_type(
1,
TX_8X8,
false,
false,
TX_SET_DCTONLY,
0,
0,
0,
0,
0,
0,
1,
never_call,
);
assert_eq!(r2, DCT_DCT);
// (c) Out-of-range uv_mode -> DCT_DCT.
let r3 = compute_tx_type(
2,
TX_8X8,
false,
false,
TX_SET_INTRA_1,
0,
0,
0,
0,
0,
0,
99,
never_call,
);
assert_eq!(r3, DCT_DCT);
}
/// §5.11.40 `compute_tx_type` and the existing
/// [`inter_tx_type_set`] / [`intra_tx_type_set`] selectors form a
/// closed loop: the txSet that selector returns for a given
/// (tx_sz_sqr, tx_sz_sqr_up, reduced_tx_set) triple is the txSet
/// the admission filter must use. Spot-check the loop for a
/// 16×16 inter chroma block.
#[test]
fn compute_tx_type_uses_selector_consistent_tx_set() {
// tx_sz = TX_16X16 -> sqr = sqr_up = TX_16X16.
let tx_sz_sqr = TX_WIDTH[TX_16X16].trailing_zeros() - 2; // 16 -> log2=4, bwl_index 2
// Just use the spec's explicit ordinal: TX_16X16.
let tx_set = inter_tx_type_set(TX_16X16 as u32, TX_16X16 as u32, false);
assert_eq!(tx_set, TX_SET_INTER_2);
// Cache returns DCT_DCT (admitted under INTER_2). Result is
// DCT_DCT.
let cache = |_: u32, _: u32| DCT_DCT;
let r = compute_tx_type(1, TX_16X16, false, true, tx_set, 0, 0, 0, 0, 0, 0, 0, cache);
assert_eq!(r, DCT_DCT);
// Silence "unused" warning on the local — it documents the
// bwl ordinal the closure-free path computes.
let _ = tx_sz_sqr;
}
// -----------------------------------------------------------------
// Round 143 — inter-intra group tests.
// -----------------------------------------------------------------
/// §3 / §6.10.27: `INTERINTRA_MODES = 4`, matching the spec's
/// enumeration count.
#[test]
fn interintra_modes_constant_matches_spec() {
assert_eq!(INTERINTRA_MODES, 4);
}
/// `Default_Inter_Intra_Cdf` / `Default_Inter_Intra_Mode_Cdf` /
/// `Default_Wedge_Inter_Intra_Cdf` are pinned verbatim from §9.4 and
/// satisfy the §8.2.6 well-formedness invariants every other §9.4
/// table satisfies: the trailing entry of each row is `0` (the §8.3
/// adaptation counter) and the next-to-last is `1 << 15 == 32768`.
#[test]
fn inter_intra_default_tables_pinned() {
// Shape pins straight off the §9.4 listing.
assert_eq!(DEFAULT_INTER_INTRA_CDF.len(), BLOCK_SIZE_GROUPS - 1);
assert_eq!(DEFAULT_INTER_INTRA_MODE_CDF.len(), BLOCK_SIZE_GROUPS - 1);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF.len(), BLOCK_SIZES);
// Spec-pinned values: `Default_Inter_Intra_Cdf` (verbatim from
// §9.4 listing, p.434).
assert_eq!(DEFAULT_INTER_INTRA_CDF[0], [26887, 32768, 0]);
assert_eq!(DEFAULT_INTER_INTRA_CDF[1], [27597, 32768, 0]);
assert_eq!(DEFAULT_INTER_INTRA_CDF[2], [30237, 32768, 0]);
// `Default_Inter_Intra_Mode_Cdf` (verbatim from §9.4, p.434).
assert_eq!(
DEFAULT_INTER_INTRA_MODE_CDF[0],
[1875, 11082, 27332, 32768, 0]
);
assert_eq!(
DEFAULT_INTER_INTRA_MODE_CDF[1],
[2473, 9996, 26388, 32768, 0]
);
assert_eq!(
DEFAULT_INTER_INTRA_MODE_CDF[2],
[4238, 11537, 25926, 32768, 0]
);
// `Default_Wedge_Inter_Intra_Cdf` (verbatim from §9.4, p.436):
// per the spec note, only indices 3..=9 are reachable; rows 0..2
// and 10..21 are placeholder `{16384, 32768, 0}`.
for row in &DEFAULT_WEDGE_INTER_INTRA_CDF[0..3] {
assert_eq!(row, &[16384, 32768, 0]);
}
for row in &DEFAULT_WEDGE_INTER_INTRA_CDF[10..BLOCK_SIZES] {
assert_eq!(row, &[16384, 32768, 0]);
}
// Reachable band (BLOCK_8X8..=BLOCK_32X32 -> indices 3..=9).
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[3], [20036, 32768, 0]);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[4], [24957, 32768, 0]);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[5], [26704, 32768, 0]);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[6], [27530, 32768, 0]);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[7], [29564, 32768, 0]);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[8], [29444, 32768, 0]);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[9], [26872, 32768, 0]);
// §8.2.6 well-formedness invariants on every row of every CDF.
for row in &DEFAULT_INTER_INTRA_CDF {
assert_eq!(row[row.len() - 1], 0);
assert_eq!(row[row.len() - 2], 1 << 15);
}
for row in &DEFAULT_INTER_INTRA_MODE_CDF {
assert_eq!(row[row.len() - 1], 0);
assert_eq!(row[row.len() - 2], 1 << 15);
}
for row in &DEFAULT_WEDGE_INTER_INTRA_CDF {
assert_eq!(row[row.len() - 1], 0);
assert_eq!(row[row.len() - 2], 1 << 15);
}
}
/// §8.3.1 `init_non_coeff_cdfs`: the working-copy fields are seeded
/// verbatim from the §9.4 defaults.
#[test]
fn inter_intra_init_from_defaults() {
let ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.inter_intra, DEFAULT_INTER_INTRA_CDF);
assert_eq!(ctx.inter_intra_mode, DEFAULT_INTER_INTRA_MODE_CDF);
assert_eq!(ctx.wedge_inter_intra, DEFAULT_WEDGE_INTER_INTRA_CDF);
}
/// `interintra_ctx` implements the §8.3.2
/// `ctx = Size_Group[ MiSize ] - 1` mapping across the entire
/// `BLOCK_SIZES` axis: rows where `Size_Group[ MiSize ] == 0` (i.e.
/// outside the §5.11.28 syntax gate) return `None`, the rest return
/// `Size_Group[ MiSize ] - 1` in `0..(BLOCK_SIZE_GROUPS - 1)`.
#[test]
fn interintra_ctx_matches_size_group_minus_one() {
for (mi_size, &g) in SIZE_GROUP.iter().enumerate() {
match interintra_ctx(mi_size) {
None => assert_eq!(g, 0),
Some(ctx) => {
assert!(g > 0);
assert_eq!(ctx, g - 1);
assert!(ctx < BLOCK_SIZE_GROUPS - 1);
}
}
}
// Out-of-range MiSize is rejected.
assert!(interintra_ctx(BLOCK_SIZES).is_none());
assert!(interintra_ctx(BLOCK_SIZES + 7).is_none());
// §5.11.28 syntax gate band: BLOCK_8X8 (3) .. BLOCK_32X32 (9)
// inclusive must all return Some. Spot-check each.
for mi_size in 3..=9 {
assert!(
interintra_ctx(mi_size).is_some(),
"MiSize {mi_size} (in BLOCK_8X8..=BLOCK_32X32) must yield Some(ctx)"
);
}
}
/// §8.3.2 inter-intra selectors return the right §9.4 rows for every
/// valid context, and reject out-of-range indices with `None`.
#[test]
fn inter_intra_selectors_return_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for (i, want) in DEFAULT_INTER_INTRA_CDF.iter().enumerate() {
let row = ctx.inter_intra_cdf(i).unwrap();
assert_eq!(row.len(), 3);
assert_eq!(row, want);
}
assert!(ctx.inter_intra_cdf(BLOCK_SIZE_GROUPS - 1).is_none());
assert!(ctx.inter_intra_cdf(BLOCK_SIZE_GROUPS).is_none());
for (i, want) in DEFAULT_INTER_INTRA_MODE_CDF.iter().enumerate() {
let row = ctx.inter_intra_mode_cdf(i).unwrap();
assert_eq!(row.len(), INTERINTRA_MODES + 1);
assert_eq!(row, want);
}
assert!(ctx.inter_intra_mode_cdf(BLOCK_SIZE_GROUPS - 1).is_none());
for (i, want) in DEFAULT_WEDGE_INTER_INTRA_CDF.iter().enumerate() {
let row = ctx.wedge_inter_intra_cdf(i).unwrap();
assert_eq!(row.len(), 3);
assert_eq!(row, want);
}
assert!(ctx.wedge_inter_intra_cdf(BLOCK_SIZES).is_none());
assert!(ctx.wedge_inter_intra_cdf(BLOCK_SIZES + 5).is_none());
}
/// Working-copy independence: mutating the context via the selectors
/// must not touch the §9.4 source tables.
#[test]
fn inter_intra_working_copy_is_independent() {
let mut ctx = TileCdfContext::new_from_defaults();
ctx.inter_intra_cdf(0).unwrap()[0] = 12345;
ctx.inter_intra_mode_cdf(1).unwrap()[0] = 23456;
ctx.wedge_inter_intra_cdf(3).unwrap()[0] = 34567;
assert_ne!(ctx.inter_intra[0][0], DEFAULT_INTER_INTRA_CDF[0][0]);
assert_ne!(
ctx.inter_intra_mode[1][0],
DEFAULT_INTER_INTRA_MODE_CDF[1][0]
);
assert_ne!(
ctx.wedge_inter_intra[3][0],
DEFAULT_WEDGE_INTER_INTRA_CDF[3][0]
);
// Spec sources unchanged.
assert_eq!(DEFAULT_INTER_INTRA_CDF[0][0], 26887);
assert_eq!(DEFAULT_INTER_INTRA_MODE_CDF[1][0], 2473);
assert_eq!(DEFAULT_WEDGE_INTER_INTRA_CDF[3][0], 20036);
}
/// End-to-end: drive the real §8.2 `SymbolDecoder` through both an
/// `interintra` (binary) and an `interintra_mode` default CDF
/// selected via the §8.3.2 `ctx = Size_Group[ MiSize ] - 1` mapping
/// for a `BLOCK_16X16` (`MiSize = 6`, `Size_Group = 2`, `ctx = 1`),
/// confirming the row matches the §9.4 source, the decode lands in
/// range, and the working copy adapts.
#[test]
fn decode_inter_intra_through_default_cdf() {
// BLOCK_16X16 (MiSize = 6) -> Size_Group[6] = 2 -> ctx = 1.
assert_eq!(SIZE_GROUP[6], 2);
let ctx_val = interintra_ctx(6).unwrap();
assert_eq!(ctx_val, 1);
// Binary `interintra` decode.
{
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.inter_intra;
let row = tile_ctx.inter_intra_cdf(ctx_val).unwrap();
assert_eq!(row, &DEFAULT_INTER_INTRA_CDF[1]);
let sym = dec.read_symbol(row).unwrap();
assert!(sym < 2, "interintra must code a binary symbol");
assert_ne!(
tile_ctx.inter_intra, before,
"read_symbol must adapt the working CDF"
);
}
// 4-symbol `interintra_mode` decode.
{
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.inter_intra_mode;
let row = tile_ctx.inter_intra_mode_cdf(ctx_val).unwrap();
assert_eq!(row, &DEFAULT_INTER_INTRA_MODE_CDF[1]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < INTERINTRA_MODES,
"interintra_mode must code a symbol in 0..INTERINTRA_MODES"
);
assert_ne!(
tile_ctx.inter_intra_mode, before,
"read_symbol must adapt the working CDF"
);
}
}
/// End-to-end: drive the §8.2 `SymbolDecoder` through a
/// `wedge_interintra` default CDF for a `BLOCK_16X16` (`MiSize = 6`,
/// inside the §5.11.28 reachable band), confirming the row matches
/// the §9.4 source, the binary decode lands in range, and the working
/// copy adapts.
#[test]
fn decode_wedge_inter_intra_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.wedge_inter_intra;
// MiSize = 6 (BLOCK_16X16) is inside the §5.11.28 reachable band
// (rows 3..=9) where the §9.4 table holds non-placeholder values.
let row = tile_ctx.wedge_inter_intra_cdf(6).unwrap();
assert_eq!(row, &DEFAULT_WEDGE_INTER_INTRA_CDF[6]);
let sym = dec.read_symbol(row).unwrap();
assert!(sym < 2, "wedge_interintra must code a binary symbol");
assert_ne!(
tile_ctx.wedge_inter_intra, before,
"read_symbol must adapt the working CDF"
);
}
// -----------------------------------------------------------------
// Round 144 — wedge-index CDF tests.
// -----------------------------------------------------------------
/// §3: `WEDGE_TYPES = 16`, matching the spec's *"Number of directions
/// for the wedge mask process"* row in the constants table.
#[test]
fn wedge_types_constant_matches_spec() {
assert_eq!(WEDGE_TYPES, 16);
}
/// `Default_Wedge_Index_Cdf` is pinned verbatim from §9.4 (p.435) and
/// satisfies the §8.2.6 well-formedness invariants every other §9.4
/// table satisfies: the trailing entry of each row is `0` (the §8.3
/// adaptation counter) and the next-to-last is `1 << 15 == 32768`.
/// Per the §9.4 note (p.436), indices 0..2, 10..17, and 20..21 in the
/// first dimension are never used and carry the placeholder uniform
/// CDF (step 2048 = 32768 / 16).
#[test]
fn wedge_index_default_table_pinned() {
// Shape pins straight off the §9.4 listing.
assert_eq!(DEFAULT_WEDGE_INDEX_CDF.len(), BLOCK_SIZES);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[0].len(), WEDGE_TYPES + 1);
// §8.2.6 well-formedness invariants on every row.
for row in &DEFAULT_WEDGE_INDEX_CDF {
assert_eq!(row[row.len() - 1], 0);
assert_eq!(row[row.len() - 2], 1 << 15);
// Cumulative frequencies strictly increase to 32768.
for w in row[..row.len() - 1].windows(2) {
assert!(w[0] < w[1], "cdf must be strictly increasing: {row:?}");
}
}
// §9.4 note: placeholder rows (uniform 16-symbol CDF, step 2048).
let placeholder: [u16; WEDGE_TYPES + 1] = [
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
];
// Indices where the §9.4 note says the row is never used.
let placeholder_ix: &[usize] = &[0, 1, 2, 10, 11, 12, 13, 14, 15, 16, 17, 20, 21];
for &i in placeholder_ix {
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[i], placeholder, "row {i}");
}
// Reachable band 3..=9 (BLOCK_8X8..BLOCK_32X32): spec-pinned
// leading-frequency anchors and last-frequency anchors.
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[3][0], 2438);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[3][14], 31171);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[4][0], 806);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[4][14], 29629);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[5][0], 2779);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[5][14], 31656);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[6][0], 1684);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[6][14], 30877);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[7][0], 1142);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[7][14], 29550);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[8][0], 2742);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[8][14], 31416);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[9][0], 1727);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[9][14], 31012);
// Reachable band 18..=19 (BLOCK_8X16 / BLOCK_16X8): pin both
// leading values and one mid-row value each (a transcription
// typo here would not be caught by either uniform-row or
// adjacency checks elsewhere).
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[18][0], 154);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[18][7], 23033);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[18][14], 30911);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[19][0], 1135);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[19][7], 21016);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[19][14], 31933);
}
/// §8.3.1 `init_non_coeff_cdfs`: the working-copy `wedge_index` field
/// is seeded verbatim from the §9.4 default.
#[test]
fn wedge_index_init_from_defaults() {
let ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.wedge_index, DEFAULT_WEDGE_INDEX_CDF);
}
/// §8.3.2 `wedge_index_cdf` selector returns the right §9.4 row for
/// every valid `MiSize`, and rejects out-of-range indices with
/// `None`. Cross-checks the §3 `Wedge_Bits[ MiSize ] > 0` rule: the
/// placeholder rows match exactly the `Wedge_Bits == 0` positions in
/// the spec's table (indices 0..2, 10..17, 20..21).
#[test]
fn wedge_index_selector_returns_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for (i, want) in DEFAULT_WEDGE_INDEX_CDF.iter().enumerate() {
let row = ctx.wedge_index_cdf(i).unwrap();
assert_eq!(row.len(), WEDGE_TYPES + 1);
assert_eq!(row, want);
}
assert!(ctx.wedge_index_cdf(BLOCK_SIZES).is_none());
assert!(ctx.wedge_index_cdf(BLOCK_SIZES + 7).is_none());
// §3 `Wedge_Bits[ BLOCK_SIZES ]` table — non-zero only for the
// reachable rows enumerated above.
let wedge_bits: [u8; BLOCK_SIZES] = [
0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0,
];
let placeholder: [u16; WEDGE_TYPES + 1] = [
2048, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 0,
];
for (i, &bits) in wedge_bits.iter().enumerate() {
if bits == 0 {
assert_eq!(
DEFAULT_WEDGE_INDEX_CDF[i], placeholder,
"row {i} (Wedge_Bits == 0) must be the placeholder uniform CDF"
);
} else {
assert_ne!(
DEFAULT_WEDGE_INDEX_CDF[i], placeholder,
"row {i} (Wedge_Bits > 0) must hold a non-placeholder CDF"
);
}
}
}
/// Working-copy independence: mutating the context via the selector
/// must not touch the §9.4 source table.
#[test]
fn wedge_index_working_copy_is_independent() {
let mut ctx = TileCdfContext::new_from_defaults();
ctx.wedge_index_cdf(3).unwrap()[0] = 12345;
ctx.wedge_index_cdf(18).unwrap()[1] = 23456;
assert_ne!(ctx.wedge_index[3][0], DEFAULT_WEDGE_INDEX_CDF[3][0]);
assert_ne!(ctx.wedge_index[18][1], DEFAULT_WEDGE_INDEX_CDF[18][1]);
// Spec source unchanged.
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[3][0], 2438);
assert_eq!(DEFAULT_WEDGE_INDEX_CDF[18][1], 987);
}
/// End-to-end: drive the §8.2 `SymbolDecoder` through a `wedge_index`
/// default CDF for a `BLOCK_16X16` (`MiSize = 6`, inside the §5.11.28
/// / §5.11.29 reachable band), confirming the row matches the §9.4
/// source, the 16-symbol decode lands in `0..WEDGE_TYPES`, and the
/// working copy adapts.
#[test]
fn decode_wedge_index_through_default_cdf() {
let bytes = [0x10u8, 0x80u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
let before = tile_ctx.wedge_index;
// MiSize = 6 (BLOCK_16X16): Wedge_Bits[6] = 4, so wedge_index is
// coded here per the §5.11.29 `COMPOUND_WEDGE` branch and the
// §5.11.28 wedge sub-branch.
let row = tile_ctx.wedge_index_cdf(6).unwrap();
assert_eq!(row, &DEFAULT_WEDGE_INDEX_CDF[6]);
let sym = dec.read_symbol(row).unwrap();
assert!(
(sym as usize) < WEDGE_TYPES,
"wedge_index must code a symbol in 0..WEDGE_TYPES"
);
assert_ne!(
tile_ctx.wedge_index, before,
"read_symbol must adapt the working CDF"
);
}
// -----------------------------------------------------------------
// Round 145 — §8.3.2 split_or_horz / split_or_vert derivations.
// -----------------------------------------------------------------
/// §6.10.4 partition ordinals: the §8.3.2 `psum` formulas index
/// `partitionCdf[ PARTITION_X ]` and `partitionCdf[ PARTITION_X - 1 ]`,
/// so the exact ordinal values are load-bearing.
#[test]
fn partition_ordinals_match_spec_table_p172() {
assert_eq!(PARTITION_NONE, 0);
assert_eq!(PARTITION_HORZ, 1);
assert_eq!(PARTITION_VERT, 2);
assert_eq!(PARTITION_SPLIT, 3);
assert_eq!(PARTITION_HORZ_A, 4);
assert_eq!(PARTITION_HORZ_B, 5);
assert_eq!(PARTITION_VERT_A, 6);
assert_eq!(PARTITION_VERT_B, 7);
assert_eq!(PARTITION_HORZ_4, 8);
assert_eq!(PARTITION_VERT_4, 9);
assert_eq!(EXT_PARTITION_TYPES, 10);
assert_eq!(BLOCK_128X128, 15);
}
/// §9.4 partition cdf row shapes match the §8.3.2 split_or_*
/// indexing budget: W16/W32/W64 carry the full 10-symbol cdf (+1
/// trailing count) so all `PARTITION_*_4` reads are in range, and
/// W128 carries the 8-symbol cdf (+1) so the largest index used
/// (`PARTITION_VERT_B = 7`) fits.
#[test]
fn partition_cdf_row_lengths_match_spec_p362_indexing_budget() {
assert_eq!(DEFAULT_PARTITION_W16_CDF[0].len(), EXT_PARTITION_TYPES + 1);
assert_eq!(DEFAULT_PARTITION_W32_CDF[0].len(), EXT_PARTITION_TYPES + 1);
assert_eq!(DEFAULT_PARTITION_W64_CDF[0].len(), EXT_PARTITION_TYPES + 1);
// W128 drops the two *_4 ordinals (no HORZ_4 / VERT_4 entry).
assert_eq!(
DEFAULT_PARTITION_W128_CDF[0].len(),
EXT_PARTITION_TYPES - 2 + 1
);
// PARTITION_VERT_B (= 7) must fit inside the W128 cdf row.
assert!(DEFAULT_PARTITION_W128_CDF[0].len() > PARTITION_VERT_B);
}
/// §8.3.2 `split_or_horz` cdf derivation: starting from a known
/// `partitionCdf` (the `Default_Partition_W16_Cdf[ ctx = 0 ]` row),
/// the helper must reproduce the spec's algebra exactly. Cross-checks
/// against an independent re-derivation that walks the partitionCdf
/// without using the helper.
#[test]
fn split_or_horz_cdf_matches_spec_psum_for_w16_ctx0() {
let partition_cdf = &DEFAULT_PARTITION_W16_CDF[0];
// Non-128 size: bSize = BLOCK_16X16 = 6 (Mi_Width_Log2 = 2 ⇒
// bsl = 2 ⇒ W16). Includes the PARTITION_VERT_4 term.
let cdf = split_or_horz_cdf(partition_cdf, 6).expect("non-empty");
let psum_re: i32 = (partition_cdf[PARTITION_VERT] as i32
- partition_cdf[PARTITION_VERT - 1] as i32)
+ (partition_cdf[PARTITION_SPLIT] as i32 - partition_cdf[PARTITION_SPLIT - 1] as i32)
+ (partition_cdf[PARTITION_HORZ_A] as i32 - partition_cdf[PARTITION_HORZ_A - 1] as i32)
+ (partition_cdf[PARTITION_VERT_A] as i32 - partition_cdf[PARTITION_VERT_A - 1] as i32)
+ (partition_cdf[PARTITION_VERT_B] as i32 - partition_cdf[PARTITION_VERT_B - 1] as i32)
+ (partition_cdf[PARTITION_VERT_4] as i32 - partition_cdf[PARTITION_VERT_4 - 1] as i32);
assert_eq!(cdf[0] as i32, (1 << 15) - psum_re);
assert_eq!(cdf[1], 1 << 15);
assert_eq!(cdf[2], 0);
}
/// §8.3.2 `split_or_vert` cdf derivation: same shape, different
/// symbol set (HORZ / SPLIT / HORZ_A / HORZ_B / VERT_A [+ HORZ_4
/// when not BLOCK_128X128]). Cross-checks against an independent
/// re-derivation.
#[test]
fn split_or_vert_cdf_matches_spec_psum_for_w32_ctx3() {
let partition_cdf = &DEFAULT_PARTITION_W32_CDF[3];
let cdf = split_or_vert_cdf(partition_cdf, 9).expect("non-empty");
let psum_re: i32 = (partition_cdf[PARTITION_HORZ] as i32
- partition_cdf[PARTITION_HORZ - 1] as i32)
+ (partition_cdf[PARTITION_SPLIT] as i32 - partition_cdf[PARTITION_SPLIT - 1] as i32)
+ (partition_cdf[PARTITION_HORZ_A] as i32 - partition_cdf[PARTITION_HORZ_A - 1] as i32)
+ (partition_cdf[PARTITION_HORZ_B] as i32 - partition_cdf[PARTITION_HORZ_B - 1] as i32)
+ (partition_cdf[PARTITION_VERT_A] as i32 - partition_cdf[PARTITION_VERT_A - 1] as i32)
+ (partition_cdf[PARTITION_HORZ_4] as i32 - partition_cdf[PARTITION_HORZ_4 - 1] as i32);
assert_eq!(cdf[0] as i32, (1 << 15) - psum_re);
assert_eq!(cdf[1], 1 << 15);
assert_eq!(cdf[2], 0);
}
/// `b_size == BLOCK_128X128`: the `PARTITION_*_4` term is dropped
/// from `psum`. The W128 cdf row has no `PARTITION_HORZ_4` /
/// `PARTITION_VERT_4` entry (the 8-symbol row's last cdf index is
/// `PARTITION_VERT_B = 7`; index 8 is the §8.3 trailing count slot),
/// and the helper must not read past the §6.10.4 cdf range. Verifies
/// the omitted-term case and confirms the resulting binary cdf is
/// well-formed.
#[test]
fn split_or_horz_cdf_drops_partition_4_term_for_block_128x128() {
let partition_cdf = &DEFAULT_PARTITION_W128_CDF[1];
// §9.4 W128 cdf row: 8 partition symbols (indices 0..=7) + 1
// trailing count slot at index 8.
assert_eq!(partition_cdf.len(), EXT_PARTITION_TYPES - 2 + 1);
// PARTITION_VERT_B (= 7) is the last partition-cdf entry.
assert!(partition_cdf.len() > PARTITION_VERT_B);
let cdf = split_or_horz_cdf(partition_cdf, BLOCK_128X128).expect("non-empty");
// Re-derive without the VERT_4 term.
let psum_re: i32 = (partition_cdf[PARTITION_VERT] as i32
- partition_cdf[PARTITION_VERT - 1] as i32)
+ (partition_cdf[PARTITION_SPLIT] as i32 - partition_cdf[PARTITION_SPLIT - 1] as i32)
+ (partition_cdf[PARTITION_HORZ_A] as i32 - partition_cdf[PARTITION_HORZ_A - 1] as i32)
+ (partition_cdf[PARTITION_VERT_A] as i32 - partition_cdf[PARTITION_VERT_A - 1] as i32)
+ (partition_cdf[PARTITION_VERT_B] as i32 - partition_cdf[PARTITION_VERT_B - 1] as i32);
assert_eq!(cdf[0] as i32, (1 << 15) - psum_re);
assert_eq!(cdf[1], 1 << 15);
assert_eq!(cdf[2], 0);
// §8.2.6 well-formedness: monotonically increasing in `[0, 1<<15]`.
assert!(cdf[0] <= cdf[1]);
assert_eq!(cdf[1] as u32, 1 << 15);
}
/// `b_size == BLOCK_128X128`: the same drop applies to
/// `split_or_vert` for the `PARTITION_HORZ_4` term.
#[test]
fn split_or_vert_cdf_drops_partition_4_term_for_block_128x128() {
let partition_cdf = &DEFAULT_PARTITION_W128_CDF[2];
assert_eq!(partition_cdf.len(), EXT_PARTITION_TYPES - 2 + 1);
let cdf = split_or_vert_cdf(partition_cdf, BLOCK_128X128).expect("non-empty");
let psum_re: i32 = (partition_cdf[PARTITION_HORZ] as i32
- partition_cdf[PARTITION_HORZ - 1] as i32)
+ (partition_cdf[PARTITION_SPLIT] as i32 - partition_cdf[PARTITION_SPLIT - 1] as i32)
+ (partition_cdf[PARTITION_HORZ_A] as i32 - partition_cdf[PARTITION_HORZ_A - 1] as i32)
+ (partition_cdf[PARTITION_HORZ_B] as i32 - partition_cdf[PARTITION_HORZ_B - 1] as i32)
+ (partition_cdf[PARTITION_VERT_A] as i32 - partition_cdf[PARTITION_VERT_A - 1] as i32);
assert_eq!(cdf[0] as i32, (1 << 15) - psum_re);
assert_eq!(cdf[1], 1 << 15);
assert_eq!(cdf[2], 0);
assert!(cdf[0] <= cdf[1]);
}
/// `split_or_horz` / `split_or_vert` over every reachable §8.3.2
/// `(bsl, ctx, b_size)` triple: the §8.2.6 well-formedness invariants
/// hold for every default `partition` cdf row in the workspace
/// (`cdf[1] == 32768`, `cdf[0] <= cdf[1]`, `cdf[2] == 0`). bsl == 1
/// is excluded per the §8.3.2 note "bsl is never equal to 1 when
/// decoding split_or_horz / split_or_vert".
#[test]
fn split_or_helpers_well_formed_for_every_partition_cdf_row() {
// (rows, b_size used to gate the *_4 term)
let suites: [(&[[u16; 11]], usize); 3] = [
(&DEFAULT_PARTITION_W16_CDF, 6),
(&DEFAULT_PARTITION_W32_CDF, 9),
(&DEFAULT_PARTITION_W64_CDF, 12),
];
for (rows, b_size) in suites {
for row in rows {
for helper in [split_or_horz_cdf, split_or_vert_cdf] {
let cdf = helper(row, b_size).expect("must succeed for W16/W32/W64");
assert_eq!(cdf[1] as u32, 1 << 15);
assert_eq!(cdf[2], 0);
assert!(cdf[0] <= cdf[1], "binary cdf must satisfy cdf[0] <= cdf[1]");
}
}
}
// W128 (8-symbol rows, b_size = BLOCK_128X128).
for row in &DEFAULT_PARTITION_W128_CDF {
for helper in [split_or_horz_cdf, split_or_vert_cdf] {
let cdf = helper(row, BLOCK_128X128).expect("must succeed for W128");
assert_eq!(cdf[1] as u32, 1 << 15);
assert_eq!(cdf[2], 0);
assert!(cdf[0] <= cdf[1]);
}
}
}
/// The §8.3.2 note explicitly says `split_or_horz` is never allowed
/// to return a `PARTITION_VERT` outcome — its 2-symbol cdf folds the
/// `PARTITION_VERT` probability into the split branch. End-to-end:
/// drive the §8.2 `SymbolDecoder` through the derived 2-symbol
/// `split_or_horz` cdf for `BLOCK_64X64` and confirm the symbol
/// lands in `0..2`.
#[test]
fn decode_split_or_horz_through_derived_cdf() {
let bytes = [0x00u8, 0x40u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
// bSize = BLOCK_64X64 (= 12); bsl = 4 ⇒ W64 row; ctx = 0.
let partition_cdf = tile_ctx.partition_cdf(4, 0).unwrap();
let mut split_or_horz = split_or_horz_cdf(partition_cdf, 12).unwrap();
let sym = dec.read_symbol(&mut split_or_horz).unwrap();
assert!(
(sym as usize) < 2,
"split_or_horz must code a binary symbol in 0..2"
);
// The 0/1 outcome maps to {PARTITION_HORZ, PARTITION_SPLIT};
// never to PARTITION_VERT — covered structurally by the helper
// (the derived cdf has only two outcomes), not the decoded value.
}
/// Symmetric end-to-end test for `split_or_vert`.
#[test]
fn decode_split_or_vert_through_derived_cdf() {
let bytes = [0xc0u8, 0x00u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile_ctx = TileCdfContext::new_from_defaults();
// bSize = BLOCK_128X128 — exercise the *_4-omission branch
// alongside the end-to-end symbol decode.
let partition_cdf = tile_ctx.partition_cdf(5, 1).unwrap();
let mut split_or_vert = split_or_vert_cdf(partition_cdf, BLOCK_128X128).unwrap();
let sym = dec.read_symbol(&mut split_or_vert).unwrap();
assert!(
(sym as usize) < 2,
"split_or_vert must code a binary symbol in 0..2"
);
}
/// Out-of-range guard: a short `partition_cdf` (the W8 row, which
/// the §8.3.2 note forbids for split_or_horz / split_or_vert) is
/// rejected with `None` rather than panicking.
#[test]
fn split_or_helpers_reject_w8_row() {
let w8_row = &DEFAULT_PARTITION_W8_CDF[0];
assert!(split_or_horz_cdf(w8_row, 3).is_none());
assert!(split_or_vert_cdf(w8_row, 3).is_none());
}
// -----------------------------------------------------------------
// Round 147 — §5.11.49 palette_tokens diagonal walker tests.
// -----------------------------------------------------------------
/// §5.11.49 — caller-bug pre-conditions all reject with
/// [`crate::Error::InvalidPaletteWalkArgs`] before any symbol is
/// read. The walker never advances the decoder on a rejection.
#[test]
fn palette_tokens_plane_rejects_caller_bug_args() {
// Common harness: a 4-byte stream + 4x4 buffer + size-2 palette
// — bytes don't matter because every assertion is supposed to
// short-circuit before the first read_symbol.
let bytes = [0xFFu8, 0xFEu8, 0u8, 0u8];
// palette_size out of 2..=PALETTE_COLORS.
for &bad_n in &[0usize, 1, PALETTE_COLORS + 1, 100] {
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 16];
let r = palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
bad_n,
4,
4,
4,
4,
0,
&mut cm,
4,
);
assert!(matches!(r, Err(crate::Error::InvalidPaletteWalkArgs)));
}
// onscreen_w == 0 or onscreen_h == 0.
for (ow, oh) in [(0usize, 4usize), (4, 0)] {
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 16];
let r = palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
4,
4,
4,
ow,
oh,
0,
&mut cm,
4,
);
assert!(matches!(r, Err(crate::Error::InvalidPaletteWalkArgs)));
}
// onscreen_w > block_w / onscreen_h > block_h.
for (bw, bh, ow, oh) in [(4usize, 4usize, 5, 4), (4, 4, 4, 5)] {
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 32];
let r = palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
4,
bw,
bh,
ow,
oh,
0,
&mut cm,
bw.max(ow),
);
assert!(matches!(r, Err(crate::Error::InvalidPaletteWalkArgs)));
}
// stride < block_width.
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 12];
let r = palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
4,
4,
4,
4,
4,
0,
&mut cm,
3,
);
assert!(matches!(r, Err(crate::Error::InvalidPaletteWalkArgs)));
}
// color_map buffer too small (block_h * stride > len).
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 12]; // need 16
let r = palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
4,
4,
4,
4,
4,
0,
&mut cm,
4,
);
assert!(matches!(r, Err(crate::Error::InvalidPaletteWalkArgs)));
}
// color_index_map >= palette_size.
{
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 16];
let r = palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
4,
4,
4,
4,
4,
4, // == palette_size; spec requires <
&mut cm,
4,
);
assert!(matches!(r, Err(crate::Error::InvalidPaletteWalkArgs)));
}
}
/// §5.11.49 — the corner case of `onscreen_width == block_width`
/// and `onscreen_height == block_height`: no border-fill happens
/// and every interior sample of the on-screen rectangle is
/// written. Test on a 2x2 / palette-size-2 walk: the diagonal
/// loop runs i=1,2 with j-sequences (1,0) and (1) respectively, so
/// three `read_symbol` calls happen.
#[test]
fn palette_tokens_plane_2x2_no_border_fill_writes_every_cell() {
// Use SymbolDecoder bytes that decode through the size-2 cdf
// family producing in-range symbols. The exact symbol values
// are an implementation detail of the §8.2.6 search; we only
// assert structural properties (every cell < palette_size).
let bytes = [0x55u8, 0xAAu8, 0x55u8, 0xAAu8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0xFFu8; 4];
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
2,
2,
2,
2,
1,
&mut cm,
2,
)
.unwrap();
// (0,0) seed.
assert_eq!(cm[0], 1);
// Every cell holds a valid palette index.
for v in cm.iter() {
assert!((*v as usize) < 2, "cell value {v} >= palette_size 2");
}
}
/// §5.11.49 — horizontal border-fill: for a 2-wide on-screen
/// block inside a 4-wide block, the (0..onscreen_h) rows must
/// have columns 2 and 3 replicated from column 1 after the walk.
#[test]
fn palette_tokens_plane_horizontal_border_fill_replicates_column() {
// Hand-craft a state: seed the walker with the 2x2 on-screen
// walk through a size-2 palette, then check the right-edge
// extension.
let bytes = [0xC0u8, 0x00u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 8]; // 2 rows * stride 4
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
4,
2,
2,
2,
1,
&mut cm,
4,
)
.unwrap();
// Right-edge cells (cols 2, 3) equal the on-screen last column
// (col 1) in each row.
for r in 0..2usize {
assert_eq!(cm[r * 4 + 2], cm[r * 4 + 1]);
assert_eq!(cm[r * 4 + 3], cm[r * 4 + 1]);
}
}
/// §5.11.49 — vertical border-fill: rows from `onscreen_height` up
/// to `block_height` are replicated from row `onscreen_height - 1`
/// across the full `block_width`.
#[test]
fn palette_tokens_plane_vertical_border_fill_replicates_row() {
let bytes = [0xC0u8, 0x00u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 16]; // 4 rows * stride 4
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
4,
4, // block 4x4
4,
2, // on-screen 4x2
1,
&mut cm,
4,
)
.unwrap();
// Rows 2 and 3 equal row 1 (the last on-screen row) cell-by-cell
// across all 4 columns.
for r in 2..4usize {
for c in 0..4usize {
assert_eq!(cm[r * 4 + c], cm[4 + c]);
}
}
}
/// §5.11.49 — combined border-fill: 2x2 on-screen inside a 4x4
/// block exercises both the right-edge and the bottom-band paths.
/// The bottom band (rows 2..4) must mirror row 1 across columns
/// 0..4 (including the right-edge replicated cells).
#[test]
fn palette_tokens_plane_combined_border_fill_corner_block() {
let bytes = [0xC0u8, 0x00u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 16];
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
4,
4,
2,
2,
1,
&mut cm,
4,
)
.unwrap();
// Right edge.
for r in 0..2usize {
assert_eq!(cm[r * 4 + 2], cm[r * 4 + 1]);
assert_eq!(cm[r * 4 + 3], cm[r * 4 + 1]);
}
// Bottom band — row 1 across the whole width gets replicated.
for r in 2..4usize {
for c in 0..4usize {
assert_eq!(cm[r * 4 + c], cm[4 + c]);
}
}
}
/// §5.11.49 — sweeping every spec-realisable `(onscreen_w,
/// onscreen_h)` shape inside a 4x4 / size-2 palette walk: the
/// (0,0) seed is preserved, every cell in `0..block_h * stride`
/// holds a value `< palette_size`, and no decoder-side error is
/// raised. Confirms the diagonal-walk index arithmetic holds for
/// rectangular shapes (W != H).
#[test]
fn palette_tokens_plane_rectangular_onscreen_shapes_well_formed() {
for ow in 1..=4usize {
for oh in 1..=4usize {
// Each call gets a fresh decoder; bytes are arbitrary
// but enough for the largest 4x4 walk to consume.
let bytes = [0x55u8, 0xAAu8, 0x55u8, 0xAAu8, 0x55u8, 0xAAu8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 6, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0xFEu8; 16];
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
4,
4,
ow,
oh,
1,
&mut cm,
4,
)
.unwrap();
assert_eq!(cm[0], 1, "(0,0) seed preserved");
for &v in cm.iter() {
assert!((v as usize) < 2);
}
}
}
}
/// §5.11.49 — the UV plane runs the same diagonal walk against
/// the `palette_uv_color_cdf` family. With a size-2 / no-CDF-update
/// decoder and the same harness as the Y test, the UV path must
/// behave identically (the walker's only Y/UV difference is the
/// cdf selector). We further confirm the §8.3 adaptation _did_
/// occur on the UV row when `disable_cdf_update == false`.
#[test]
fn palette_tokens_plane_uv_plane_adapts_uv_cdf_family() {
let bytes = [0xC0u8, 0x00u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, false).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let before_y2 = tile.palette_y_color_cdf(2, 0).unwrap().to_vec();
let before_uv2 = tile.palette_uv_color_cdf(2, 0).unwrap().to_vec();
let mut cm = [0u8; 4];
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Uv,
2,
2,
2,
2,
2,
0,
&mut cm,
2,
)
.unwrap();
// (0,0) seed.
assert_eq!(cm[0], 0);
for &v in cm.iter() {
assert!((v as usize) < 2);
}
// The Y family must remain untouched (Uv plane only adapts the
// UV cdf family); some UV row must have ticked.
assert_eq!(
tile.palette_y_color_cdf(2, 0).unwrap(),
before_y2.as_slice()
);
let mut any_changed = false;
for c in 0..PALETTE_COLOR_CONTEXTS {
if tile.palette_uv_color_cdf(2, c).unwrap() != before_uv2.as_slice() {
any_changed = true;
break;
}
}
// The walker reads 3 symbols total (i=1: j=1,0; i=2: j=1) so
// at least one of the size-2 UV rows must have been adapted.
let _ = (before_uv2, any_changed);
assert!(any_changed, "§8.3 CDF update must touch the UV cdf family");
}
/// §5.11.49 — chroma-subsampled UV path: the spec adjusts
/// `blockWidth`, `blockHeight`, `onscreenWidth`, `onscreenHeight`
/// by `subsampling_{x,y}`, then bumps the width or height by 2 if
/// it dropped below 4. The walker itself sees only the already-adjusted
/// values, so a 4x4 / on-screen-4x4 / palette-2 UV call must behave
/// equivalently to the corresponding Y call. We check that here:
/// both produce a 16-cell colour map where every cell is `< 2`.
#[test]
fn palette_tokens_plane_uv_chroma_subsampled_shape_well_formed() {
let bytes_y = [0x55u8, 0xAAu8, 0x55u8, 0xAAu8];
let mut dec_y = SymbolDecoder::init_symbol(&bytes_y, 4, true).unwrap();
let mut tile_y = TileCdfContext::new_from_defaults();
let mut cm_y = [0xFFu8; 16];
palette_tokens_plane(
&mut dec_y,
&mut tile_y,
PalettePlane::Y,
2,
4,
4,
4,
4,
0,
&mut cm_y,
4,
)
.unwrap();
let bytes_uv = [0x55u8, 0xAAu8, 0x55u8, 0xAAu8];
let mut dec_uv = SymbolDecoder::init_symbol(&bytes_uv, 4, true).unwrap();
let mut tile_uv = TileCdfContext::new_from_defaults();
let mut cm_uv = [0xFFu8; 16];
palette_tokens_plane(
&mut dec_uv,
&mut tile_uv,
PalettePlane::Uv,
2,
4,
4,
4,
4,
0,
&mut cm_uv,
4,
)
.unwrap();
// Both must produce 16 in-range cells. (Values may differ —
// the size-2 UV cdf is not byte-equal to the size-2 Y cdf.)
for r in 0..4usize {
for c in 0..4usize {
assert!((cm_y[r * 4 + c] as usize) < 2);
assert!((cm_uv[r * 4 + c] as usize) < 2);
}
}
}
/// §5.11.49 — confirm the §5.11.50 boundary guards drive the
/// walker correctly at the (0, c) and (r, 0) edges. With every
/// cell so far equal to 0 (the §5.11.50 partial sort leaves
/// ColorOrder = [0, 1] in this case), the decoded
/// `palette_color_idx_y` must map through `ColorOrder[ idx ]` and
/// the result must always be in `0..2`.
#[test]
fn palette_tokens_plane_edge_positions_use_color_order_remap() {
let bytes = [0x00u8, 0x00u8, 0x00u8, 0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 4];
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
2,
2,
2,
2,
0,
&mut cm,
2,
)
.unwrap();
// (0,0) was seeded to 0. The three decoded cells must be in 0..2.
assert_eq!(cm[0], 0);
for &v in &cm[1..] {
assert!((v as usize) < 2);
}
}
/// §5.11.49 — degenerate 1-wide block (block_width == 1,
/// onscreen_width == 1). The diagonal walk `i < 1 + onscreen_h - 1`
/// reduces to a single column; only the (0,0) seed is written for
/// onscreen_h == 1, otherwise the rest of the column is decoded
/// via the same diagonal arithmetic. Confirms the walker handles
/// the bounds without underflow.
#[test]
fn palette_tokens_plane_degenerate_one_wide_shape() {
// 1x1 on-screen / 1x1 block: only (0,0) seed; no read_symbol
// and no border-fill.
let bytes = [0xFFu8, 0xFFu8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 2, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0xAAu8; 1];
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
1,
1,
1,
1,
1,
&mut cm,
1,
)
.unwrap();
assert_eq!(cm[0], 1);
// 1x3 on-screen / 1x3 block: walk runs i=1,2 each with j=0.
let mut dec = SymbolDecoder::init_symbol(&bytes, 2, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0xAAu8; 3];
palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
2,
1,
3,
1,
3,
0,
&mut cm,
1,
)
.unwrap();
assert_eq!(cm[0], 0);
for &v in &cm[1..] {
assert!((v as usize) < 2);
}
}
/// §5.11.49 — `read_symbol` byte-underflow propagates as
/// `Error::UnexpectedEnd` (the §8.2 path) rather than as one of
/// the walker's caller-bug variants. We provoke this by giving
/// the symbol decoder a tiny `sz` (1 byte) so the renormaliser
/// hits the `SymbolMaxBits` floor after a handful of reads and
/// then runs out of bytes.
#[test]
fn palette_tokens_plane_propagates_symbol_decoder_underflow() {
// A pathological case: a max-size palette walk against a
// 1-byte bitstream. The §8.2 reader cannot service the many
// renormalisation reads the walk demands.
let bytes = [0x00u8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 1, true).unwrap();
let mut tile = TileCdfContext::new_from_defaults();
let mut cm = [0u8; 64];
let r = palette_tokens_plane(
&mut dec,
&mut tile,
PalettePlane::Y,
8,
8,
8,
8,
8,
0,
&mut cm,
8,
);
// Either underflow (UnexpectedEnd) is raised, or — if the
// arithmetic happens to track padding zeros through to the
// end — the walker completes; both are §8.2-conformant. The
// important invariant is that the walker never produces an
// InvalidPaletteWalkArgs on this input.
if let Err(e) = r {
assert!(matches!(e, crate::Error::UnexpectedEnd));
}
}
// ---- Round 150 — Partition_Subsize table tests ---------------
/// §3 enumeration pin: every staged `BLOCK_*` constant matches
/// its av1-spec p.171-172 ordinal exactly. Catches a typo in any
/// of the 19 new constants r150 added alongside the table.
#[test]
fn block_size_constants_match_spec_ordinals() {
assert_eq!(BLOCK_4X4, 0);
assert_eq!(BLOCK_4X8, 1);
assert_eq!(BLOCK_8X4, 2);
assert_eq!(BLOCK_8X8, 3);
assert_eq!(BLOCK_8X16, 4);
assert_eq!(BLOCK_16X8, 5);
assert_eq!(BLOCK_16X16, 6);
assert_eq!(BLOCK_16X32, 7);
assert_eq!(BLOCK_32X16, 8);
assert_eq!(BLOCK_32X32, 9);
assert_eq!(BLOCK_32X64, 10);
assert_eq!(BLOCK_64X32, 11);
assert_eq!(BLOCK_64X64, 12);
assert_eq!(BLOCK_64X128, 13);
assert_eq!(BLOCK_128X64, 14);
assert_eq!(BLOCK_128X128, 15);
assert_eq!(BLOCK_4X16, 16);
assert_eq!(BLOCK_16X4, 17);
assert_eq!(BLOCK_8X32, 18);
assert_eq!(BLOCK_32X8, 19);
assert_eq!(BLOCK_16X64, 20);
assert_eq!(BLOCK_64X16, 21);
assert_eq!(BLOCK_INVALID, 22);
// BLOCK_INVALID sits one past the last valid BLOCK_SIZES
// ordinal, matching the §3 constant-table note (p.7).
assert_eq!(BLOCK_INVALID, BLOCK_SIZES);
}
/// `PARTITION_TYPES_TOTAL` equals `PARTITION_VERT_4 + 1` (the
/// highest §6.10.4 partition ordinal). Catches a future addition
/// to the partition enumeration that forgets to lift the table's
/// first dimension.
#[test]
fn partition_types_total_matches_highest_partition() {
assert_eq!(PARTITION_TYPES_TOTAL, PARTITION_VERT_4 + 1);
assert_eq!(PARTITION_TYPES_TOTAL, 10);
}
/// Table shape: `[10][BLOCK_SIZES]`. Pins the literal dimensions
/// against the spec p.402 declaration
/// `Partition_Subsize[ 10 ][ BLOCK_SIZES ]`.
#[test]
fn partition_subsize_table_shape() {
assert_eq!(PARTITION_SUBSIZE.len(), 10);
for (i, row) in PARTITION_SUBSIZE.iter().enumerate() {
assert_eq!(
row.len(),
BLOCK_SIZES,
"PARTITION_SUBSIZE row {i} has wrong length",
);
}
}
/// `PARTITION_NONE` is the identity on every square `bSize`: the
/// block is left intact. Pins the row-0 diagonal entries spec
/// p.402.
#[test]
fn partition_none_is_identity_on_squares() {
for &b in &[
BLOCK_4X4,
BLOCK_8X8,
BLOCK_16X16,
BLOCK_32X32,
BLOCK_64X64,
BLOCK_128X128,
] {
assert_eq!(partition_subsize(PARTITION_NONE, b), Some(b));
}
}
/// `PARTITION_HORZ` halves a square `bSize` vertically; pins
/// every reachable entry on row 1 of spec p.402.
#[test]
fn partition_horz_halves_height_for_squares() {
let pairs = [
(BLOCK_8X8, BLOCK_8X4),
(BLOCK_16X16, BLOCK_16X8),
(BLOCK_32X32, BLOCK_32X16),
(BLOCK_64X64, BLOCK_64X32),
(BLOCK_128X128, BLOCK_128X64),
];
for (b, expect) in pairs {
assert_eq!(partition_subsize(PARTITION_HORZ, b), Some(expect));
}
// BLOCK_4X4 cannot split — the row-1 entry is BLOCK_INVALID.
assert_eq!(partition_subsize(PARTITION_HORZ, BLOCK_4X4), None);
}
/// `PARTITION_VERT` halves a square `bSize` horizontally;
/// mirror of the HORZ test against row 2 of spec p.402.
#[test]
fn partition_vert_halves_width_for_squares() {
let pairs = [
(BLOCK_8X8, BLOCK_4X8),
(BLOCK_16X16, BLOCK_8X16),
(BLOCK_32X32, BLOCK_16X32),
(BLOCK_64X64, BLOCK_32X64),
(BLOCK_128X128, BLOCK_64X128),
];
for (b, expect) in pairs {
assert_eq!(partition_subsize(PARTITION_VERT, b), Some(expect));
}
assert_eq!(partition_subsize(PARTITION_VERT, BLOCK_4X4), None);
}
/// `PARTITION_SPLIT` halves both dimensions, yielding a smaller
/// square block (row 3 of spec p.402). The §5.11.4 walk reads
/// this row both as `subSize` (for the `_SPLIT` partition) and
/// as `splitSize` (for the `_HORZ_A` / `_HORZ_B` / `_VERT_A` /
/// `_VERT_B` partitions).
#[test]
fn partition_split_halves_both_dimensions() {
let pairs = [
(BLOCK_8X8, BLOCK_4X4),
(BLOCK_16X16, BLOCK_8X8),
(BLOCK_32X32, BLOCK_16X16),
(BLOCK_64X64, BLOCK_32X32),
(BLOCK_128X128, BLOCK_64X64),
];
for (b, expect) in pairs {
assert_eq!(partition_subsize(PARTITION_SPLIT, b), Some(expect));
}
assert_eq!(partition_subsize(PARTITION_SPLIT, BLOCK_4X4), None);
}
/// `PARTITION_HORZ_A` and `PARTITION_HORZ_B` share the row-1
/// (HORZ) subSize values: the §5.11.4 walk distinguishes
/// `_A` from `_B` by the order it issues `decode_block` calls
/// (top is the small split for `_A`, bottom for `_B`). Pinning
/// the row equality enforces the av1-spec p.402-403 invariant
/// the rest of the decode path will rely on.
#[test]
fn partition_horz_a_and_b_share_horz_subsize() {
let horz = &PARTITION_SUBSIZE[PARTITION_HORZ];
let horz_a = &PARTITION_SUBSIZE[PARTITION_HORZ_A];
let horz_b = &PARTITION_SUBSIZE[PARTITION_HORZ_B];
for (b, ((&a, &h), &bb)) in horz_a
.iter()
.zip(horz.iter())
.zip(horz_b.iter())
.enumerate()
{
assert_eq!(a, h, "row HORZ_A/HORZ differ at bSize {b}");
assert_eq!(bb, h, "row HORZ_B/HORZ differ at bSize {b}");
}
}
/// Symmetric to `partition_horz_a_and_b_share_horz_subsize`:
/// row 2 (VERT) equals rows 6 (VERT_A) and 7 (VERT_B).
#[test]
fn partition_vert_a_and_b_share_vert_subsize() {
let vert = &PARTITION_SUBSIZE[PARTITION_VERT];
let vert_a = &PARTITION_SUBSIZE[PARTITION_VERT_A];
let vert_b = &PARTITION_SUBSIZE[PARTITION_VERT_B];
for (b, ((&a, &v), &bb)) in vert_a
.iter()
.zip(vert.iter())
.zip(vert_b.iter())
.enumerate()
{
assert_eq!(a, v, "row VERT_A/VERT differ at bSize {b}");
assert_eq!(bb, v, "row VERT_B/VERT differ at bSize {b}");
}
}
/// `PARTITION_HORZ_4` and `PARTITION_VERT_4` only resolve for the
/// middle square sizes (BLOCK_16X16 / 32X32 / 64X64). Pin the
/// quarter-split entries and confirm the BLOCK_128X128 column is
/// `BLOCK_INVALID` for both (the partition_w128 cdf has no `_4`
/// slot — round-145 `split_or_horz` derivation already drops
/// the `_4` term at BLOCK_128X128, so a `None` at this lookup
/// matches that gate).
#[test]
fn partition_horz4_and_vert4_quarter_splits() {
let horz4 = [
(BLOCK_16X16, BLOCK_16X4),
(BLOCK_32X32, BLOCK_32X8),
(BLOCK_64X64, BLOCK_64X16),
];
for (b, expect) in horz4 {
assert_eq!(partition_subsize(PARTITION_HORZ_4, b), Some(expect));
}
let vert4 = [
(BLOCK_16X16, BLOCK_4X16),
(BLOCK_32X32, BLOCK_8X32),
(BLOCK_64X64, BLOCK_16X64),
];
for (b, expect) in vert4 {
assert_eq!(partition_subsize(PARTITION_VERT_4, b), Some(expect));
}
// Quarter-splits drop at BLOCK_128X128 (no BLOCK_128X1 or
// BLOCK_1X128 extended block exists).
assert_eq!(partition_subsize(PARTITION_HORZ_4, BLOCK_128X128), None);
assert_eq!(partition_subsize(PARTITION_VERT_4, BLOCK_128X128), None);
// And at the smallest square BLOCK_8X8 (no extended 8x2).
assert_eq!(partition_subsize(PARTITION_HORZ_4, BLOCK_8X8), None);
assert_eq!(partition_subsize(PARTITION_VERT_4, BLOCK_8X8), None);
}
/// `BLOCK_4X4` is splittable only by `PARTITION_NONE`. Every
/// other partition ordinal must return `None`. Pins the
/// av1-spec p.402 column-0 BLOCK_INVALID-except-row-0 layout.
#[test]
fn block_4x4_only_partition_none_resolves() {
assert_eq!(
partition_subsize(PARTITION_NONE, BLOCK_4X4),
Some(BLOCK_4X4)
);
for p in 1..PARTITION_TYPES_TOTAL {
assert_eq!(
partition_subsize(p, BLOCK_4X4),
None,
"partition {p} should not resolve for BLOCK_4X4",
);
}
}
/// Rectangular `bSize` columns are entirely `BLOCK_INVALID`
/// across every partition row. Pins the av1-spec p.401 note
/// "The table will never get accessed for rectangular block
/// sizes": [`partition_subsize`] must return `None` for every
/// rectangular `bSize` regardless of `partition`.
#[test]
fn rectangular_bsize_columns_are_invalid() {
let rectangular = [
BLOCK_4X8,
BLOCK_8X4,
BLOCK_8X16,
BLOCK_16X8,
BLOCK_16X32,
BLOCK_32X16,
BLOCK_32X64,
BLOCK_64X32,
BLOCK_64X128,
BLOCK_128X64,
BLOCK_4X16,
BLOCK_16X4,
BLOCK_8X32,
BLOCK_32X8,
BLOCK_16X64,
BLOCK_64X16,
];
for &b in &rectangular {
for p in 0..PARTITION_TYPES_TOTAL {
assert_eq!(
partition_subsize(p, b),
None,
"partition {p} on rectangular bSize {b} should be invalid",
);
}
}
}
/// Whenever [`partition_subsize`] returns `Some(child)`, the
/// child must be a valid `BLOCK_SIZES` ordinal (`< 22`) — never
/// the sentinel and never out of range. Exhaustive check across
/// every `(partition, b_size)` pair.
#[test]
fn every_resolved_subsize_is_in_range() {
for p in 0..PARTITION_TYPES_TOTAL {
for b in 0..BLOCK_SIZES {
if let Some(child) = partition_subsize(p, b) {
assert!(
child < BLOCK_SIZES,
"partition {p} bSize {b} resolved to out-of-range {child}",
);
assert_ne!(
child, BLOCK_INVALID,
"partition {p} bSize {b} resolved to BLOCK_INVALID",
);
}
}
}
}
/// Out-of-range `partition` or `b_size` returns `None` (the
/// caller-bug guard), not a panic. Catches a future regression
/// that swaps the guard with a `[]` index on an attacker-shaped
/// (partition, b_size) tuple from the bitstream.
#[test]
fn out_of_range_indices_return_none() {
assert_eq!(partition_subsize(PARTITION_TYPES_TOTAL, BLOCK_8X8), None);
assert_eq!(partition_subsize(99, BLOCK_8X8), None);
assert_eq!(partition_subsize(PARTITION_NONE, BLOCK_SIZES), None);
assert_eq!(partition_subsize(PARTITION_NONE, BLOCK_INVALID), None);
assert_eq!(partition_subsize(PARTITION_NONE, 99), None);
// Both out-of-range together — still None.
assert_eq!(partition_subsize(99, 99), None);
}
/// §5.11.4 invariant: the resolved sub-block area is at most
/// half the parent block's area for splitting partitions
/// (HORZ / VERT / HORZ_A / HORZ_B / VERT_A / VERT_B) and at
/// most a quarter for SPLIT / HORZ_4 / VERT_4. Encodes the
/// "subdivision shrinks the block" invariant the decode walk
/// relies on to terminate.
#[test]
fn subdivision_strictly_shrinks_block_area() {
let halving = [
PARTITION_HORZ,
PARTITION_VERT,
PARTITION_HORZ_A,
PARTITION_HORZ_B,
PARTITION_VERT_A,
PARTITION_VERT_B,
];
let quartering = [PARTITION_SPLIT, PARTITION_HORZ_4, PARTITION_VERT_4];
for b in 0..BLOCK_SIZES {
let parent_area = block_width(b) * block_height(b);
for &p in &halving {
if let Some(child) = partition_subsize(p, b) {
let child_area = block_width(child) * block_height(child);
assert!(
child_area * 2 <= parent_area,
"partition {p} bSize {b}: child area {child_area} > parent/2 {}",
parent_area / 2,
);
}
}
for &p in &quartering {
if let Some(child) = partition_subsize(p, b) {
let child_area = block_width(child) * block_height(child);
assert!(
child_area * 4 <= parent_area,
"partition {p} bSize {b}: child area {child_area} > parent/4 {}",
parent_area / 4,
);
}
}
}
}
/// §5.11.4 data-flow pin: the `subSize` / `splitSize` pair the
/// `decode_partition` body reads side by side for every
/// `_HORZ_A` / `_HORZ_B` partition is well-formed — both rows
/// resolve for the same `bSize` window (the larger `subSize`
/// from row HORZ matches the smaller `splitSize` from row
/// SPLIT halved one further axis).
#[test]
fn horz_a_b_subsize_and_splitsize_pair_resolves() {
for b in [
BLOCK_8X8,
BLOCK_16X16,
BLOCK_32X32,
BLOCK_64X64,
BLOCK_128X128,
] {
let sub = partition_subsize(PARTITION_HORZ_A, b).unwrap();
let split = partition_subsize(PARTITION_SPLIT, b).unwrap();
// subSize is the BLOCK_HORZ child (e.g. BLOCK_16X8 for
// BLOCK_16X16); splitSize is the BLOCK_SPLIT child
// (e.g. BLOCK_8X8). Sub-block width == parent width,
// splitSize width == parent width / 2.
assert_eq!(block_width(sub), block_width(b));
assert_eq!(block_height(sub), block_height(b) / 2);
assert_eq!(block_width(split), block_width(b) / 2);
assert_eq!(block_height(split), block_height(b) / 2);
}
}
// -----------------------------------------------------------------
// Round 151 — §5.11.4 decode_partition walker tests.
// -----------------------------------------------------------------
/// Helper: force one of the §8.3.2 partition CDFs (an 11-entry
/// W16/W32/W64 or 10-entry W128 row) to select `symbol` on the
/// next [`SymbolDecoder::read_symbol`] against a fresh decoder
/// initialised with all-zero bytes (which seeds `SymbolValue =
/// 32767`). The rigging puts `1 << 15` cumulative probability on
/// every index `>= symbol` and `0` on every index `< symbol`,
/// so the §8.2.6 loop breaks at iteration `symbol` (`f = 0`,
/// `cur = 4 * (N - symbol - 1) <= 36 << SymbolValue = 32767`).
fn force_partition_cdf_w(symbol: usize, n: usize) -> Vec<u16> {
let mut row = vec![0u16; n + 1];
for entry in row.iter_mut().take(n).skip(symbol) {
*entry = 1 << 15;
}
// Counter slot stays 0.
row
}
/// `TileGeometry::is_inside` boundary checks against the §5.11.51
/// `MiColStart <= candidateC < MiColEnd && MiRowStart <=
/// candidateR < MiRowEnd` rule.
#[test]
fn tile_geometry_is_inside_boundaries() {
let g = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
assert!(g.is_inside(0, 0));
assert!(g.is_inside(31, 31));
assert!(!g.is_inside(-1, 0));
assert!(!g.is_inside(0, -1));
assert!(!g.is_inside(32, 0));
assert!(!g.is_inside(0, 32));
// Non-zero start (subsequent tile).
let g2 = TileGeometry {
mi_row_start: 8,
mi_row_end: 24,
mi_col_start: 16,
mi_col_end: 48,
};
assert!(!g2.is_inside(7, 16));
assert!(g2.is_inside(8, 16));
assert!(g2.is_inside(23, 47));
assert!(!g2.is_inside(24, 47));
assert!(!g2.is_inside(8, 48));
}
/// §5.11.4 first conditional: `r >= MiRows || c >= MiCols`
/// returns 0 — the walker emits no leaves and reads no symbol.
#[test]
fn decode_partition_early_return_at_frame_edge() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// r >= MiRows
walker
.decode_partition(&mut dec, &mut cdfs, 16, 0, BLOCK_16X16)
.unwrap();
assert!(walker.blocks().is_empty());
// c >= MiCols
walker
.decode_partition(&mut dec, &mut cdfs, 0, 16, BLOCK_16X16)
.unwrap();
assert!(walker.blocks().is_empty());
// Far past the bounds.
walker
.decode_partition(&mut dec, &mut cdfs, 64, 64, BLOCK_64X64)
.unwrap();
assert!(walker.blocks().is_empty());
}
/// `bSize < BLOCK_8X8` short-circuit at BLOCK_4X4: `partition =
/// PARTITION_NONE`, single BLOCK_4X4 leaf emitted, no symbol read.
/// (BLOCK_4X8 / BLOCK_8X4 are rectangular and therefore
/// unreachable as `bSize` arguments — the §5.11.4 recursion only
/// emits square `subSize` values per the PARTITION_SPLIT row of
/// [`PARTITION_SUBSIZE`], so the walker never starts a recursion
/// with a rectangular `bSize`.)
#[test]
fn decode_partition_small_block_emits_single_leaf() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Empty byte slice — any read would panic / underflow, so a
// successful walk here proves no symbol was consumed.
let bytes = [0u8; 1];
let mut dec = SymbolDecoder::init_symbol(&bytes, 1, true).unwrap();
let pos_before = dec.position();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_4X4)
.unwrap();
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read for bSize < BLOCK_8X8"
);
assert_eq!(walker.blocks().len(), 1);
assert_eq!(walker.blocks()[0].sub_size, BLOCK_4X4);
assert_eq!(walker.blocks()[0].mi_row, 0);
assert_eq!(walker.blocks()[0].mi_col, 0);
}
/// §6.10.4 `MiSizes[]` grid-fill invariant: every cell of the
/// leaf's `bh4 * bw4` footprint carries `sub_size`. Drives a
/// single BLOCK_4X4 leaf at `(4, 8)` via the `bSize < BLOCK_8X8`
/// short-circuit (no symbol read), then checks that exactly the
/// one-cell footprint `MiSizes[4][8]` carries `BLOCK_4X4` and
/// surrounding cells stay `BLOCK_INVALID`.
#[test]
fn decode_block_fills_mi_sizes_footprint() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 1];
let mut dec = SymbolDecoder::init_symbol(&bytes, 1, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 4, 8, BLOCK_4X4)
.unwrap();
// BLOCK_4X4: bw4=1, bh4=1. So MiSizes[4][8] = BLOCK_4X4.
assert_eq!(walker.mi_sizes()[4 * 32 + 8], BLOCK_4X4);
// Outside footprint is still BLOCK_INVALID.
assert_eq!(walker.mi_sizes()[3 * 32 + 8], BLOCK_INVALID);
assert_eq!(walker.mi_sizes()[5 * 32 + 8], BLOCK_INVALID);
assert_eq!(walker.mi_sizes()[4 * 32 + 7], BLOCK_INVALID);
assert_eq!(walker.mi_sizes()[4 * 32 + 9], BLOCK_INVALID);
}
/// §5.11.4 `!hasRows && !hasCols` fall-through: PARTITION_SPLIT
/// is selected without reading a symbol, then the four quadrants
/// are all out-of-bounds (the early-return path). Net result:
/// 0 leaves emitted, 0 bits consumed.
///
/// Constructed with a frame whose MiRows / MiCols are exactly the
/// block's `halfBlock4x4` (so `r + halfBlock4x4 == MiRows`,
/// `c + halfBlock4x4 == MiCols` ⇒ `hasRows == hasCols == false`),
/// driven from `(0, 0)` so the four split children land at
/// `(0, half)`, `(half, 0)`, `(half, half)` — all hit the
/// `r >= MiRows || c >= MiCols` early return.
#[test]
fn decode_partition_corner_split_fallback_no_symbol() {
// BLOCK_16X16: halfBlock4x4 = 2. Frame is 2 mi units.
let mi_rows = NUM_4X4_BLOCKS_HIGH[BLOCK_16X16] as u32 / 2;
let mi_cols = NUM_4X4_BLOCKS_WIDE[BLOCK_16X16] as u32 / 2;
assert_eq!(mi_rows, 2);
assert_eq!(mi_cols, 2);
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
};
let mut walker = PartitionWalker::new(mi_rows, mi_cols, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 1];
let mut dec = SymbolDecoder::init_symbol(&bytes, 1, true).unwrap();
let pos_before = dec.position();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(
dec.position(),
pos_before,
"neither partition nor split_or_* symbol should be read"
);
// SPLIT recurses on BLOCK_8X8 — the 4 children at (0,0),
// (0,1), (1,0), (1,1). Each one has bSize=BLOCK_8X8,
// halfBlock4x4=1, hasRows = (r+1 < 2), hasCols = (c+1 < 2).
// (0,0): hasRows=true, hasCols=true → reads a partition
// symbol against the default CDF.
// The walk would consume some bytes for the inner recursion,
// so we can't assert pos_before == pos_after for the full
// walk. Re-issue with a fresh decoder and assert no PANIC, and
// the walker emits a non-empty leaf list (the inner reads
// against defaults will resolve to *some* partition).
let mut walker2 = PartitionWalker::new(mi_rows, mi_cols, geom).unwrap();
let mut cdfs2 = TileCdfContext::new_from_defaults();
let mut dec2 = SymbolDecoder::init_symbol(&[0u8; 16], 16, true).unwrap();
walker2
.decode_partition(&mut dec2, &mut cdfs2, 0, 0, BLOCK_16X16)
.unwrap();
// Some leaves should have been emitted by the 4 inner BLOCK_8X8
// recursions.
assert!(
!walker2.blocks().is_empty(),
"inner BLOCK_8X8 children should emit leaves"
);
}
/// Forced PARTITION_NONE at BLOCK_16X16: single leaf, sub_size ==
/// BLOCK_16X16.
#[test]
fn decode_partition_forced_none_emits_single_leaf() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Override partition_w16[ctx=0] to force PARTITION_NONE (idx 0).
let rigged = force_partition_cdf_w(PARTITION_NONE, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 1);
assert_eq!(walker.blocks()[0].sub_size, BLOCK_16X16);
assert_eq!(walker.blocks()[0].mi_row, 0);
assert_eq!(walker.blocks()[0].mi_col, 0);
}
/// Forced PARTITION_HORZ at BLOCK_16X16: 2 leaves of BLOCK_16X8
/// stacked vertically.
#[test]
fn decode_partition_forced_horz_emits_two_leaves() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_partition_cdf_w(PARTITION_HORZ, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 2);
assert_eq!(
walker.blocks()[0],
DecodedBlockRecord {
mi_row: 0,
mi_col: 0,
sub_size: BLOCK_16X8
}
);
// halfBlock4x4 for BLOCK_16X16 = 4 / 2 = 2.
assert_eq!(
walker.blocks()[1],
DecodedBlockRecord {
mi_row: 2,
mi_col: 0,
sub_size: BLOCK_16X8
}
);
}
/// Forced PARTITION_VERT at BLOCK_16X16: 2 leaves of BLOCK_8X16.
#[test]
fn decode_partition_forced_vert_emits_two_leaves() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_partition_cdf_w(PARTITION_VERT, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 2);
assert_eq!(walker.blocks()[0].sub_size, BLOCK_8X16);
assert_eq!(walker.blocks()[1].sub_size, BLOCK_8X16);
assert_eq!(walker.blocks()[0].mi_col, 0);
assert_eq!(walker.blocks()[1].mi_col, 2);
}
/// Forced PARTITION_HORZ_4 at BLOCK_16X16: 4 leaves of BLOCK_16X4.
#[test]
fn decode_partition_forced_horz_4_emits_four_leaves() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_partition_cdf_w(PARTITION_HORZ_4, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 4);
// quarterBlock4x4 for BLOCK_16X16 = 4 / 2 / 2 = 1.
for (i, blk) in walker.blocks().iter().enumerate() {
assert_eq!(blk.sub_size, BLOCK_16X4);
assert_eq!(blk.mi_row, i as u32);
assert_eq!(blk.mi_col, 0);
}
}
/// Forced PARTITION_VERT_4 at BLOCK_16X16: 4 leaves of BLOCK_4X16.
#[test]
fn decode_partition_forced_vert_4_emits_four_leaves() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_partition_cdf_w(PARTITION_VERT_4, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 4);
for (i, blk) in walker.blocks().iter().enumerate() {
assert_eq!(blk.sub_size, BLOCK_4X16);
assert_eq!(blk.mi_row, 0);
assert_eq!(blk.mi_col, i as u32);
}
}
/// Forced PARTITION_HORZ_A at BLOCK_16X16: 3 leaves: 2 splitSize
/// (BLOCK_8X8) + 1 subSize (BLOCK_16X8).
#[test]
fn decode_partition_forced_horz_a_emits_three_leaves() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_partition_cdf_w(PARTITION_HORZ_A, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 3);
// HORZ_A: subSize = BLOCK_16X8, splitSize = BLOCK_8X8.
// Decode order: (r,c) splitSize, (r,c+half) splitSize,
// (r+half,c) subSize.
assert_eq!(walker.blocks()[0].sub_size, BLOCK_8X8);
assert_eq!(walker.blocks()[1].sub_size, BLOCK_8X8);
assert_eq!(walker.blocks()[2].sub_size, BLOCK_16X8);
assert_eq!(walker.blocks()[0].mi_col, 0);
assert_eq!(walker.blocks()[1].mi_col, 2);
assert_eq!(walker.blocks()[2].mi_row, 2);
}
/// Forced PARTITION_VERT_B at BLOCK_16X16: 3 leaves: 1 subSize
/// (BLOCK_8X16) + 2 splitSize (BLOCK_8X8).
#[test]
fn decode_partition_forced_vert_b_emits_three_leaves() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_partition_cdf_w(PARTITION_VERT_B, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 3);
// VERT_B: subSize = BLOCK_8X16, splitSize = BLOCK_8X8.
// Decode order: (r,c) subSize, (r,c+half) splitSize,
// (r+half,c+half) splitSize.
assert_eq!(walker.blocks()[0].sub_size, BLOCK_8X16);
assert_eq!(walker.blocks()[1].sub_size, BLOCK_8X8);
assert_eq!(walker.blocks()[2].sub_size, BLOCK_8X8);
assert_eq!(walker.blocks()[1].mi_col, 2);
assert_eq!(walker.blocks()[2].mi_row, 2);
assert_eq!(walker.blocks()[2].mi_col, 2);
}
/// Forced PARTITION_SPLIT at BLOCK_16X16 with all sub-BLOCK_8X8
/// children forced to PARTITION_NONE: 4 leaves of BLOCK_8X8 in
/// the spec's documented quadrant order.
#[test]
fn decode_partition_forced_split_recurses_to_4_leaves() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Top-level BLOCK_16X16 → PARTITION_SPLIT.
let rigged_w16 = force_partition_cdf_w(PARTITION_SPLIT, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged_w16);
}
// Each BLOCK_8X8 child → PARTITION_NONE (W8 row has 5 symbols).
let rigged_w8 = force_partition_cdf_w(PARTITION_NONE, 4);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w8[ctx_idx].copy_from_slice(&rigged_w8);
}
let bytes = [0u8; 64];
let mut dec = SymbolDecoder::init_symbol(&bytes, 64, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(walker.blocks().len(), 4);
for blk in walker.blocks() {
assert_eq!(blk.sub_size, BLOCK_8X8);
}
// §5.11.4 quadrant order: (r,c), (r,c+half), (r+half,c),
// (r+half,c+half).
assert_eq!(walker.blocks()[0].mi_row, 0);
assert_eq!(walker.blocks()[0].mi_col, 0);
assert_eq!(walker.blocks()[1].mi_row, 0);
assert_eq!(walker.blocks()[1].mi_col, 2);
assert_eq!(walker.blocks()[2].mi_row, 2);
assert_eq!(walker.blocks()[2].mi_col, 0);
assert_eq!(walker.blocks()[3].mi_row, 2);
assert_eq!(walker.blocks()[3].mi_col, 2);
}
/// §6.10.4 grid-fill: after a forced PARTITION_HORZ at
/// BLOCK_16X16, every cell of the two BLOCK_16X8 leaves' `bh4 *
/// bw4` footprints carries `BLOCK_16X8`, and adjacent cells stay
/// `BLOCK_INVALID`.
#[test]
fn decode_partition_grid_fill_after_horz() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_partition_cdf_w(PARTITION_HORZ, 10);
for ctx_idx in 0..PARTITION_CONTEXTS {
cdfs.partition_w16[ctx_idx].copy_from_slice(&rigged);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
// BLOCK_16X8 footprint: bw4 = 4, bh4 = 2.
// Top leaf at (0,0): MiSizes[0..2][0..4] = BLOCK_16X8.
for r in 0..2 {
for c in 0..4 {
assert_eq!(
walker.mi_sizes()[r * 32 + c],
BLOCK_16X8,
"top leaf cell ({r},{c}) should be BLOCK_16X8"
);
}
}
// Bottom leaf at (2,0): MiSizes[2..4][0..4] = BLOCK_16X8.
for r in 2..4 {
for c in 0..4 {
assert_eq!(walker.mi_sizes()[r * 32 + c], BLOCK_16X8);
}
}
// Outside footprint stays BLOCK_INVALID.
assert_eq!(walker.mi_sizes()[4], BLOCK_INVALID);
assert_eq!(walker.mi_sizes()[4 * 32], BLOCK_INVALID);
}
/// Smoke test: a full walk against the §9.4 default CDFs at the
/// W128 superblock size completes without error and emits
/// well-formed leaves entirely inside the frame extent. The
/// partition tree picked depends on the byte stream, but the
/// invariants are independent.
#[test]
fn decode_partition_default_cdf_w128_smoke() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// 128 bytes of bitstream — plenty for the partition tree's
// worst-case depth.
let bytes = [0x55u8; 128];
let mut dec = SymbolDecoder::init_symbol(&bytes, 128, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_128X128)
.unwrap();
// At least one leaf must be emitted.
assert!(!walker.blocks().is_empty());
// Every leaf is in-frame and has a valid sub_size.
for blk in walker.blocks() {
assert!(blk.sub_size < BLOCK_SIZES);
assert!(blk.mi_row < 32);
assert!(blk.mi_col < 32);
}
// Total mi area of leaves <= frame mi area (no double-counting).
let total: u64 = walker
.blocks()
.iter()
.map(|b| {
(NUM_4X4_BLOCKS_WIDE[b.sub_size] as u64) * (NUM_4X4_BLOCKS_HIGH[b.sub_size] as u64)
})
.sum();
assert!(total <= (32 * 32) as u64);
}
/// §8.3.2 partition_ctx derivation: the walker computes `above`
/// and `left` against the §6.10.4 MiSizes grid populated by
/// previously-decoded leaves. At the (0, 0) superblock, both
/// AvailU and AvailL are false (the neighbour positions are
/// outside the tile), so partition_ctx returns 0 regardless of
/// neighbour state.
#[test]
fn partition_ctx_for_origin_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let walker = PartitionWalker::new(32, 32, geom).unwrap();
// For any bsl, (r=0, c=0) -> AvailU=false, AvailL=false -> ctx=0.
for bsl in 1..=5 {
assert_eq!(walker.partition_ctx_for(0, 0, bsl), 0);
}
}
/// `partition_ctx_for` after a leaf decode: if a wider neighbour
/// is present, the §8.3.2 `Mi_Width_Log2[ ... ] < bsl` test fails
/// (neighbour width >= bsl) and the corresponding bit stays
/// `false`.
#[test]
fn partition_ctx_for_wide_neighbour_drops_bit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
// Fill row 0 with BLOCK_16X16 (mi_width_log2 = 2) so the
// above-neighbour at (1, 0) is BLOCK_16X16.
walker.decode_block(0, 0, BLOCK_16X16);
// At (4, 0) with bsl = 2 (W16): AvailU=true (4-1=3 inside),
// above_neighbour at (3, 0) is BLOCK_INVALID (BLOCK_16X16
// footprint is only rows 0..4, cols 0..4 — (3,0) IS in the
// footprint, so MiSizes[3][0] = BLOCK_16X16). With
// mi_width_log2[BLOCK_16X16] = 2 and bsl = 2, the test
// (2 < 2) is false ⇒ above bit = false.
// AvailL=true at (4, 0) ⇒ left col is -1 ⇒ is_inside false ⇒
// left = false.
// Actually with mi_col -1, is_inside is false ⇒ AvailL=false ⇒
// left=false.
// ctx should be 0.
assert_eq!(walker.partition_ctx_for(4, 0, 2), 0);
// At (4, 4) with bsl = 1 (W8): AvailU=true (3 inside),
// above_neighbour at (3, 4) is BLOCK_INVALID (outside the
// 16x16 footprint at col 4). BLOCK_INVALID > BLOCK_SIZES, so
// the check `nb < BLOCK_SIZES` is false ⇒ above = false.
// AvailL=true (3 inside), left_neighbour at (4, 3) is also
// outside the BLOCK_16X16 footprint (rows 0..4) ⇒
// BLOCK_INVALID ⇒ left = false.
// ctx = 0.
assert_eq!(walker.partition_ctx_for(4, 4, 1), 0);
// At (4, 0) with bsl = 3 (W32): above_neighbour at (3, 0) is
// BLOCK_16X16 with mi_width_log2 = 2. 2 < 3 ⇒ above = true.
// left = false. ctx = (false as 2) + (true as 1) = 1.
assert_eq!(walker.partition_ctx_for(4, 0, 3), 1);
}
/// Construction sanity: PartitionWalker::new with absurd
/// dimensions returns None.
#[test]
fn partition_walker_construction_overflow() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: u32::MAX,
mi_col_start: 0,
mi_col_end: u32::MAX,
};
// mi_rows * mi_cols overflows usize on this platform.
assert!(PartitionWalker::new(u32::MAX, u32::MAX, geom).is_none());
}
/// `take_blocks` drains and resets the internal buffer.
#[test]
fn take_blocks_drains_buffer() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 1];
let mut dec = SymbolDecoder::init_symbol(&bytes, 1, true).unwrap();
walker
.decode_partition(&mut dec, &mut cdfs, 0, 0, BLOCK_4X4)
.unwrap();
assert_eq!(walker.blocks().len(), 1);
let drained = walker.take_blocks();
assert_eq!(drained.len(), 1);
assert!(walker.blocks().is_empty());
}
// -----------------------------------------------------------------
// Round 152 — §5.11.11 read_skip walker tests.
// -----------------------------------------------------------------
/// Helper: force a 2-symbol CDF (here `skip_cdf[ctx]` which has
/// length 3) to select `symbol` on the next `read_symbol`.
/// `Default_Skip_Cdf` rows have shape `[a, 1 << 15, 0]`; the §8.2.6
/// search returns the first symbol whose cumulative `cur > 0` is
/// strictly below `SymbolValue`. With a freshly-initialised
/// SymbolDecoder against zero bytes, `SymbolValue = 32767`; setting
/// `cdf[symbol..N] = [1 << 15; N - symbol]` and `cdf[..symbol] = 0`
/// puts `cur = 0` for every smaller symbol and `cur` near the top
/// for the target.
fn force_binary_cdf(symbol: u8) -> [u16; 3] {
match symbol {
0 => [1 << 15, 1 << 15, 0],
1 => [0, 1 << 15, 0],
_ => panic!("force_binary_cdf supports 0 or 1 only"),
}
}
/// §5.11.11 first branch: `SegIdPreSkip && seg_feature_active(
/// SEG_LVL_SKIP )` ⇒ `skip = 1`, no symbol read. The decoder
/// position must not advance.
#[test]
fn decode_skip_seg_short_circuit_no_symbol_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let skip = walker
.decode_skip(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, true)
.unwrap();
assert_eq!(skip, 1, "seg_skip_active ⇒ skip = 1");
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read on the seg short-circuit"
);
}
/// §5.11.11 else branch with a rigged CDF forcing symbol 0
/// (skip = 0): the decoder advances past the S() symbol and the
/// returned value matches.
#[test]
fn decode_skip_else_branch_returns_symbol_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force every Skip ctx row to select symbol 0.
let rigged = force_binary_cdf(0);
for ctx_idx in 0..SKIP_CONTEXTS {
cdfs.skip[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let skip = walker
.decode_skip(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
assert_eq!(skip, 0);
}
/// §5.11.11 else branch with a rigged CDF forcing symbol 1
/// (skip = 1): the returned value matches and the value is stamped
/// into the `Skips[]` grid over the block's `bh4 * bw4` footprint.
#[test]
fn decode_skip_else_branch_returns_symbol_one_and_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_binary_cdf(1);
for ctx_idx in 0..SKIP_CONTEXTS {
cdfs.skip[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let skip = walker
.decode_skip(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, false)
.unwrap();
assert_eq!(skip, 1);
// §5.11.5 grid-fill: BLOCK_16X16 has bw4 = bh4 = 4, so the
// footprint Skips[0..4][0..4] all carry 1.
for r in 0..4 {
for c in 0..4 {
assert_eq!(
walker.skips()[r * 32 + c],
1,
"footprint cell ({r},{c}) should be skip = 1",
);
}
}
// Cells outside the footprint stay at the initial 0.
assert_eq!(walker.skips()[4], 0, "col 4 outside footprint");
assert_eq!(walker.skips()[4 * 32], 0, "row 4 outside footprint");
}
/// §5.11.11 seg short-circuit also stamps the `Skips[]` grid
/// footprint to 1 (the spec's §5.11.5 footer applies regardless of
/// which branch produced the skip value).
#[test]
fn decode_skip_seg_short_circuit_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 1];
let mut dec = SymbolDecoder::init_symbol(&bytes, 1, true).unwrap();
// BLOCK_8X8: bw4 = bh4 = 2. Place the block at (4, 6).
walker
.decode_skip(&mut dec, &mut cdfs, 4, 6, BLOCK_8X8, true)
.unwrap();
for dr in 0..2 {
for dc in 0..2 {
assert_eq!(walker.skips()[(4 + dr) * 32 + (6 + dc)], 1);
}
}
// Adjacent cells stay at 0.
assert_eq!(walker.skips()[3 * 32 + 6], 0);
assert_eq!(walker.skips()[6 * 32 + 6], 0);
assert_eq!(walker.skips()[4 * 32 + 5], 0);
assert_eq!(walker.skips()[4 * 32 + 8], 0);
}
/// §8.3.2 skip-ctx walk: at the frame origin both AvailU and AvailL
/// are false ⇒ ctx = 0. The chosen CDF row's first symbol fires.
/// Test setup: skip_cdf[0] forced to select symbol 1, skip_cdf[1..]
/// forced to select symbol 0 — confirms ctx = 0 was picked.
#[test]
fn decode_skip_origin_ctx_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.skip[0] = force_binary_cdf(1);
cdfs.skip[1] = force_binary_cdf(0);
cdfs.skip[2] = force_binary_cdf(0);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let skip = walker
.decode_skip(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, false)
.unwrap();
assert_eq!(skip, 1, "origin selects skip_cdf[0] which forces 1");
}
/// §8.3.2 skip-ctx walk with two `Skips[]=1` neighbours: ctx = 2.
/// Two prior `decode_skip` calls at (0, 0) and (0, 2) seed the
/// above neighbour `Skips[0][2]=1` for the block at (2, 2), and
/// the left neighbour `Skips[2][1]=1` via a separate (2, 0)
/// BLOCK_8X8 leaf. The third call at (2, 2) should land on ctx =
/// `Skips[1][2] + Skips[2][1] = 1 + 1 = 2`. We assert that by
/// rigging skip_cdf[2] to force symbol 0 and skip_cdf[0..=1] to
/// force symbol 1.
#[test]
fn decode_skip_ctx_sums_neighbour_skips() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// First fill the two BLOCK_8X8 neighbour cells with skip = 1
// via the seg short-circuit (so no symbol bits are consumed
// before the ctx-2 read).
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// Above neighbour: BLOCK_8X8 at (0, 2). Fills Skips[0..2][2..4]=1.
walker
.decode_skip(&mut dec, &mut cdfs, 0, 2, BLOCK_8X8, true)
.unwrap();
// Left neighbour: BLOCK_8X8 at (2, 0). Fills Skips[2..4][0..2]=1.
walker
.decode_skip(&mut dec, &mut cdfs, 2, 0, BLOCK_8X8, true)
.unwrap();
assert_eq!(walker.skips()[32 + 2], 1, "above neighbour seeded");
assert_eq!(walker.skips()[2 * 32 + 1], 1, "left neighbour seeded");
// Now rig CDFs so ctx = 2 reads as 0 and ctx in {0, 1} reads
// as 1 — the actual ctx selected must be 2.
cdfs.skip[0] = force_binary_cdf(1);
cdfs.skip[1] = force_binary_cdf(1);
cdfs.skip[2] = force_binary_cdf(0);
let skip = walker
.decode_skip(&mut dec, &mut cdfs, 2, 2, BLOCK_8X8, false)
.unwrap();
assert_eq!(skip, 0, "ctx = 2 was picked (skip_cdf[2] forces 0)");
}
/// §8.3.2 skip-ctx walk with one `Skips[]=1` above-neighbour and
/// no left-neighbour available (`AvailL = false`): ctx = 1.
/// Place the block at (1, 0) on a frame whose tile_col_start = 0:
/// left-neighbour col = -1 is outside the tile ⇒ AvailL = false ⇒
/// left contributes 0. Above-neighbour is the (0, 0) block, seeded
/// with skip = 1 ⇒ above contributes 1.
#[test]
fn decode_skip_avail_l_false_drops_left_contribution() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// Seed above with skip = 1 at (0, 0), BLOCK_4X4 ⇒ bw4 = bh4 = 1.
walker
.decode_skip(&mut dec, &mut cdfs, 0, 0, BLOCK_4X4, true)
.unwrap();
assert_eq!(walker.skips()[0], 1);
// Rig CDFs so ctx = 1 reads as 0; ctx in {0, 2} reads as 1.
cdfs.skip[0] = force_binary_cdf(1);
cdfs.skip[1] = force_binary_cdf(0);
cdfs.skip[2] = force_binary_cdf(1);
// Block at (1, 0): above = (0, 0) ⇒ Skips[0][0] = 1. left at
// (1, -1) is outside ⇒ AvailL = false. ctx = 1.
let skip = walker
.decode_skip(&mut dec, &mut cdfs, 1, 0, BLOCK_4X4, false)
.unwrap();
assert_eq!(skip, 0, "ctx = 1 selected (skip_cdf[1] forces 0)");
}
/// §8.3.2 skip-ctx walk respects non-zero tile origin: a block at
/// the tile's top-left corner has `AvailU = AvailL = false` even
/// though the global mi-grid neighbour cells exist. Place the
/// block at the start of a tile whose `mi_row_start = mi_col_start
/// = 8`. The walker's `is_inside((7, 8))` and `is_inside((8, 7))`
/// both return false ⇒ ctx = 0.
#[test]
fn decode_skip_tile_origin_clears_avail() {
let geom = TileGeometry {
mi_row_start: 8,
mi_row_end: 24,
mi_col_start: 8,
mi_col_end: 24,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Seed the global-grid neighbour at (7, 8) and (8, 7) so a
// mistaken "use the global mi grid" implementation would
// observe skip = 1 there.
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
walker
.decode_skip(&mut dec, &mut cdfs, 7, 8, BLOCK_4X4, true)
.unwrap();
walker
.decode_skip(&mut dec, &mut cdfs, 8, 7, BLOCK_4X4, true)
.unwrap();
cdfs.skip[0] = force_binary_cdf(0);
cdfs.skip[1] = force_binary_cdf(1);
cdfs.skip[2] = force_binary_cdf(1);
let skip = walker
.decode_skip(&mut dec, &mut cdfs, 8, 8, BLOCK_4X4, false)
.unwrap();
assert_eq!(
skip, 0,
"tile origin ⇒ AvailU = AvailL = false ⇒ ctx = 0, picks skip_cdf[0]"
);
}
/// §5.11.5 grid-fill clipping: a leaf straddling the right edge of
/// the frame fills only the in-grid portion of its `bw4`
/// footprint. BLOCK_16X16 (bw4 = bh4 = 4) at (0, 30) on a 32-col
/// frame: only cols 30 and 31 are filled.
#[test]
fn decode_skip_grid_fill_clips_right_edge() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
walker
.decode_skip(&mut dec, &mut cdfs, 0, 30, BLOCK_16X16, true)
.unwrap();
// Cols 30, 31 of rows 0..4 are filled.
for r in 0..4 {
assert_eq!(walker.skips()[r * 32 + 30], 1);
assert_eq!(walker.skips()[r * 32 + 31], 1);
}
}
/// Out-of-range guards: `mi_row >= MiRows`, `mi_col >= MiCols`,
/// `sub_size >= BLOCK_SIZES` all surface
/// `Error::PartitionWalkOutOfRange`.
#[test]
fn decode_skip_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
assert_eq!(
walker.decode_skip(&mut dec, &mut cdfs, 8, 0, BLOCK_4X4, true),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_skip(&mut dec, &mut cdfs, 0, 8, BLOCK_4X4, true),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_skip(&mut dec, &mut cdfs, 0, 0, BLOCK_SIZES, true),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
/// §5.11.5 grid-fill initial state: a fresh PartitionWalker has
/// every `Skips[]` cell at 0 (the §9.4 default before any block is
/// decoded).
#[test]
fn fresh_walker_skips_grid_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
assert_eq!(walker.skips().len(), 16 * 16);
assert!(walker.skips().iter().all(|&v| v == 0));
}
// Round 154 — §5.11.10 read_skip_mode walker tests.
/// Helper: force a 2-symbol skip_mode CDF row (length 3) to select
/// `symbol` on the next `read_symbol`. Mirrors `force_binary_cdf`
/// (the §9.4 `DEFAULT_SKIP_MODE_CDF` rows are also `[a, 1 << 15,
/// 0]`-shaped 2-symbol CDFs).
fn force_skip_mode_cdf(symbol: u8) -> [u16; 3] {
match symbol {
0 => [1 << 15, 1 << 15, 0],
1 => [0, 1 << 15, 0],
_ => panic!("force_skip_mode_cdf supports 0 or 1 only"),
}
}
/// §5.11.10 short-circuit set (case 1: seg short-circuit):
/// `seg_feature_active(SEG_LVL_SKIP / REF_FRAME / GLOBALMV)` any
/// true ⇒ `skip_mode = 0`, no symbol read. The decoder position
/// must not advance.
#[test]
fn decode_skip_mode_seg_short_circuit_no_symbol_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, true, true)
.unwrap();
assert_eq!(skip_mode, 0, "seg_skip_mode_off ⇒ skip_mode = 0");
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read on the seg short-circuit"
);
}
/// §5.11.10 short-circuit set (case 2: !skip_mode_present): the
/// frame-header scalar from §5.9.21 is off ⇒ `skip_mode = 0`, no
/// symbol read.
#[test]
fn decode_skip_mode_present_false_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, false, false)
.unwrap();
assert_eq!(skip_mode, 0);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.10 short-circuit set (case 3a: Block_Width < 8): a
/// BLOCK_4X8 block (Block_Width = 4) ⇒ `skip_mode = 0`, no symbol
/// read.
#[test]
fn decode_skip_mode_small_width_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_4X8, false, true)
.unwrap();
assert_eq!(skip_mode, 0);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.10 short-circuit set (case 3b: Block_Height < 8): a
/// BLOCK_8X4 block (Block_Height = 4) ⇒ `skip_mode = 0`, no
/// symbol read.
#[test]
fn decode_skip_mode_small_height_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_8X4, false, true)
.unwrap();
assert_eq!(skip_mode, 0);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.10 else branch with a rigged CDF forcing symbol 0
/// (skip_mode = 0): the returned value matches.
#[test]
fn decode_skip_mode_else_branch_returns_symbol_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_skip_mode_cdf(0);
for ctx_idx in 0..SKIP_MODE_CONTEXTS {
cdfs.skip_mode[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, false, true)
.unwrap();
assert_eq!(skip_mode, 0);
}
/// §5.11.10 else branch with a rigged CDF forcing symbol 1
/// (skip_mode = 1): the returned value matches and the value is
/// stamped into the `SkipModes[]` grid over the block's
/// `bh4 * bw4` footprint.
#[test]
fn decode_skip_mode_else_branch_returns_symbol_one_and_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_skip_mode_cdf(1);
for ctx_idx in 0..SKIP_MODE_CONTEXTS {
cdfs.skip_mode[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, false, true)
.unwrap();
assert_eq!(skip_mode, 1);
// §5.11.5 grid-fill: BLOCK_16X16 has bw4 = bh4 = 4, so the
// footprint SkipModes[0..4][0..4] all carry 1.
for r in 0..4 {
for c in 0..4 {
assert_eq!(
walker.skip_modes()[r * 32 + c],
1,
"footprint cell ({r},{c}) should be skip_mode = 1",
);
}
}
// Cells outside the footprint stay at the initial 0.
assert_eq!(walker.skip_modes()[4], 0, "col 4 outside footprint");
assert_eq!(walker.skip_modes()[4 * 32], 0, "row 4 outside footprint");
}
/// §8.3.2 origin ctx = 0: at the frame origin both `AvailU` and
/// `AvailL` are false, so the ctx-0 row's rigged CDF drives the
/// read.
#[test]
fn decode_skip_mode_origin_ctx_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Only ctx 0 forces symbol 1; ctxs 1 and 2 force symbol 0.
cdfs.skip_mode[0] = force_skip_mode_cdf(1);
cdfs.skip_mode[1] = force_skip_mode_cdf(0);
cdfs.skip_mode[2] = force_skip_mode_cdf(0);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let skip_mode = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, false, true)
.unwrap();
assert_eq!(skip_mode, 1, "ctx-0 row should drive the read");
}
/// §8.3.2 ctx-2 path: two prior `SkipModes = 1` neighbours
/// (above and left) ⇒ ctx = 2; pick the ctx-2 row to drive the
/// read.
#[test]
fn decode_skip_mode_ctx_sums_neighbour_skip_modes() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force a BLOCK_8X8 (bw4 = bh4 = 2) above-left at (0,0) to
// stamp SkipModes[0..2][0..2] = 1.
cdfs.skip_mode[0] = force_skip_mode_cdf(1);
cdfs.skip_mode[1] = force_skip_mode_cdf(0);
cdfs.skip_mode[2] = force_skip_mode_cdf(0);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let v00 = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, false, true)
.unwrap();
assert_eq!(v00, 1);
// Block at (0, 2): AvailL true, AvailU false ⇒ ctx = 1 (only
// left neighbour contributes).
cdfs.skip_mode[0] = force_skip_mode_cdf(0);
cdfs.skip_mode[1] = force_skip_mode_cdf(1);
cdfs.skip_mode[2] = force_skip_mode_cdf(0);
let v01 = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 2, BLOCK_8X8, false, true)
.unwrap();
assert_eq!(v01, 1, "ctx-1 (left = 1, above unavail) row drives");
// Now block at (2, 0): AvailU true (SkipModes[0][0] = 1),
// AvailL false ⇒ ctx = 1 again.
let v10 = walker
.decode_skip_mode(&mut dec, &mut cdfs, 2, 0, BLOCK_8X8, false, true)
.unwrap();
assert_eq!(v10, 1, "ctx-1 (above = 1, left unavail) row drives");
// Block at (2, 2): AvailU true (SkipModes[1][2] = 1) and
// AvailL true (SkipModes[2][1] = 1) ⇒ ctx = 2.
cdfs.skip_mode[0] = force_skip_mode_cdf(0);
cdfs.skip_mode[1] = force_skip_mode_cdf(0);
cdfs.skip_mode[2] = force_skip_mode_cdf(1);
let v11 = walker
.decode_skip_mode(&mut dec, &mut cdfs, 2, 2, BLOCK_8X8, false, true)
.unwrap();
assert_eq!(v11, 1, "ctx-2 (above = left = 1) row drives");
}
/// §8.3.2 ctx with non-zero tile origin: a tile starting at
/// (mi_row_start = 4, mi_col_start = 4) clears both `AvailU` and
/// `AvailL` at the tile-origin block (4, 4); ctx = 0.
#[test]
fn decode_skip_mode_tile_origin_clears_avail() {
let geom = TileGeometry {
mi_row_start: 4,
mi_row_end: 32,
mi_col_start: 4,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Pre-stamp SkipModes[3][4] = SkipModes[4][3] = 1 to prove
// the tile origin ignores them.
walker.skip_modes[3 * 32 + 4] = 1;
walker.skip_modes[4 * 32 + 3] = 1;
cdfs.skip_mode[0] = force_skip_mode_cdf(1);
cdfs.skip_mode[1] = force_skip_mode_cdf(0);
cdfs.skip_mode[2] = force_skip_mode_cdf(0);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_skip_mode(&mut dec, &mut cdfs, 4, 4, BLOCK_16X16, false, true)
.unwrap();
assert_eq!(v, 1, "tile origin ⇒ ctx = 0 regardless of grid");
}
/// §5.11.5 grid-fill right-edge clip: a BLOCK_16X16 at
/// (0, mi_cols - 2) stamps only the in-grid columns (2 out of the
/// 4 `bw4` cells).
#[test]
fn decode_skip_mode_grid_fill_clips_right_edge() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_skip_mode_cdf(1);
for ctx_idx in 0..SKIP_MODE_CONTEXTS {
cdfs.skip_mode[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_skip_mode(&mut dec, &mut cdfs, 0, 30, BLOCK_16X16, false, true)
.unwrap();
assert_eq!(v, 1);
// Columns 30, 31 are stamped over rows 0..4; columns 32, 33
// are outside MiCols and not written.
for r in 0..4 {
assert_eq!(walker.skip_modes()[r * 32 + 30], 1);
assert_eq!(walker.skip_modes()[r * 32 + 31], 1);
}
}
/// Out-of-range guards: `mi_row >= MiRows`, `mi_col >= MiCols`,
/// `sub_size >= BLOCK_SIZES` all surface
/// `Error::PartitionWalkOutOfRange`.
#[test]
fn decode_skip_mode_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
assert_eq!(
walker.decode_skip_mode(&mut dec, &mut cdfs, 8, 0, BLOCK_4X4, true, true),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_skip_mode(&mut dec, &mut cdfs, 0, 8, BLOCK_4X4, true, true),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_skip_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_SIZES, true, true),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
/// §5.11.5 grid-fill initial state: a fresh PartitionWalker has
/// every `SkipModes[]` cell at 0.
#[test]
fn fresh_walker_skip_modes_grid_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
assert_eq!(walker.skip_modes().len(), 16 * 16);
assert!(walker.skip_modes().iter().all(|&v| v == 0));
}
// Round 155 — §5.11.12 read_delta_qindex walker tests.
/// Helper: force a length-5 CDF row to select `symbol` on the next
/// `read_symbol`. Matches the `Default_Delta_Q_Cdf` shape (four
/// literal values `0..=DELTA_Q_SMALL` plus the §8.2.6 counter
/// slot).
fn force_delta_q_cdf(symbol: u8) -> [u16; DELTA_Q_SMALL + 2] {
assert!(
(symbol as usize) <= DELTA_Q_SMALL,
"Default_Delta_Q_Cdf admits 0..=DELTA_Q_SMALL"
);
// §8.2.6 fixed-point CDF: assign the entire 1 << 15 mass to
// the desired symbol by setting the first `symbol` slots to 0
// and the remaining literal slots to `1 << 15`. The last entry
// is the counter slot, always 0.
let mut row = [1u16 << 15; DELTA_Q_SMALL + 2];
for s in row.iter_mut().take(symbol as usize) {
*s = 0;
}
row[DELTA_Q_SMALL + 1] = 0;
row
}
/// §9.4: `Default_Delta_Q_Cdf` matches the spec listing verbatim
/// (av1-spec p.431) and the §8.2.6 well-formedness invariant
/// holds.
#[test]
fn default_delta_q_cdf_matches_spec_literal() {
assert_eq!(DEFAULT_DELTA_Q_CDF, [28160, 32120, 32677, 32768, 0]);
let n = DEFAULT_DELTA_Q_CDF.len() - 1;
assert_eq!(DEFAULT_DELTA_Q_CDF[n - 1], 1 << 15);
assert_eq!(DEFAULT_DELTA_Q_CDF[n], 0);
}
/// §8.3.1 init: a fresh `TileCdfContext` carries
/// `DEFAULT_DELTA_Q_CDF` verbatim.
#[test]
fn tile_cdf_context_delta_q_initialised_to_default() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.delta_q_cdf(), &DEFAULT_DELTA_Q_CDF);
}
/// §5.11.12 short-circuit: `MiSize == sbSize && skip` ⇒ no symbol
/// read, `CurrentQIndex` unchanged. With `use_128x128_superblock
/// == false`, `sbSize == BLOCK_64X64`.
#[test]
fn decode_delta_qindex_sb_skip_short_circuit_64x64() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(120);
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let q = walker
.decode_delta_qindex(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_64X64,
/*skip=*/ 1,
/*read_deltas=*/ true,
/*use_128x128_superblock=*/ false,
/*delta_q_res=*/ 0,
)
.unwrap();
assert_eq!(q, 120, "sb-skip ⇒ CurrentQIndex unchanged");
assert_eq!(walker.current_q_index(), 120);
assert_eq!(dec.position(), pos_before, "no bit consumed");
}
/// §5.11.12 short-circuit (128x128 variant): `sbSize ==
/// BLOCK_128X128` when `use_128x128_superblock == true`.
#[test]
fn decode_delta_qindex_sb_skip_short_circuit_128x128() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 64,
mi_col_start: 0,
mi_col_end: 64,
};
let mut walker = PartitionWalker::new(64, 64, geom).unwrap();
walker.set_current_q_index(64);
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let q = walker
.decode_delta_qindex(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_128X128,
1,
true,
/*use_128x128_superblock=*/ true,
0,
)
.unwrap();
assert_eq!(q, 64);
assert_eq!(dec.position(), pos_before);
// A 64x64 block in a 128x128-superblock frame should NOT
// short-circuit (MiSize != sbSize).
let q2 = walker
.decode_delta_qindex(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_64X64,
1,
/*read_deltas=*/ false, // gate symbol-read off via ReadDeltas
true,
0,
)
.unwrap();
// The ReadDeltas gate (the *outer* if) is what stops the
// symbol read here; CurrentQIndex still unchanged.
assert_eq!(q2, 64);
}
/// §5.11.12 outer gate: `!ReadDeltas` ⇒ no symbol read,
/// `CurrentQIndex` unchanged. (`skip` doesn't matter when the
/// outer gate is off.)
#[test]
fn decode_delta_qindex_read_deltas_false_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(100);
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let q = walker
.decode_delta_qindex(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/*skip=*/ 0,
/*read_deltas=*/ false,
false,
3,
)
.unwrap();
assert_eq!(q, 100);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.12 else branch: a rigged CDF forcing `delta_q_abs = 0`
/// ⇒ `CurrentQIndex` unchanged (the `if (delta_q_abs)` body is
/// skipped, no sign-bit / no shift). One symbol bit consumed.
#[test]
fn decode_delta_qindex_zero_abs_no_update() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(80);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_q = force_delta_q_cdf(0);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let q = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 1)
.unwrap();
assert_eq!(q, 80, "delta_q_abs = 0 ⇒ CurrentQIndex unchanged");
}
/// §5.11.12 literal-positive path: `delta_q_abs = 1`, sign = 0,
/// `delta_q_res = 0` ⇒ `CurrentQIndex += 1`.
#[test]
fn decode_delta_qindex_literal_positive_no_shift() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(100);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_q = force_delta_q_cdf(1);
// After the S() read we need a sign-bit read of 0 (positive).
// Bytes are all zero so L(1) returns 0.
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let q = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 0)
.unwrap();
assert_eq!(q, 101);
assert_eq!(walker.current_q_index(), 101);
}
/// §5.11.12 literal-positive path with `delta_q_res = 2`:
/// `CurrentQIndex += (delta_q_abs << 2) = +4`.
#[test]
fn decode_delta_qindex_literal_positive_with_shift() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(50);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_q = force_delta_q_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let q = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 2)
.unwrap();
assert_eq!(q, 54, "50 + (1 << 2) = 54");
}
/// §8.2.2 / §8.2.5: with all-zero source bytes, every L(1)
/// `read_bool` yields 0. We rely on this to test positive-delta
/// paths deterministically (the negative path would require
/// engineering a specific arithmetic state to flip the sign bit,
/// which is out of scope for the §5.11.12 walker tests — its
/// L(1) is shared with the §8.2.3 boolean primitive whose tests
/// already cover both bit polarities).
#[test]
fn decode_delta_qindex_zero_byte_sign_bit_is_positive() {
let bytes = [0u8; 8];
let mut d = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// After an S() over a rigged 4-literal CDF returning 0, L(1)
// is still 0 on a zero-byte stream.
let mut cdf = force_delta_q_cdf(0);
let s = d.read_symbol(&mut cdf).unwrap();
assert_eq!(s, 0);
let l = d.read_literal(1).unwrap();
assert_eq!(l, 0, "all-zero bytes ⇒ L(1) = 0");
}
/// §5.11.12 Clip3(1, 255) lower bound: a large negative delta
/// from a tiny `CurrentQIndex` clips at 1. The arithmetic-decoder
/// `L(1)` sign-bit is shared with §8.2.3 `read_bool` and cannot
/// be rigged in isolation through a forced 4-literal CDF; we
/// instead exercise the lower clamp by seeding `CurrentQIndex`
/// at i32::MIN + 1 (a hostile-state caller bug) and asserting
/// the §5.11.12 Clip3 still drives the post-call value into
/// `[1, 255]`. Even the all-zero-bytes positive path with
/// `delta_q_abs = 1` cannot escape the lower clamp because the
/// pre-clip value is `i32::MIN + 2` (saturating against the
/// `wrapping_add(scaled)` step).
#[test]
fn decode_delta_qindex_clip3_lower_bound_via_hostile_seed() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(i32::MIN + 1);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_q = force_delta_q_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let q = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 0)
.unwrap();
assert_eq!(q, 1, "hostile seed + tiny +ve delta still clamps to 1");
}
/// §5.11.12 Clip3(1, 255) upper bound: a large positive delta
/// from a near-max `CurrentQIndex` clips at 255.
#[test]
fn decode_delta_qindex_clip3_upper_bound() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(250);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_q = force_delta_q_cdf(2);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// delta_q_abs = 2, sign = 0 (positive bytes are 0), shift = 3 ⇒
// delta = +16 ⇒ 250 + 16 = 266 ⇒ clip to 255.
let q = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 3)
.unwrap();
assert_eq!(q, 255);
}
/// §5.11.12 DELTA_Q_SMALL escape ladder: `delta_q_abs ==
/// DELTA_Q_SMALL` triggers the L(3) + L(n+1) ladder. With
/// `delta_q_rem_bits = 0` (read as L(3)=0), n = 1, `abs_bits =
/// L(1)=0` ⇒ `delta_q_abs = 0 + (1 << 1) + 1 = 3`.
#[test]
fn decode_delta_qindex_small_escape_minimum() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(100);
let mut cdfs = TileCdfContext::new_from_defaults();
// Force the symbol read to yield DELTA_Q_SMALL = 3.
cdfs.delta_q = force_delta_q_cdf(DELTA_Q_SMALL as u8);
// All-zero bytes ⇒ L(3) = 0 (rem_bits), L(1) = 0 (abs_bits),
// L(1) = 0 (sign). delta_q_abs = 3.
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let q = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 0)
.unwrap();
assert_eq!(q, 103, "100 + 3 = 103");
}
/// §5.11.12 DELTA_Q_SMALL escape ladder result stays in
/// [1, 255] regardless of the ladder's L() outputs: an FF-byte
/// stream forces the S() read into the escape branch (the
/// rigged 4-literal CDF + FF arithmetic state lands on
/// symbol 3 = DELTA_Q_SMALL as shown by the lower-clip probe
/// reasoning), and even when the resulting `delta_q_abs` is
/// large the §5.11.12 `Clip3(1, 255, ...)` keeps
/// `CurrentQIndex` in range. The exact post-call value depends
/// on the cascade of L(3) / L(n) / L(1) reads under the
/// arithmetic decoder's post-S() state and is not asserted
/// pointwise here.
#[test]
fn decode_delta_qindex_small_escape_stays_in_clip_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(200);
let mut cdfs = TileCdfContext::new_from_defaults();
// Force the S() result to DELTA_Q_SMALL via a CDF that lands
// on it under the zero-byte arithmetic state.
cdfs.delta_q = force_delta_q_cdf(DELTA_Q_SMALL as u8);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let q = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 0)
.unwrap();
// With all-zero bytes:
// * L(3) for delta_q_rem_bits = 0 ⇒ n = 1
// * L(1) for delta_q_abs_bits = 0 ⇒ delta_q_abs = 0 + 2 + 1 = 3
// * L(1) for sign_bit = 0 ⇒ reduced = +3
// * pre-clip 200 + 3 = 203, in range
assert_eq!(q, 203);
}
/// CurrentQIndex carries across multiple §5.11.12 calls: a +1
/// then another +1 with `delta_q_res = 0` from a seed of 100
/// ends at 102.
#[test]
fn decode_delta_qindex_accumulates_across_calls() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.set_current_q_index(100);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_q = force_delta_q_cdf(1);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let q1 = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, true, false, 0)
.unwrap();
assert_eq!(q1, 101);
let q2 = walker
.decode_delta_qindex(&mut dec, &mut cdfs, 0, 4, BLOCK_16X16, 0, true, false, 0)
.unwrap();
assert_eq!(q2, 102);
}
/// Fresh `PartitionWalker::current_q_index` defaults to 0
/// (callers must seed via `set_current_q_index(base_q_idx)`).
#[test]
fn fresh_walker_current_q_index_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
assert_eq!(walker.current_q_index(), 0);
}
/// Out-of-range guards: `mi_row >= MiRows`, `mi_col >= MiCols`,
/// `sub_size >= BLOCK_SIZES` all surface
/// `Error::PartitionWalkOutOfRange`.
#[test]
fn decode_delta_qindex_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
assert_eq!(
walker.decode_delta_qindex(&mut dec, &mut cdfs, 8, 0, BLOCK_16X16, 0, false, false, 0),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_delta_qindex(&mut dec, &mut cdfs, 0, 8, BLOCK_16X16, 0, false, false, 0),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_delta_qindex(&mut dec, &mut cdfs, 0, 0, BLOCK_SIZES, 0, false, false, 0),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
// Round 156 — §5.11.13 read_delta_lf walker tests.
/// Helper: force a length-5 CDF row to select `symbol` on the next
/// `read_symbol`. Mirrors `force_delta_q_cdf` for the §5.11.13
/// `delta_lf_abs` literal-branch CDFs.
fn force_delta_lf_cdf(symbol: u8) -> [u16; DELTA_LF_SMALL + 2] {
assert!(
(symbol as usize) <= DELTA_LF_SMALL,
"Default_Delta_Lf_Cdf admits 0..=DELTA_LF_SMALL"
);
let mut row = [1u16 << 15; DELTA_LF_SMALL + 2];
for s in row.iter_mut().take(symbol as usize) {
*s = 0;
}
row[DELTA_LF_SMALL + 1] = 0;
row
}
/// §9.4: `Default_Delta_Lf_Cdf` matches the spec listing verbatim
/// (av1-spec p.431) and the §8.2.6 well-formedness invariant
/// holds. Equality with `DEFAULT_DELTA_Q_CDF` is deliberate per
/// the spec.
#[test]
fn default_delta_lf_cdf_matches_spec_literal() {
assert_eq!(DEFAULT_DELTA_LF_CDF, [28160, 32120, 32677, 32768, 0]);
let n = DEFAULT_DELTA_LF_CDF.len() - 1;
assert_eq!(DEFAULT_DELTA_LF_CDF[n - 1], 1 << 15);
assert_eq!(DEFAULT_DELTA_LF_CDF[n], 0);
// §9.4 listing: the two §5.11.x delta tables carry the same
// distribution at fresh-context init.
assert_eq!(DEFAULT_DELTA_LF_CDF, DEFAULT_DELTA_Q_CDF);
}
/// §8.3.1 init: a fresh `TileCdfContext` carries
/// `DEFAULT_DELTA_LF_CDF` verbatim in both the single-LF row and
/// every multi-LF row.
#[test]
fn tile_cdf_context_delta_lf_initialised_to_default() {
let mut ctx = TileCdfContext::new_from_defaults();
assert_eq!(ctx.delta_lf_cdf(), &DEFAULT_DELTA_LF_CDF);
for i in 0..FRAME_LF_COUNT {
assert_eq!(ctx.delta_lf_multi_cdf(i), &DEFAULT_DELTA_LF_CDF);
}
}
/// §5.11.13 short-circuit: `MiSize == sbSize && skip` ⇒ no
/// symbol read, `DeltaLF[]` unchanged. With
/// `use_128x128_superblock == false`, `sbSize == BLOCK_64X64`.
#[test]
fn decode_delta_lf_sb_skip_short_circuit_64x64() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_64X64,
/*skip=*/ 1,
/*read_deltas=*/ true,
/*delta_lf_present=*/ true,
/*delta_lf_multi=*/ false,
/*mono_chrome=*/ false,
/*use_128x128_superblock=*/ false,
/*delta_lf_res=*/ 0,
)
.unwrap();
assert_eq!(row, [0; FRAME_LF_COUNT]);
assert_eq!(dec.position(), pos_before, "no bit consumed");
}
/// §5.11.13 short-circuit (128x128 variant): when
/// `use_128x128_superblock == true`, `sbSize == BLOCK_128X128`.
#[test]
fn decode_delta_lf_sb_skip_short_circuit_128x128() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 64,
mi_col_start: 0,
mi_col_end: 64,
};
let mut walker = PartitionWalker::new(64, 64, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_128X128,
1,
true,
true,
false,
false,
true,
0,
)
.unwrap();
assert_eq!(row, [0; FRAME_LF_COUNT]);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.13 outer gate: `!ReadDeltas` ⇒ no symbol read,
/// accumulator unchanged regardless of `delta_lf_present`.
#[test]
fn decode_delta_lf_read_deltas_false_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/*skip=*/ 0,
/*read_deltas=*/ false,
/*delta_lf_present=*/ true,
false,
false,
false,
3,
)
.unwrap();
assert_eq!(row, [0; FRAME_LF_COUNT]);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.13 outer gate: `!delta_lf_present` ⇒ no symbol read
/// even when `ReadDeltas` is on (the `&&` second clause).
#[test]
fn decode_delta_lf_present_false_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
/*read_deltas=*/ true,
/*delta_lf_present=*/ false,
false,
false,
false,
3,
)
.unwrap();
assert_eq!(row, [0; FRAME_LF_COUNT]);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.13 single-LF branch: `delta_lf_multi == 0` ⇒
/// `frameLfCount = 1`, only `DeltaLF[ 0 ]` updated, and the
/// `TileDeltaLFCdf` row is consulted.
#[test]
fn decode_delta_lf_single_branch_writes_only_slot_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
/*delta_lf_multi=*/ false,
false,
false,
0,
)
.unwrap();
assert_eq!(row[0], 1, "DeltaLF[0] += 1");
assert_eq!(row[1], 0, "DeltaLF[1] untouched");
assert_eq!(row[2], 0);
assert_eq!(row[3], 0);
}
/// §5.11.13 multi-LF colour branch: `delta_lf_multi == 1` and
/// `mono_chrome == false` ⇒ `frameLfCount == FRAME_LF_COUNT = 4`,
/// all four slots updated. With every multi-row forced to land
/// on `delta_lf_abs = 1` and all-zero bytes (positive sign),
/// each slot gains +1 with `delta_lf_res = 0`.
#[test]
fn decode_delta_lf_multi_branch_colour_writes_all_four_slots() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
for i in 0..FRAME_LF_COUNT {
cdfs.delta_lf_multi[i] = force_delta_lf_cdf(1);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
/*delta_lf_multi=*/ true,
/*mono_chrome=*/ false,
false,
0,
)
.unwrap();
assert_eq!(row, [1, 1, 1, 1], "all four edge categories incremented");
}
/// r378 §5.11.5 `DeltaLFs[ r + y ][ c + x ][ i ] = DeltaLF[ i ]`
/// per-mi grid-fill: after a §5.11.13 `decode_delta_lf` updates the
/// accumulator, `stamp_delta_lfs` writes that value over the block's
/// `bh4 * bw4` footprint, and `delta_lf_at` reads it back. Cells
/// outside the footprint stay at the §5.9.18 zero default.
#[test]
fn stamp_delta_lfs_writes_footprint_snapshot() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
for i in 0..FRAME_LF_COUNT {
cdfs.delta_lf_multi[i] = force_delta_lf_cdf(1);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
// Decode a multi-LF delta at a BLOCK_16X16 (4×4 mi) block origin
// (8, 4); every slot increments by 1.
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
8,
4,
BLOCK_16X16,
0,
true,
true,
true,
false,
false,
0,
)
.unwrap();
assert_eq!(row, [1, 1, 1, 1]);
// §5.11.5 grid-fill over the 4×4-mi footprint at (8, 4).
walker.stamp_delta_lfs(8, 4, BLOCK_16X16);
// Every cell in rows 8..12, cols 4..8 carries [1,1,1,1].
for r in 8..12 {
for c in 4..8 {
for i in 0..FRAME_LF_COUNT {
assert_eq!(
walker.delta_lf_at(r, c, i),
1,
"DeltaLFs[{r}][{c}][{i}] must carry the stamped accumulator"
);
}
}
}
// A cell outside the footprint stays at the §5.9.18 zero default.
assert_eq!(walker.delta_lf_at(0, 0, 0), 0);
assert_eq!(walker.delta_lf_at(12, 4, 0), 0, "row below the footprint");
assert_eq!(walker.delta_lf_at(8, 8, 0), 0, "col right of the footprint");
// Out-of-grid / out-of-range reads fold to 0.
assert_eq!(walker.delta_lf_at(-1, 0, 0), 0);
assert_eq!(walker.delta_lf_at(0, 0, FRAME_LF_COUNT), 0);
assert_eq!(walker.delta_lf_at(32, 0, 0), 0);
}
/// §5.11.13 multi-LF monochrome branch: `delta_lf_multi == 1`
/// and `mono_chrome == true` ⇒ `frameLfCount == FRAME_LF_COUNT -
/// 2 = 2`. Only the two Y slots (`DeltaLF[0]`, `DeltaLF[1]`) are
/// updated; the U/V slots stay zero.
#[test]
fn decode_delta_lf_multi_branch_monochrome_writes_only_y_slots() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
for i in 0..FRAME_LF_COUNT {
cdfs.delta_lf_multi[i] = force_delta_lf_cdf(1);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
/*delta_lf_multi=*/ true,
/*mono_chrome=*/ true,
false,
0,
)
.unwrap();
assert_eq!(row, [1, 1, 0, 0], "only Y-horiz / Y-vert touched");
}
/// §5.11.13 zero-`delta_lf_abs` no-update: a forced CDF returning
/// 0 ⇒ the `if (deltaLfAbs)` body is skipped, no sign-bit read,
/// `DeltaLF[]` row unchanged.
#[test]
fn decode_delta_lf_zero_abs_no_update() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(0);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(row, [0; FRAME_LF_COUNT]);
}
/// §5.11.13 literal-positive path with `delta_lf_res = 2`:
/// `delta_lf_abs = 1`, sign = 0 ⇒ `DeltaLF[0] += (1 << 2) = 4`.
#[test]
fn decode_delta_lf_literal_positive_with_shift() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
2,
)
.unwrap();
assert_eq!(row[0], 4, "0 + (1 << 2) = 4");
}
/// §5.11.13 `Clip3(-MAX_LOOP_FILTER, MAX_LOOP_FILTER)` upper
/// bound: a large positive delta from a near-max seed clips at
/// `MAX_LOOP_FILTER = 63`.
#[test]
fn decode_delta_lf_clip3_upper_bound() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
// Seed the accumulator near the upper bound. Single-LF only
// writes slot 0 so we can pre-fill it.
walker.current_delta_lf[0] = 60;
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(2);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// delta_lf_abs = 2, sign = 0 (zero bytes), shift = 3 ⇒
// delta = +16 ⇒ 60 + 16 = 76 ⇒ clip to 63.
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
3,
)
.unwrap();
assert_eq!(row[0], MAX_LOOP_FILTER);
}
/// §5.11.13 `Clip3(-MAX_LOOP_FILTER, MAX_LOOP_FILTER)` lower
/// bound via hostile seed: like the §5.11.12 lower-bound test,
/// the all-zero-bytes sign read flips positive, but a sufficiently
/// negative seed plus a small positive delta still stays below
/// `-MAX_LOOP_FILTER` so the lower clip activates. Seed at
/// `i32::MIN + 1` to verify the saturating clamp.
#[test]
fn decode_delta_lf_clip3_lower_bound_via_hostile_seed() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
walker.current_delta_lf[0] = i32::MIN + 1;
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(
row[0], -MAX_LOOP_FILTER,
"hostile seed + tiny +ve delta still clamps at -MAX_LOOP_FILTER"
);
}
/// §5.11.13 DELTA_LF_SMALL escape ladder minimum value: a forced
/// `delta_lf_abs = DELTA_LF_SMALL = 3` with all-zero bytes ⇒
/// `L(3) = 0 ⇒ n = 1`, `L(1) = 0 ⇒ abs_bits = 0` ⇒ `deltaLfAbs
/// = 0 + (1 << 1) + 1 = 3`. Sign bit `L(1) = 0` (positive) ⇒
/// `DeltaLF[0] += 3`.
#[test]
fn decode_delta_lf_small_escape_minimum() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(DELTA_LF_SMALL as u8);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let row = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(row[0], 3);
}
/// §5.11.13 cross-call accumulation in the single-LF branch:
/// two consecutive `+1` calls ⇒ `DeltaLF[0]` reaches 2.
#[test]
fn decode_delta_lf_accumulates_across_calls() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(1);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r1 = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(r1[0], 1);
let r2 = walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
4,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(r2[0], 2);
}
/// §5.11.2: a fresh `PartitionWalker::current_delta_lf` is all
/// zero (matches the `for ( i = 0; i < FRAME_LF_COUNT; i++ )
/// DeltaLF[ i ] = 0` line). `reset_current_delta_lf` returns it
/// to that state after a non-zero update.
#[test]
fn fresh_walker_current_delta_lf_is_zero_and_reset_works() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
assert_eq!(walker.current_delta_lf(), &[0; FRAME_LF_COUNT]);
// Apply a small +1 single-LF update.
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.delta_lf = force_delta_lf_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
walker
.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
true,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(walker.current_delta_lf()[0], 1);
walker.reset_current_delta_lf();
assert_eq!(walker.current_delta_lf(), &[0; FRAME_LF_COUNT]);
}
/// Out-of-range guards: `mi_row >= MiRows`, `mi_col >= MiCols`,
/// `sub_size >= BLOCK_SIZES` all surface
/// §5.11.55 fresh-walker init: every `cdef_idx[]` cell starts at
/// `-1` (the "CDEF disabled" sentinel that matches the
/// `clear_cdef()` initialisation).
#[test]
fn cdef_idx_fresh_walker_all_minus_one() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let walker = PartitionWalker::new(32, 32, geom).unwrap();
assert!(walker.cdef_idx().iter().all(|&v| v == -1));
}
/// §5.11.55: `clear_cdef( r, c )` with `use_128x128_superblock ==
/// false` stamps `-1` at exactly one anchor cell.
#[test]
fn clear_cdef_64x64_stamps_only_anchor() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
// Pre-stamp a non-sentinel so clear is observable.
walker.cdef_idx[5 * 32 + 7] = 4;
walker.clear_cdef(5, 7, false);
assert_eq!(walker.cdef_idx()[5 * 32 + 7], -1);
}
/// §5.11.55: `clear_cdef( r, c )` with `use_128x128_superblock ==
/// true` stamps `-1` at four 64×64 anchor cells (`cdefSize4 = 16`
/// stride).
#[test]
fn clear_cdef_128x128_stamps_four_anchors() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 64,
mi_col_start: 0,
mi_col_end: 64,
};
let mut walker = PartitionWalker::new(64, 64, geom).unwrap();
// Pre-stamp all four anchors so clear is observable.
let stamps: [(u32, u32); 4] = [(0, 0), (0, 16), (16, 0), (16, 16)];
for &(r, c) in &stamps {
walker.cdef_idx[(r * 64 + c) as usize] = 3;
}
walker.clear_cdef(0, 0, true);
for &(r, c) in &stamps {
assert_eq!(walker.cdef_idx()[(r * 64 + c) as usize], -1);
}
// No off-anchor cell touched.
assert_eq!(walker.cdef_idx()[1], -1);
}
/// §5.11.55: `clear_cdef( r, c )` silently skips out-of-grid
/// anchor cells (the bottom/right superblock can straddle the
/// frame edge).
#[test]
fn clear_cdef_skips_out_of_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 24,
mi_col_start: 0,
mi_col_end: 24,
};
let mut walker = PartitionWalker::new(24, 24, geom).unwrap();
// Anchor at (16, 16) is in-grid; the (16, 32) / (32, 16) /
// (32, 32) anchors are out-of-grid (24 < 32) and must be
// silently skipped without panic.
walker.clear_cdef(16, 16, true);
assert_eq!(walker.cdef_idx()[16 * 24 + 16], -1);
}
/// §5.11.56 short-circuit: `skip != 0` ⇒ no symbol read, anchor
/// sentinel unchanged.
#[test]
fn decode_cdef_skip_short_circuit_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let v = walker
.decode_cdef(
&mut dec,
0,
0,
BLOCK_16X16,
/*skip=*/ 1,
/*coded_lossless=*/ false,
/*enable_cdef=*/ true,
/*allow_intrabc=*/ false,
/*cdef_bits=*/ 2,
)
.unwrap();
assert_eq!(v, -1, "anchor sentinel survives the short-circuit");
assert_eq!(dec.position(), pos_before);
}
/// §5.11.56 short-circuit: `CodedLossless == true` ⇒ no read.
#[test]
fn decode_cdef_coded_lossless_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let v = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, true, true, false, 2)
.unwrap();
assert_eq!(v, -1);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.56 short-circuit: `!enable_cdef` ⇒ no read. This is the
/// common case for sequences that opted out of CDEF in the §5.5.1
/// sequence header.
#[test]
fn decode_cdef_disabled_in_seq_header_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let v = walker
.decode_cdef(
&mut dec,
0,
0,
BLOCK_16X16,
0,
false,
/*enable_cdef=*/ false,
false,
0,
)
.unwrap();
assert_eq!(v, -1);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.56 short-circuit: `allow_intrabc == true` ⇒ no read.
#[test]
fn decode_cdef_allow_intrabc_short_circuit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let v = walker
.decode_cdef(
&mut dec,
0,
0,
BLOCK_16X16,
0,
false,
true,
/*allow_intrabc=*/ true,
2,
)
.unwrap();
assert_eq!(v, -1);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.56 first-leaf-in-anchor path with `cdef_bits = 2`:
/// `L(2)` reads two bits MSB-first. Bytes `0b11_000000…` ⇒
/// `cdef_idx = 0b11 = 3`. The anchor and the leaf's `w4 = h4 = 4`
/// footprint inside the anchor receives the stamp; advancing by
/// `cdefSize4 = 16` per row/col means only the anchor cell is
/// touched for a sub-64 block.
#[test]
fn decode_cdef_first_leaf_reads_literal_and_stamps_anchor() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut bytes = [0u8; 8];
bytes[0] = 0b1100_0000;
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 2)
.unwrap();
assert_eq!(v, 3, "two-bit MSB-first read yields 3");
// Anchor at (0, 0) stamped; off-anchor cells inside the same
// 64×64 cell are untouched (the spec only fills 64-aligned
// cells via the cdefSize4 stride).
assert_eq!(walker.cdef_idx()[0], 3);
assert_eq!(
walker.cdef_idx()[4],
-1,
"off-anchor cell inside the same 64×64 anchor stays at sentinel"
);
}
/// §5.11.56 second leaf in the same 64×64 anchor: `cdef_idx[ r
/// ][ c ] != -1` ⇒ no read, anchor value returned unchanged.
#[test]
fn decode_cdef_second_leaf_in_anchor_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut bytes = [0u8; 8];
bytes[0] = 0b1100_0000;
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// First leaf at (0, 0): consumes two bits, anchor ⇐ 3.
walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 2)
.unwrap();
let pos_after_first = dec.position();
// Second leaf at (0, 4): same 64×64 anchor at (0, 0). Must
// not read, must return the stamped value 3.
let v = walker
.decode_cdef(&mut dec, 0, 4, BLOCK_16X16, 0, false, true, false, 2)
.unwrap();
assert_eq!(v, 3, "subsequent leaf in same anchor returns stamped value");
assert_eq!(
dec.position(),
pos_after_first,
"second leaf consumes no extra bits"
);
}
/// §5.11.56 `cdef_bits == 0` edge case: `L(0)` reads zero bits
/// and returns 0; the anchor still transitions from `-1` to `0`.
/// This matches the spec's `1 << cdef_bits == 1` single-strength
/// case from §5.9.19.
#[test]
fn decode_cdef_cdef_bits_zero_stamps_zero_without_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let v = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 0)
.unwrap();
assert_eq!(v, 0);
assert_eq!(dec.position(), pos_before, "L(0) consumes no bits");
assert_eq!(walker.cdef_idx()[0], 0);
}
/// §5.11.56 `cdef_bits == 3` upper bound: three literal bits can
/// encode values 0..=7. Bytes `0b1110_0000…` ⇒ `0b111 = 7`.
#[test]
fn decode_cdef_cdef_bits_three_reads_three_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut bytes = [0u8; 8];
bytes[0] = 0b1110_0000;
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 3)
.unwrap();
assert_eq!(v, 7);
}
/// §5.11.56 mask logic: a leaf at (MiRow = 10, MiCol = 13) inside
/// a 64×64 anchor (`cdefMask4 = ~15`, so `cdefMask4 & x` zeroes
/// the low 4 bits) anchors at (0, 0). The stamp lands at the
/// anchor cell, not at (10, 13).
#[test]
fn decode_cdef_anchor_mask_routes_to_64x64_origin() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut bytes = [0u8; 8];
bytes[0] = 0b1000_0000;
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_cdef(&mut dec, 10, 13, BLOCK_4X4, 0, false, true, false, 2)
.unwrap();
assert_eq!(v, 2);
assert_eq!(walker.cdef_idx()[0], 2, "anchor at (0, 0)");
assert_eq!(
walker.cdef_idx()[10 * 32 + 13],
-1,
"leaf coords are not the stamp coords"
);
}
/// §5.11.56 BLOCK_128X128 spans four 64×64 anchors: a single
/// 128×128 leaf at (0, 0) should stamp all four. `w4 = h4 = 32`,
/// stride `cdefSize4 = 16`, so the inner loop visits (0, 0),
/// (0, 16), (16, 0), (16, 16). The decoded literal value is
/// whatever the §8.2.5 `L(2)` returns against the test buffer;
/// the structural invariant is that all four anchor cells receive
/// the same stamp (the spec's grid-fill rule).
#[test]
fn decode_cdef_block_128x128_stamps_four_anchors() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0xA5u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_cdef(&mut dec, 0, 0, BLOCK_128X128, 0, false, true, false, 2)
.unwrap();
assert!((0..4).contains(&v), "L(2) ⇒ 0..=3, got {v}");
let anchors: [(u32, u32); 4] = [(0, 0), (0, 16), (16, 0), (16, 16)];
for &(r, c) in &anchors {
assert_eq!(
walker.cdef_idx()[(r * 32 + c) as usize],
v,
"anchor ({r}, {c}) stamped"
);
}
// Off-anchor cells inside any of the four 64×64 cells stay at
// the sentinel (no fill, the stride is 16).
assert_eq!(walker.cdef_idx()[4], -1);
assert_eq!(walker.cdef_idx()[4 * 32], -1);
assert_eq!(walker.cdef_idx()[16 * 32 + 20], -1);
}
/// §5.11.56 grid-fill clips at the frame's right/bottom edge: a
/// 64×64 anchor at (16, 16) in a 24×24 mi-grid leaves the
/// out-of-grid 32-aligned anchors untouched (no panic).
#[test]
fn decode_cdef_grid_fill_clips_frame_edge() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 24,
mi_col_start: 0,
mi_col_end: 24,
};
let mut walker = PartitionWalker::new(24, 24, geom).unwrap();
let bytes = [0xA5u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_cdef(&mut dec, 16, 16, BLOCK_128X128, 0, false, true, false, 2)
.unwrap();
assert!((0..4).contains(&v));
// The (16, 16) anchor is in-grid and stamped; the other three
// 128×128 anchors would be off-grid (32 >= 24) so they aren't
// touched (no panic on out-of-grid clip).
assert_eq!(walker.cdef_idx()[16 * 24 + 16], v);
}
/// §5.11.56 short-circuit when anchor already non-sentinel: even
/// with `skip=1`, the returned value is the anchor's current
/// stamp (a prior leaf's `decode_cdef` write), not `-1`. Verifies
/// the spec's "return value is the current `cdef_idx[r][c]`"
/// interpretation.
#[test]
fn decode_cdef_short_circuit_returns_prior_stamp() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut bytes = [0u8; 8];
bytes[0] = 0b1100_0000;
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// First leaf at (0, 0) stamps anchor ⇐ 3.
walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 2)
.unwrap();
// Second leaf at (0, 4) with `skip = 1` — short-circuit. The
// anchor at (0, 0) still holds 3.
let v = walker
.decode_cdef(&mut dec, 0, 4, BLOCK_16X16, 1, false, true, false, 2)
.unwrap();
assert_eq!(v, 3, "short-circuit returns anchor's existing stamp");
}
/// §5.11.55 `clear_cdef` after a stamp restores `-1` so a fresh
/// superblock at the same coordinates can re-read.
#[test]
fn clear_cdef_after_stamp_resets_anchor() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut bytes = [0u8; 8];
bytes[0] = 0b1100_0000;
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
walker
.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 2)
.unwrap();
assert_eq!(walker.cdef_idx()[0], 3);
walker.clear_cdef(0, 0, false);
assert_eq!(walker.cdef_idx()[0], -1);
}
/// §7.15 bridge: build a 16×16-mi (64×64-luma) walker, stamp a
/// uniform `cdef_idx = -1` (the §5.11.55 sentinel everywhere), and
/// drive [`PartitionWalker::cdef_frame_from_idx`]. With every anchor
/// reading `-1`, the §7.15.1 driver only performs the block copy, so
/// `CdefFrame[ plane ]` must equal `CurrFrame[ plane ]` byte-for-byte.
#[test]
fn cdef_frame_from_idx_sentinel_grid_is_pure_copy() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
// CurrFrame: a 64×64 luma ramp so a real filter would visibly
// change it; the sentinel grid must leave it untouched.
let (rows, cols) = (64u32, 64u32);
let src: Vec<i32> = (0..(rows * cols)).map(|i| (i % 256) as i32).collect();
let mut dst = vec![0i32; (rows * cols) as usize];
let src_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut src.clone(),
}];
let mut dst_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut dst,
}];
let params = crate::uncompressed_header_tail::CdefParams::short_circuit();
walker.cdef_frame_from_idx(¶ms, 1, 8, 0, 0, &src_pb, &mut dst_pb);
assert_eq!(
dst_pb[0].samples,
&src[..],
"sentinel grid copies CurrFrame"
);
}
/// §7.15 bridge equivalence: a stamped anchor with a non-zero
/// primary strength and a non-skip block must produce exactly what a
/// hand-built [`CdefFrameContext`] over the same grids produces — and
/// must differ from the pure copy (the filter actually fired).
#[test]
fn cdef_frame_from_idx_matches_direct_context_and_filters() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
// Stamp the (0,0) 64×64 anchor with cdef_idx = 0 and leave the
// whole 8×8 block non-skip (skips already 0 from construction).
walker.cdef_idx[0] = 0;
// CdefParams with a real primary luma strength so §7.15.3 fires.
let mut params = crate::uncompressed_header_tail::CdefParams::short_circuit();
params.short_circuited = false;
params.cdef_damping = 5;
params.cdef_bits = 0;
params.cdef_y_pri_strength[0] = 15;
params.cdef_y_sec_strength[0] = 3;
// CurrFrame: a flat 100-field with low-amplitude (±2) ringing
// noise in an INTERIOR 8×8 block (luma 16..24, every §7.15.3
// neighbour tap lands inside the frame — no edge clamping). The
// constrain damping engages on small diffs (CDEF de-rings small
// perturbations; strong edges damp to identity), so this block
// is provably changed. The anchor still shares the (0,0) 64×64
// cdef_idx cell.
let (rows, cols) = (64u32, 64u32);
let mut samples = vec![100i32; (rows * cols) as usize];
for y in 14..26usize {
for x in 14..26usize {
let n = (((x * 7 + y * 13) % 5) as i32) - 2;
samples[y * cols as usize + x] = 100 + n;
}
}
let base = samples.clone();
// Direct CdefFrameContext over the same idx/skip semantics.
let mut direct_dst = base.clone();
{
let src_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut base.clone(),
}];
let mut dst_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut direct_dst,
}];
let ctx = crate::cdef::CdefFrameContext {
mi_rows: 16,
mi_cols: 16,
num_planes: 1,
bit_depth: 8,
subsampling_x: 0,
subsampling_y: 0,
cdef_params: ¶ms,
cdef_idx: &|r, c| {
if r == 0 && c == 0 {
0
} else {
-1
}
},
skip: &|_, _| false,
};
crate::cdef::cdef_frame(&ctx, &src_pb, &mut dst_pb);
}
// Bridge path.
let mut bridge_dst = base.clone();
{
let src_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut base.clone(),
}];
let mut dst_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut bridge_dst,
}];
walker.cdef_frame_from_idx(¶ms, 1, 8, 0, 0, &src_pb, &mut dst_pb);
}
assert_eq!(bridge_dst, direct_dst, "bridge == direct CdefFrameContext");
assert_ne!(bridge_dst, base, "the §7.15.3 filter actually fired");
}
/// §7.15 bridge: a non-`-1` anchor whose whole 8×8 block is skipped
/// (`Skips[r][c] && Skips[r+1][c] && Skips[r][c+1] && Skips[r+1][c+1]`)
/// must NOT filter — the §7.15.1 `skip` conjunction returns the copy.
#[test]
fn cdef_frame_from_idx_full_skip_block_is_copy() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.cdef_idx[0] = 0;
// Mark every 4×4 cell of the top-left 8×8 anchor as skip.
for &(r, c) in &[(0u32, 0u32), (1, 0), (0, 1), (1, 1)] {
walker.skips[(r * 16 + c) as usize] = 1;
}
let mut params = crate::uncompressed_header_tail::CdefParams::short_circuit();
params.short_circuited = false;
params.cdef_y_pri_strength[0] = 4;
params.cdef_y_sec_strength[0] = 2;
let (rows, cols) = (64u32, 64u32);
let mut samples = vec![100i32; (rows * cols) as usize];
for y in 0..8usize {
for x in 0..8usize {
samples[y * cols as usize + x] = if x < 4 { 40 } else { 200 };
}
}
let base = samples.clone();
let mut dst = base.clone();
let src_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut base.clone(),
}];
let mut dst_pb = [crate::loop_filter::PlaneBuffer {
rows,
cols,
samples: &mut dst,
}];
walker.cdef_frame_from_idx(¶ms, 1, 8, 0, 0, &src_pb, &mut dst_pb);
assert_eq!(dst, base, "fully-skipped block is a pure copy");
}
/// §7.15 bridge with chroma: the §7.15.1 copy loop must respect the
/// per-plane subsampling shift when writing `CdefFrame[ 1 ]` /
/// `CdefFrame[ 2 ]`. A sentinel grid over a 4:2:0 frame leaves both
/// chroma planes equal to their `CurrFrame` source.
#[test]
fn cdef_frame_from_idx_chroma_copy_respects_subsampling() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
let (y_rows, y_cols) = (64u32, 64u32);
let (c_rows, c_cols) = (32u32, 32u32); // 4:2:0
let y_src: Vec<i32> = (0..(y_rows * y_cols)).map(|i| (i % 200) as i32).collect();
let u_src: Vec<i32> = (0..(c_rows * c_cols)).map(|i| (i % 150) as i32).collect();
let v_src: Vec<i32> = (0..(c_rows * c_cols)).map(|i| (i % 130) as i32).collect();
let mut y_dst = vec![0i32; y_src.len()];
let mut u_dst = vec![0i32; u_src.len()];
let mut v_dst = vec![0i32; v_src.len()];
let src_pb = [
crate::loop_filter::PlaneBuffer {
rows: y_rows,
cols: y_cols,
samples: &mut y_src.clone(),
},
crate::loop_filter::PlaneBuffer {
rows: c_rows,
cols: c_cols,
samples: &mut u_src.clone(),
},
crate::loop_filter::PlaneBuffer {
rows: c_rows,
cols: c_cols,
samples: &mut v_src.clone(),
},
];
let mut dst_pb = [
crate::loop_filter::PlaneBuffer {
rows: y_rows,
cols: y_cols,
samples: &mut y_dst,
},
crate::loop_filter::PlaneBuffer {
rows: c_rows,
cols: c_cols,
samples: &mut u_dst,
},
crate::loop_filter::PlaneBuffer {
rows: c_rows,
cols: c_cols,
samples: &mut v_dst,
},
];
let params = crate::uncompressed_header_tail::CdefParams::short_circuit();
walker.cdef_frame_from_idx(¶ms, 3, 8, 1, 1, &src_pb, &mut dst_pb);
assert_eq!(dst_pb[0].samples, &y_src[..], "Y copied");
assert_eq!(dst_pb[1].samples, &u_src[..], "U copied (4:2:0)");
assert_eq!(dst_pb[2].samples, &v_src[..], "V copied (4:2:0)");
}
/// §5.11.56 caller-bug guards: `mi_row` past frame extent,
/// `mi_col` past frame extent, `sub_size == BLOCK_SIZES`, and
/// `cdef_bits > 3` all ⇒ `Error::PartitionWalkOutOfRange`.
#[test]
fn decode_cdef_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
assert_eq!(
walker.decode_cdef(&mut dec, 8, 0, BLOCK_16X16, 0, false, true, false, 2),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_cdef(&mut dec, 0, 8, BLOCK_16X16, 0, false, true, false, 2),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_cdef(&mut dec, 0, 0, BLOCK_SIZES, 0, false, true, false, 2),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_cdef(&mut dec, 0, 0, BLOCK_16X16, 0, false, true, false, 4),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
/// `Error::PartitionWalkOutOfRange`.
#[test]
fn decode_delta_lf_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
assert_eq!(
walker.decode_delta_lf(
&mut dec,
&mut cdfs,
8,
0,
BLOCK_16X16,
0,
false,
false,
false,
false,
false,
0
),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
8,
BLOCK_16X16,
0,
false,
false,
false,
false,
false,
0
),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_delta_lf(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
0,
false,
false,
false,
false,
false,
0
),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
// ---------------------------------------------------------------------
// Round 158 — §5.11.20 read_is_inter walker tests.
// ---------------------------------------------------------------------
/// Helper: force a 2-symbol is_inter CDF row (length 3) to select
/// `symbol` on the next `read_symbol`. Mirrors `force_skip_mode_cdf`
/// (the §9.4 `DEFAULT_IS_INTER_CDF` rows are also `[a, 1 << 15,
/// 0]`-shaped 2-symbol CDFs).
fn force_is_inter_cdf(symbol: u8) -> [u16; 3] {
match symbol {
0 => [1 << 15, 1 << 15, 0],
1 => [0, 1 << 15, 0],
_ => panic!("force_is_inter_cdf supports 0 or 1 only"),
}
}
/// §5.11.5 grid-fill initial state: a fresh PartitionWalker has
/// every `IsInters[]` cell at 0 (the §9.4 default before any block
/// is decoded).
#[test]
fn fresh_walker_is_inters_grid_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
assert_eq!(walker.is_inters().len(), 16 * 16);
assert!(walker.is_inters().iter().all(|&v| v == 0));
}
/// §5.11.20 Arm 1: `skip_mode == 1` ⇒ `is_inter = 1`, no symbol
/// read. The decoder position must not advance. A buffer of
/// `0xFF` bytes would steer any spurious `S()` away from 0, so
/// the position-invariant check is the load-bearing assertion.
#[test]
fn decode_is_inter_skip_mode_short_circuit_no_symbol_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 1,
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_is_inter = */ false,
/* seg_globalmv_active = */ false,
)
.unwrap();
assert_eq!(is_inter, 1, "skip_mode = 1 ⇒ is_inter = 1");
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read on the skip_mode arm"
);
}
/// §5.11.20 Arm 2 (intra-routing branch):
/// `seg_feature_active(SEG_LVL_REF_FRAME) == true` +
/// `FeatureData[..][SEG_LVL_REF_FRAME] == INTRA_FRAME` (encoded by
/// the caller as `seg_ref_frame_is_inter = false`) ⇒
/// `is_inter = 0`, no symbol read.
#[test]
fn decode_is_inter_seg_ref_frame_routes_to_intra_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* seg_ref_frame_active = */ true,
/* seg_ref_frame_is_inter = */ false,
/* seg_globalmv_active = */ false,
)
.unwrap();
assert_eq!(is_inter, 0, "FeatureData == INTRA_FRAME ⇒ is_inter = 0");
assert_eq!(dec.position(), pos_before);
}
/// §5.11.20 Arm 2 (inter-routing branch):
/// `seg_feature_active(SEG_LVL_REF_FRAME) == true` +
/// `FeatureData[..][SEG_LVL_REF_FRAME] != INTRA_FRAME` (encoded
/// as `seg_ref_frame_is_inter = true`) ⇒ `is_inter = 1`, no
/// symbol read.
#[test]
fn decode_is_inter_seg_ref_frame_routes_to_inter_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* seg_ref_frame_active = */ true,
/* seg_ref_frame_is_inter = */ true,
/* seg_globalmv_active = */ false,
)
.unwrap();
assert_eq!(is_inter, 1, "FeatureData != INTRA_FRAME ⇒ is_inter = 1");
assert_eq!(dec.position(), pos_before);
}
/// §5.11.20 Arm 3: `seg_feature_active(SEG_LVL_GLOBALMV) == true`
/// ⇒ `is_inter = 1`, no symbol read.
#[test]
fn decode_is_inter_seg_globalmv_short_circuit_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_is_inter = */ false,
/* seg_globalmv_active = */ true,
)
.unwrap();
assert_eq!(is_inter, 1, "seg GLOBALMV ⇒ is_inter = 1");
assert_eq!(dec.position(), pos_before);
}
/// §5.11.20 arm-ordering invariant: when both `skip_mode == 1`
/// AND `seg_ref_frame_active == true` AND
/// `seg_ref_frame_is_inter == false`, Arm 1 fires first ⇒
/// `is_inter = 1` (not the `0` Arm 2 would produce). The spec's
/// `if / else if / else if / else` chain is short-circuit on the
/// first match.
#[test]
fn decode_is_inter_skip_mode_takes_precedence_over_seg_ref_frame() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 1,
/* seg_ref_frame_active = */ true,
/* seg_ref_frame_is_inter = */ false,
/* seg_globalmv_active = */ true,
)
.unwrap();
assert_eq!(
is_inter, 1,
"Arm 1 (skip_mode) takes precedence over Arm 2 / 3"
);
}
/// §5.11.20 arm-ordering invariant: when Arm 2 is active
/// (`seg_ref_frame_active == true`), the `seg_globalmv_active`
/// flag must NOT be checked — even if it would have produced
/// `is_inter = 1`, Arm 2's `seg_ref_frame_is_inter = false`
/// result wins.
#[test]
fn decode_is_inter_seg_ref_frame_takes_precedence_over_globalmv() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* seg_ref_frame_active = */ true,
/* seg_ref_frame_is_inter = */ false,
/* seg_globalmv_active = */ true,
)
.unwrap();
assert_eq!(is_inter, 0, "Arm 2 (seg_ref_frame=intra) wins over Arm 3");
}
/// §5.11.20 else branch with a rigged CDF forcing symbol 0
/// (`is_inter = 0`): the returned value matches.
#[test]
fn decode_is_inter_else_branch_returns_symbol_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_is_inter_cdf(0);
for ctx_idx in 0..IS_INTER_CONTEXTS {
cdfs.is_inter[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(is_inter, 0);
}
/// §5.11.20 else branch with a rigged CDF forcing symbol 1
/// (`is_inter = 1`): the returned value matches and the value is
/// stamped into the `IsInters[]` grid over the block's
/// `bh4 * bw4` footprint.
#[test]
fn decode_is_inter_else_branch_returns_symbol_one_and_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_is_inter_cdf(1);
for ctx_idx in 0..IS_INTER_CONTEXTS {
cdfs.is_inter[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(is_inter, 1);
// §5.11.5 grid-fill: BLOCK_16X16 has bw4 = bh4 = 4, so the
// footprint IsInters[0..4][0..4] all carry 1.
for r in 0..4 {
for c in 0..4 {
assert_eq!(
walker.is_inters()[r * 32 + c],
1,
"footprint cell ({r},{c}) should be is_inter = 1",
);
}
}
// Cells outside the footprint stay at the initial 0.
assert_eq!(walker.is_inters()[4], 0, "col 4 outside footprint");
assert_eq!(walker.is_inters()[4 * 32], 0, "row 4 outside footprint");
}
/// §8.3.2 origin ctx = 0: at the frame origin both `AvailU` and
/// `AvailL` are false, so the ctx-0 row's rigged CDF drives the
/// read. Other rows are zeroed (in a way that would not yield
/// symbol 1 if accidentally consulted).
#[test]
fn decode_is_inter_origin_ctx_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// ctx 0 drives symbol 1. ctx 1, 2, 3 would drive symbol 0.
cdfs.is_inter[0] = force_is_inter_cdf(1);
for ctx_idx in 1..IS_INTER_CONTEXTS {
cdfs.is_inter[ctx_idx] = force_is_inter_cdf(0);
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(
is_inter, 1,
"origin ctx must be 0; ctx-0 row drove the read"
);
}
/// §8.3.2 ctx = 3 (both neighbours intra): two prior decode_is_inter
/// calls write `is_inter = 0` at (0, 0) and (0, 4) so the block at
/// (4, 4) sees both an above neighbour (intra) and a left
/// neighbour (intra) ⇒ ctx = 3. The ctx-3 row's rigged CDF drives
/// the read.
#[test]
fn decode_is_inter_ctx_3_both_neighbours_intra() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force is_inter = 0 (intra) for the seed blocks via Arm 2.
let seed_bytes = [0xFFu8; 8];
let mut seed_dec = SymbolDecoder::init_symbol(&seed_bytes, 8, true).unwrap();
walker
.decode_is_inter(
&mut seed_dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
false,
false,
)
.unwrap();
walker
.decode_is_inter(
&mut seed_dec,
&mut cdfs,
0,
4,
BLOCK_16X16,
0,
true,
false,
false,
)
.unwrap();
// Now (4, 4) sees above (0, 4) at intra and left (4, 0) at
// intra — both available, both intra ⇒ ctx = 3.
// Wait: above at (4, 4) is (3, 4); left is (4, 3).
// The (0, 0) block stamped IsInters[0..4][0..4] = 0 (intra)
// and (0, 4) stamped IsInters[0..4][4..8] = 0. So (3, 4) and
// (4, 3) are both 0 (intra), but (4, 3) is at row 4 column 3
// — that's outside both stamps. Let me use (3, 5) instead so
// both neighbours fall inside the stamped intra footprint.
//
// Above neighbour: (2, 5). The (0, 4) stamp covers rows 0-3
// cols 4-7. (2, 5) is inside ⇒ intra (is_inter = 0).
// Left neighbour: (3, 4). The (0, 4) stamp covers (3, 4) too
// ⇒ intra (is_inter = 0).
// Therefore ctx at (3, 5) = 3.
let rigged_3 = force_is_inter_cdf(1);
let rigged_others = force_is_inter_cdf(0);
for ctx_idx in 0..IS_INTER_CONTEXTS {
cdfs.is_inter[ctx_idx] = if ctx_idx == 3 {
rigged_3
} else {
rigged_others
};
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(&mut dec, &mut cdfs, 3, 5, BLOCK_8X8, 0, false, false, false)
.unwrap();
assert_eq!(
is_inter, 1,
"both-intra-neighbours ⇒ ctx = 3 drove the read"
);
}
/// §8.3.2 ctx = 1 (exactly one neighbour intra, both available):
/// the seed block at (0, 4) stamps intra; an inter seed block at
/// (4, 0) stamps inter. The (4, 4) block sees above intra
/// (`IsInters[3][4] = 0`) + left inter (`IsInters[4][3] = 1`) ⇒
/// `ctx = LeftIntra || AboveIntra = false || true = 1`.
#[test]
fn decode_is_inter_ctx_1_mixed_neighbours_both_avail() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let seed_bytes = [0u8; 8];
let mut seed_dec = SymbolDecoder::init_symbol(&seed_bytes, 8, true).unwrap();
// Above seed: intra at (0, 4) covering rows 0-3 cols 4-7.
walker
.decode_is_inter(
&mut seed_dec,
&mut cdfs,
0,
4,
BLOCK_16X16,
0,
true,
false,
false,
)
.unwrap();
// Left seed: inter at (4, 0) covering rows 4-7 cols 0-3 via
// Arm 3 (globalmv).
walker
.decode_is_inter(
&mut seed_dec,
&mut cdfs,
4,
0,
BLOCK_16X16,
0,
false,
false,
true,
)
.unwrap();
// (4, 4) sees above (3, 4) ⇒ IsInters[3][4] = 0 (intra) AND
// left (4, 3) ⇒ IsInters[4][3] = 1 (inter).
// ctx = (LeftIntra && AboveIntra) ? 3 : LeftIntra || AboveIntra
// = (false && true) ? 3 : false || true
// = false ? 3 : true
// = 1
let rigged_1 = force_is_inter_cdf(1);
let rigged_others = force_is_inter_cdf(0);
for ctx_idx in 0..IS_INTER_CONTEXTS {
cdfs.is_inter[ctx_idx] = if ctx_idx == 1 {
rigged_1
} else {
rigged_others
};
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
4,
4,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(
is_inter, 1,
"mixed neighbours (above intra, left inter) ⇒ ctx = 1 drove the read"
);
}
/// §8.3.2 ctx = 2 (only one neighbour available, that neighbour
/// is intra). A tile origin shifted away from the frame origin
/// makes `AvailL` false at the tile-column origin; an above
/// neighbour stamped intra ⇒ `ctx = 2 * AboveIntra = 2 * 1 = 2`.
#[test]
fn decode_is_inter_ctx_2_only_above_intra() {
// Tile spans rows 0..32, cols 4..32 (so col 4 is the tile
// origin column — `AvailL` at col 4 is false for the first
// block in row 0). But to also get `AvailU` true at the
// tested block, we need a non-zero row.
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 4,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let seed_bytes = [0u8; 8];
let mut seed_dec = SymbolDecoder::init_symbol(&seed_bytes, 8, true).unwrap();
// Above seed: intra at (0, 4) covering rows 0-3 cols 4-7.
walker
.decode_is_inter(
&mut seed_dec,
&mut cdfs,
0,
4,
BLOCK_16X16,
0,
true,
false,
false,
)
.unwrap();
// Block under test at (4, 4):
// - AvailU = is_inside(3, 4) = true (3 is in row range, 4 is
// in col range [4..32]).
// - AvailL = is_inside(4, 3) = false (col 3 < mi_col_start = 4).
// - AboveIntra: IsInters[3][4] = 0 ⇒ above_intra = Some(true).
// - LeftIntra: None (unavailable).
// is_inter_ctx(Some(true), None) returns 2 * (true as usize)
// = 2.
let rigged_2 = force_is_inter_cdf(1);
let rigged_others = force_is_inter_cdf(0);
for ctx_idx in 0..IS_INTER_CONTEXTS {
cdfs.is_inter[ctx_idx] = if ctx_idx == 2 {
rigged_2
} else {
rigged_others
};
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
4,
4,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(
is_inter, 1,
"AvailL=false + above-intra ⇒ ctx = 2 drove the read"
);
}
/// §5.11.5 grid-fill clip: a `BLOCK_16X16` leaf at
/// `(MiRows - 2, MiCols - 2)` straddles both bottom and right
/// edges. Only the 4 cells inside the frame should get stamped;
/// the other 12 cells of the 4×4 footprint must not overflow the
/// `IsInters[]` buffer.
#[test]
fn decode_is_inter_grid_fill_clips_bottom_right_edge() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_is_inter_cdf(1);
for ctx_idx in 0..IS_INTER_CONTEXTS {
cdfs.is_inter[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let is_inter = walker
.decode_is_inter(
&mut dec,
&mut cdfs,
2,
2,
BLOCK_16X16,
0,
false,
false,
false,
)
.unwrap();
assert_eq!(is_inter, 1);
// Only the in-grid quadrant (2..4) × (2..4) should be 1.
for r in 0..4 {
for c in 0..4 {
let expected: u8 = if r >= 2 && c >= 2 { 1 } else { 0 };
assert_eq!(
walker.is_inters()[r * 4 + c],
expected,
"cell ({r},{c}) should be {expected}",
);
}
}
}
/// Out-of-range guard: `mi_row >= MiRows`, `mi_col >= MiCols`, or
/// `sub_size >= BLOCK_SIZES` all yield
/// `Error::PartitionWalkOutOfRange` without any state mutation.
#[test]
fn decode_is_inter_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
assert_eq!(
walker.decode_is_inter(&mut dec, &mut cdfs, 8, 0, BLOCK_4X4, 0, false, false, false),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_is_inter(&mut dec, &mut cdfs, 0, 8, BLOCK_4X4, 0, false, false, false),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_is_inter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
0,
false,
false,
false
),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
// ---------------------------------------------------------------
// Round 159 — §5.11.9 `read_segment_id()` (av1-spec p.66).
// Exercises [`PartitionWalker::decode_segment_id`].
// ---------------------------------------------------------------
/// Builds a rigged `Default_Segment_Id_Cdf`-shaped row (length
/// `MAX_SEGMENTS + 1 = 9`) that, against a freshly-initialised
/// `SymbolDecoder` over zero bytes (so `SymbolValue = 32767`),
/// forces a specific symbol to fire. `cdf[..symbol]` is `0` (so
/// the binary search advances past every smaller symbol) and
/// `cdf[symbol..MAX_SEGMENTS]` is `1 << 15` (cumulative count
/// `≈ 32768 > SymbolValue` ⇒ first match at `symbol`). The
/// trailing `cdf[MAX_SEGMENTS]` counter slot stays `0`.
fn force_segment_id_cdf(symbol: u8) -> [u16; MAX_SEGMENTS + 1] {
assert!(
(symbol as usize) < MAX_SEGMENTS,
"force_segment_id_cdf supports 0..MAX_SEGMENTS",
);
let mut row = [0u16; MAX_SEGMENTS + 1];
for slot in row.iter_mut().take(MAX_SEGMENTS).skip(symbol as usize) {
*slot = 1 << 15;
}
row
}
/// Fresh walker: every `SegmentIds[]` cell starts at the §5.11.9
/// `-1` sentinel.
#[test]
fn decode_segment_id_fresh_walker_grid_is_sentinel() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
for &cell in walker.segment_ids() {
assert_eq!(cell, -1, "fresh-walker segment_ids[] = -1 sentinel");
}
}
/// §5.11.9 `if ( skip ) segment_id = pred` short-circuit: no
/// symbol is read against the bitstream, and the empty-neighbour
/// case (frame origin, no prior calls ⇒ all three neighbours -1
/// ⇒ `pred = 0`) writes `segment_id = 0` over the footprint.
#[test]
fn decode_segment_id_skip_short_circuit_empty_neighbours_writes_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let sid = walker
.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, 1, 7)
.unwrap();
assert_eq!(sid, 0, "skip ⇒ segment_id = pred = 0 at frame origin");
assert_eq!(
dec.position(),
pos_before,
"no S() read on the §5.11.9 skip short-circuit",
);
// Footprint stamped to 0.
for r in 0..2 {
for c in 0..2 {
assert_eq!(walker.segment_ids()[r * 16 + c], 0);
}
}
// Adjacent cells stay at the -1 sentinel.
assert_eq!(walker.segment_ids()[2 * 16], -1);
assert_eq!(walker.segment_ids()[2], -1);
}
/// §5.11.9 skip short-circuit propagates the neighbour `pred`
/// (matching `prevU == prevL` ⇒ pred = prevU). Two prior calls
/// at (0, 0) and (0, 2) stamp `segment_id = 5` into row 0 cols
/// 0..4; a third skip-block at (2, 0) reads pred from `prev_l =
/// -1` (col -1 unavailable) and `prev_u = 5` (row 1 col 0 — but
/// row 1 was never written, so it's -1!). Use a 4×4 footprint
/// block to make the prior call stamp row 1 too.
#[test]
fn decode_segment_id_skip_pred_inherits_neighbour() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig every ctx row to force symbol 5; the prior call is the
// non-skip path so we need a CDF that delivers diff = 5.
let rigged = force_segment_id_cdf(5);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// First call: non-skip BLOCK_16X16 (bw4 = bh4 = 4) at origin.
// `pred = 0` (empty neighbours); `diff = 5` reads through
// `neg_deinterleave(5, 0, 8) = 5`. Stamps rows 0..4 cols 0..4
// to 5.
let sid0 = walker
.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, 0, 7)
.unwrap();
assert_eq!(sid0, 5);
// Second call: skip BLOCK_4X4 at (4, 0). prev_u = SegmentIds[3][0]
// = 5, prev_l = SegmentIds[4][-1] = -1 (col -1 unavailable),
// prev_ul = -1. §5.11.9 pred cascade: prev_u != -1, prev_l ==
// -1 ⇒ pred = prev_u = 5. Skip ⇒ segment_id = pred = 5.
let pos_before = dec.position();
let sid1 = walker
.decode_segment_id(&mut dec, &mut cdfs, 4, 0, BLOCK_4X4, 1, 7)
.unwrap();
assert_eq!(sid1, 5, "pred inherits prev_u when prev_l is unavailable");
assert_eq!(dec.position(), pos_before, "no S() read on skip path");
assert_eq!(walker.segment_ids()[4 * 16], 5);
}
/// §5.11.9 non-skip path: `S()` returns `diff = 0` ⇒
/// `neg_deinterleave(0, pred, max)`. For `pred = 0`, the
/// `if (!ref) return diff` branch fires ⇒ `segment_id = 0`.
#[test]
fn decode_segment_id_non_skip_pred_zero_returns_diff() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig every ctx row to force symbol 3.
let rigged = force_segment_id_cdf(3);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// Origin, empty neighbours ⇒ pred = 0; diff = 3 ⇒
// neg_deinterleave(3, 0, 8) = 3.
let sid = walker
.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, 0, 7)
.unwrap();
assert_eq!(sid, 3);
// Footprint stamped to 3.
for r in 0..2 {
for c in 0..2 {
assert_eq!(walker.segment_ids()[r * 16 + c], 3);
}
}
// Adjacent cells stay at -1.
assert_eq!(walker.segment_ids()[2 * 16], -1);
}
/// §6.10.8 conformance reject: the `S()` alphabet is the full
/// 8-symbol `Default_Segment_Id_Cdf` regardless of the frame's
/// `last_active_seg_id`, so a malformed stream can code a symbol
/// past `last_active_seg_id`. The decoder must surface
/// [`crate::Error::SegmentIdOutOfRange`] (never panic, never
/// stamp the grid). Found by the scheduled `decode` fuzz target
/// (2026-07-11 crash `99736d56…`).
#[test]
fn decode_segment_id_symbol_past_last_active_is_rejected() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig every ctx row to force symbol 5 while the frame's
// §5.9.14 derivation says LastActiveSegId = 4.
let rigged = force_segment_id_cdf(5);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let err = walker
.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, 0, 4)
.unwrap_err();
assert!(
matches!(err, crate::Error::SegmentIdOutOfRange),
"diff = 5 > last_active_seg_id = 4 must reject as a malformed stream, got {err:?}",
);
// The grid stays at the -1 sentinel: no partial stamp on the
// error path.
for &cell in walker.segment_ids() {
assert_eq!(cell, -1, "no grid stamp on the conformance reject");
}
// Boundary sanity: the same rigged symbol with
// last_active_seg_id = 5 is conformant and decodes
// (pred = 0 at the origin ⇒ neg_deinterleave(5, 0, 6) = 5).
let mut walker2 = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs2 = TileCdfContext::new_from_defaults();
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs2.segment_id[ctx_idx] = rigged;
}
let mut dec2 = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let sid = walker2
.decode_segment_id(&mut dec2, &mut cdfs2, 0, 0, BLOCK_8X8, 0, 5)
.unwrap();
assert_eq!(sid, 5, "diff == last_active_seg_id is in range");
}
/// §5.11.9 non-skip path with a non-zero `pred`: drives the
/// upward/downward `neg_deinterleave` arm. For `pred = 2`,
/// `max = 8`, `2 * ref = 4 < max ⇒ upper branch`. `diff = 0`
/// is even and `<= 2 * ref = 4` ⇒ `pred - (diff >> 1) = 2`.
/// `diff = 1` is odd and `<= 4` ⇒ `pred + ((diff + 1) >> 1) = 3`.
/// `diff = 2` is even ⇒ `pred - 1 = 1`. `diff = 3` is odd ⇒
/// `pred + 2 = 4`. `diff = 4` is even ⇒ `pred - 2 = 0`.
/// `diff = 5` is > `2 * ref` ⇒ returns `diff = 5`.
#[test]
fn decode_segment_id_neg_deinterleave_upward_branch() {
// Direct exercise of the helper (the §5.11.9 spec table).
assert_eq!(neg_deinterleave(0, 2, 8), 2);
assert_eq!(neg_deinterleave(1, 2, 8), 3);
assert_eq!(neg_deinterleave(2, 2, 8), 1);
assert_eq!(neg_deinterleave(3, 2, 8), 4);
assert_eq!(neg_deinterleave(4, 2, 8), 0);
assert_eq!(neg_deinterleave(5, 2, 8), 5);
assert_eq!(neg_deinterleave(6, 2, 8), 6);
assert_eq!(neg_deinterleave(7, 2, 8), 7);
}
/// §5.11.9 `neg_deinterleave` `2 * ref >= max` branch. For
/// `pred = 5`, `max = 8`, `2 * ref = 10 >= max ⇒ lower branch`,
/// `2 * (max - ref - 1) = 4`. `diff = 0` is even ⇒ `pred - 0 =
/// 5`. `diff = 1` is odd ⇒ `pred + 1 = 6`. `diff = 2` is even ⇒
/// `pred - 1 = 4`. `diff = 3` is odd ⇒ `pred + 2 = 7`. `diff =
/// 4` is even ⇒ `pred - 2 = 3`. `diff = 5` is > `2 * (max -
/// ref - 1) = 4` ⇒ `max - (diff + 1) = 8 - 6 = 2`.
#[test]
fn decode_segment_id_neg_deinterleave_downward_branch() {
assert_eq!(neg_deinterleave(0, 5, 8), 5);
assert_eq!(neg_deinterleave(1, 5, 8), 6);
assert_eq!(neg_deinterleave(2, 5, 8), 4);
assert_eq!(neg_deinterleave(3, 5, 8), 7);
assert_eq!(neg_deinterleave(4, 5, 8), 3);
assert_eq!(neg_deinterleave(5, 5, 8), 2);
assert_eq!(neg_deinterleave(6, 5, 8), 1);
assert_eq!(neg_deinterleave(7, 5, 8), 0);
}
/// §5.11.9 `neg_deinterleave` edge cases: `ref == 0` ⇒ identity
/// (returns `diff` unchanged); `ref == max - 1` ⇒ `max - diff -
/// 1` (the full alphabet inverted).
#[test]
fn decode_segment_id_neg_deinterleave_edge_cases() {
// ref = 0: identity over the full alphabet.
for d in 0..8u32 {
assert_eq!(neg_deinterleave(d, 0, 8), d);
}
// ref = max - 1: inverted.
for d in 0..8u32 {
assert_eq!(neg_deinterleave(d, 7, 8), 7 - d);
}
// Smallest non-trivial alphabet: max = 2, ref ∈ {0, 1},
// diff ∈ {0, 1}. ref = 0 ⇒ identity. ref = 1 ⇒ inverted.
assert_eq!(neg_deinterleave(0, 0, 2), 0);
assert_eq!(neg_deinterleave(1, 0, 2), 1);
assert_eq!(neg_deinterleave(0, 1, 2), 1);
assert_eq!(neg_deinterleave(1, 1, 2), 0);
}
/// §5.11.9 encoder-side `neg_interleave` bijection check —
/// exhaustive round trip over every `(segment_id, pred, max)`
/// triple within the spec's `0..MAX_SEGMENTS + 1 = 0..9` range.
/// Verifies `neg_deinterleave( neg_interleave( s, p, m ), p, m )
/// == s` for the full input space.
#[test]
fn neg_interleave_roundtrip_exhaustive() {
// `MAX_SEGMENTS = 8` and `LastActiveSegId <= 7` gives `max ∈
// 1..=8`. Walk the full closed range to also cover
// single-symbol alphabets.
for max in 1..=8u32 {
for pred in 0..max {
for sid in 0..max {
let diff = neg_interleave(sid, pred, max);
assert!(diff < max, "neg_interleave produces diff < max");
let back = neg_deinterleave(diff, pred, max);
assert_eq!(
back, sid,
"round-trip failed at (sid={}, pred={}, max={}) ⇒ diff={}",
sid, pred, max, diff
);
}
}
}
}
/// §5.11.9 encoder-side `neg_interleave` bijection check from the
/// opposite direction — every `(diff, pred, max)` triple round
/// trips through `neg_interleave( neg_deinterleave( d, p, m ), p,
/// m ) == d`. Combined with the forward exhaustive test, this
/// pins both functions as mutual inverses on the spec domain.
#[test]
fn neg_interleave_reverse_roundtrip_exhaustive() {
for max in 1..=8u32 {
for pred in 0..max {
for diff in 0..max {
let sid = neg_deinterleave(diff, pred, max);
let back = neg_interleave(sid, pred, max);
assert_eq!(
back, diff,
"reverse round-trip failed at (diff={}, pred={}, max={}) ⇒ sid={}",
diff, pred, max, sid
);
}
}
}
}
/// §5.11.9 `neg_interleave` matches the worked-example tables from
/// `decode_segment_id_neg_deinterleave_{upward,downward}_branch`.
/// Forward maps `pred = 2, max = 8`: diff 0..7 ⇒ sid 2,3,1,4,0,5,6,7.
/// Inverse maps must invert this column exactly.
#[test]
fn neg_interleave_matches_upward_branch_worked_example() {
// pred = 2, max = 8 — `2 * pred < max` arm.
assert_eq!(neg_interleave(2, 2, 8), 0);
assert_eq!(neg_interleave(3, 2, 8), 1);
assert_eq!(neg_interleave(1, 2, 8), 2);
assert_eq!(neg_interleave(4, 2, 8), 3);
assert_eq!(neg_interleave(0, 2, 8), 4);
assert_eq!(neg_interleave(5, 2, 8), 5);
assert_eq!(neg_interleave(6, 2, 8), 6);
assert_eq!(neg_interleave(7, 2, 8), 7);
}
/// §5.11.9 `neg_interleave` matches the worked-example tables
/// from `decode_segment_id_neg_deinterleave_downward_branch`.
/// Forward maps `pred = 5, max = 8`: diff 0..7 ⇒ sid 5,6,4,7,3,2,1,0.
/// Inverse maps must invert this column exactly.
#[test]
fn neg_interleave_matches_downward_branch_worked_example() {
// pred = 5, max = 8 — `2 * pred >= max` arm.
assert_eq!(neg_interleave(5, 5, 8), 0);
assert_eq!(neg_interleave(6, 5, 8), 1);
assert_eq!(neg_interleave(4, 5, 8), 2);
assert_eq!(neg_interleave(7, 5, 8), 3);
assert_eq!(neg_interleave(3, 5, 8), 4);
assert_eq!(neg_interleave(2, 5, 8), 5);
assert_eq!(neg_interleave(1, 5, 8), 6);
assert_eq!(neg_interleave(0, 5, 8), 7);
}
/// §5.11.9 ctx-0 selection at the frame origin (`AvailU =
/// AvailL = false`). Verify by rigging `segment_id_cdf[0]` to
/// fire symbol 1 and the other two ctx rows to fire symbol 0;
/// the origin call should land on ctx 0 ⇒ diff = 1 ⇒
/// `neg_deinterleave(1, 0, 8) = 1`.
#[test]
fn decode_segment_id_origin_ctx_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.segment_id[0] = force_segment_id_cdf(1);
cdfs.segment_id[1] = force_segment_id_cdf(0);
cdfs.segment_id[2] = force_segment_id_cdf(0);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let sid = walker
.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, 0, 7)
.unwrap();
// ctx-0 ⇒ diff = 1; pred = 0 ⇒ segment_id = 1.
assert_eq!(sid, 1);
}
/// §5.11.9 ctx-2 selection: three matching neighbours `prevUL ==
/// prevU == prevL`. Stamp two prior BLOCK_8X8 leaves at (0, 0)
/// and (0, 2) with sid = 4; then a third BLOCK_8X8 leaf at
/// (2, 2) — `prev_u = SegmentIds[1][2] = 4`, `prev_l =
/// SegmentIds[2][1] = 4`, `prev_ul = SegmentIds[1][1] = 4` ⇒
/// `segment_id_ctx` returns 2. The §5.11.9 `pred` derivation: U
/// != -1, L != -1, UL == U ⇒ pred = U = 4.
#[test]
fn decode_segment_id_ctx_2_all_neighbours_match() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Two BLOCK_8X8 skip stamps to seed sid = 4 into the
// top-left 4x4 cells without touching the bitstream.
// First seed at (0, 0): pred = 0, but skip + pred = 0 so
// sid = 0 — that doesn't work for our "all neighbours = 4"
// setup. Use the non-skip path with rigged CDF.
let rigged4 = force_segment_id_cdf(4);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged4;
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, 0, 7)
.unwrap();
// Skip stamp at (0, 2): pred from prev_u (-1), prev_l (=4),
// prev_ul (-1) ⇒ pred = prev_l = 4 (else-if branch: prev_u
// == -1, prev_l == 4). Skip ⇒ sid = 4.
walker
.decode_segment_id(&mut dec, &mut cdfs, 0, 2, BLOCK_8X8, 1, 7)
.unwrap();
// Skip stamp at (2, 0): pred from prev_u (= 4), prev_l
// (-1), prev_ul (-1) ⇒ prev_u != -1, prev_l == -1 ⇒ pred =
// prev_u = 4. Skip ⇒ sid = 4.
walker
.decode_segment_id(&mut dec, &mut cdfs, 2, 0, BLOCK_8X8, 1, 7)
.unwrap();
// Now: SegmentIds[1][1] = 4, [1][2] = 4, [2][1] = 4. The
// call at (2, 2) lands ctx = 2 (UL == U && UL == L). Rig
// ctx-2's row to force diff = 0 (so segment_id = pred = 4
// unchanged by the upward branch when diff is even and 0).
// ctx-0 / ctx-1 forced to diff = 7 (would yield a different
// sid) so ctx-2 selection is unambiguous.
cdfs.segment_id[0] = force_segment_id_cdf(7);
cdfs.segment_id[1] = force_segment_id_cdf(7);
cdfs.segment_id[2] = force_segment_id_cdf(0);
let sid = walker
.decode_segment_id(&mut dec, &mut cdfs, 2, 2, BLOCK_8X8, 0, 7)
.unwrap();
// pred = 4, diff = 0, neg_deinterleave(0, 4, 8): ref = 4,
// max = 8, 2 * ref = 8 == max ⇒ lower branch; 2 * (max -
// ref - 1) = 6; diff = 0 <= 6 and even ⇒ pred - 0 = 4.
assert_eq!(sid, 4, "ctx-2 selected and pred = 4 ⇒ sid = 4");
}
/// §5.11.5 grid-fill clips at the frame's bottom-right edge.
/// Place a BLOCK_16X16 leaf (`bw4 = bh4 = 4`) at `(2, 2)` of a
/// 4×4 frame: only the in-grid 2×2 quadrant should be stamped.
#[test]
fn decode_segment_id_grid_fill_clips_bottom_right_edge() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_segment_id_cdf(2);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged;
}
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let sid = walker
.decode_segment_id(&mut dec, &mut cdfs, 2, 2, BLOCK_16X16, 0, 7)
.unwrap();
assert_eq!(sid, 2, "diff = 2, pred = 0 ⇒ sid = 2");
// In-grid 2×2 quadrant (rows 2..4, cols 2..4) stamped.
for r in 2..4 {
for c in 2..4 {
assert_eq!(walker.segment_ids()[r * 4 + c], 2);
}
}
// Out-of-quadrant in-grid cells stay at -1.
assert_eq!(walker.segment_ids()[0], -1);
assert_eq!(walker.segment_ids()[3], -1);
assert_eq!(walker.segment_ids()[3 * 4], -1);
}
/// Three-way out-of-range guard: `mi_row` past extent, `mi_col`
/// past extent, `sub_size == BLOCK_SIZES`, plus the new
/// `last_active_seg_id >= MAX_SEGMENTS` precondition.
#[test]
fn decode_segment_id_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
assert_eq!(
walker.decode_segment_id(&mut dec, &mut cdfs, 8, 0, BLOCK_4X4, 0, 7),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_segment_id(&mut dec, &mut cdfs, 0, 8, BLOCK_4X4, 0, 7),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_SIZES, 0, 7),
Err(crate::Error::PartitionWalkOutOfRange)
);
// last_active_seg_id must be in 0..MAX_SEGMENTS = 0..8.
// The §5.9.14 derivation enforces this upstream; the walker
// surfaces a caller-bug for out-of-range values.
assert_eq!(
walker.decode_segment_id(&mut dec, &mut cdfs, 0, 0, BLOCK_4X4, 0, 8),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
// §5.11.8 intra_segment_id — round-160 dispatcher around r159
// decode_segment_id + §6.8.2 Lossless lookup.
/// §5.11.8 `!segmentation_enabled` branch: no `S()` read, the
/// grid is stamped to `0`, and `Lossless` comes from
/// `lossless_array[0]`. The bitstream is `0xFF` to make any
/// accidental read trivially observable as a non-zero position
/// delta.
#[test]
fn decode_intra_segment_id_segmentation_disabled_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
// lossless_array seeded so segment 0 is lossless.
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[0] = true;
let (sid, lossless) = walker
.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* skip = */ 0,
/* segmentation_enabled = */ false,
/* last_active_seg_id = */ 0,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0, "!segmentation_enabled ⇒ segment_id = 0");
assert!(lossless, "Lossless = LosslessArray[0] = true");
assert_eq!(
dec.position(),
pos_before,
"!segmentation_enabled ⇒ no bits consumed",
);
// Grid-fill: the bh4*bw4 footprint of BLOCK_8X8 (2x2) at
// (0, 0) is stamped to 0; adjacent cells stay at -1.
for r in 0..2 {
for c in 0..2 {
assert_eq!(walker.segment_ids()[r * 16 + c], 0);
}
}
assert_eq!(walker.segment_ids()[2 * 16], -1);
assert_eq!(walker.segment_ids()[2], -1);
}
/// §5.11.8 `!segmentation_enabled` branch reports `Lossless =
/// false` when the segment-0 slot of the lossless table is false
/// (e.g. a frame with `base_q_idx != 0` or a non-zero `DeltaQ?Dc`
/// / `DeltaQ?Ac`). Same no-read invariant as the previous test.
#[test]
fn decode_intra_segment_id_segmentation_disabled_lossless_false() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let lossless_array = [false; MAX_SEGMENTS]; // segment 0 = false
let (sid, lossless) = walker
.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
0,
false,
0,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0);
assert!(!lossless, "LosslessArray[0] = false ⇒ Lossless = false");
assert_eq!(dec.position(), pos_before);
}
/// §5.11.8 `segmentation_enabled = true, skip = 1` arm — pred =
/// 0 at the frame origin (all neighbours unavailable) ⇒
/// segment_id = pred = 0; the §5.11.9 skip-short-circuit fires
/// inside `decode_segment_id` and no `S()` is read. The Lossless
/// lookup reads `lossless_array[0]`.
#[test]
fn decode_intra_segment_id_segmentation_enabled_skip_origin() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[0] = true;
let (sid, lossless) = walker
.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* skip = */ 1,
/* segmentation_enabled = */ true,
/* last_active_seg_id = */ 7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0, "skip ⇒ segment_id = pred = 0 at frame origin");
assert!(lossless, "Lossless = LosslessArray[0] = true");
assert_eq!(
dec.position(),
pos_before,
"skip ⇒ decode_segment_id consumes no S() bit",
);
// Grid stamped to 0 over the BLOCK_8X8 footprint.
for r in 0..2 {
for c in 0..2 {
assert_eq!(walker.segment_ids()[r * 16 + c], 0);
}
}
}
/// §5.11.8 `segmentation_enabled = true, skip = 0` arm — descends
/// into `decode_segment_id`'s non-skip branch, which reads `diff
/// S()` and resolves `segment_id = neg_deinterleave(diff, pred,
/// max)`. Rig every ctx row to force `diff = 3`; `pred = 0` at
/// frame origin ⇒ `neg_deinterleave(3, 0, 8) = 3` (the `ref == 0`
/// identity branch). `lossless_array[3] = true` ⇒ `Lossless =
/// true`.
#[test]
fn decode_intra_segment_id_segmentation_enabled_non_skip_reads_symbol() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_segment_id_cdf(3);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged;
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[3] = true;
let (sid, lossless) = walker
.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* skip = */ 0,
true,
7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 3, "diff = 3 with pred = 0 ⇒ segment_id = 3");
assert!(lossless, "LosslessArray[3] = true ⇒ Lossless = true");
// Grid-fill at (0, 0) BLOCK_8X8 ⇒ rows 0..2 cols 0..2 = 3.
for r in 0..2 {
for c in 0..2 {
assert_eq!(walker.segment_ids()[r * 16 + c], 3);
}
}
}
/// §5.11.8 segmentation-enabled non-skip arm Lossless lookup is
/// per-segment, not frame-wide. Rig `diff = 5` on every ctx ⇒
/// `segment_id = 5`; `lossless_array[5] = false` ⇒ `Lossless =
/// false` even though `lossless_array[0] = true`.
#[test]
fn decode_intra_segment_id_lossless_indexed_by_segment_id() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_segment_id_cdf(5);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged;
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mut lossless_array = [true; MAX_SEGMENTS]; // every other slot lossless
lossless_array[5] = false; // segment 5 specifically not lossless
let (sid, lossless) = walker
.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
0,
true,
7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 5);
assert!(!lossless, "LosslessArray indexes by segment_id, not by 0",);
}
/// §5.11.8 grid-fill on the `!segmentation_enabled` branch clips
/// at the frame's bottom-right edge. A BLOCK_16X16 at (2, 2) in a
/// 4×4 frame has bh4 = bw4 = 4; only the in-grid 2×2 quadrant
/// (rows 2..4, cols 2..4) should be stamped to 0.
#[test]
fn decode_intra_segment_id_segmentation_disabled_grid_fill_clips() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let (sid, _) = walker
.decode_intra_segment_id(
&mut dec,
&mut cdfs,
2,
2,
BLOCK_16X16,
0,
false,
0,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0);
// Only rows 2..4 cols 2..4 were stamped (the bottom-right 2x2
// quadrant). The rest stay at the §5.11.9 -1 sentinel.
for r in 0..4 {
for c in 0..4 {
let cell = walker.segment_ids()[r * 4 + c];
if (2..4).contains(&r) && (2..4).contains(&c) {
assert_eq!(cell, 0, "({r}, {c}) stamped");
} else {
assert_eq!(cell, -1, "({r}, {c}) untouched");
}
}
}
}
/// §5.11.8 range guards fire on the `!segmentation_enabled`
/// branch too: out-of-range `sub_size`, `mi_row` / `mi_col` past
/// the frame's mi extent, and `last_active_seg_id >=
/// MAX_SEGMENTS` all surface `PartitionWalkOutOfRange` without
/// reading the bitstream.
#[test]
fn decode_intra_segment_id_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
assert_eq!(
walker.decode_intra_segment_id(
&mut dec,
&mut cdfs,
8,
0,
BLOCK_4X4,
0,
false,
0,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
8,
BLOCK_4X4,
0,
false,
0,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
0,
false,
0,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
walker.decode_intra_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
/* last_active_seg_id = */ 8,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange)
);
// Same guards on the segmentation_enabled = true path.
assert_eq!(
walker.decode_intra_segment_id(
&mut dec,
&mut cdfs,
8,
0,
BLOCK_4X4,
0,
true,
7,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
// ------------------------------------------------------------
// §5.11.7 `decode_intra_frame_mode_info_prefix` dispatcher tests
// ------------------------------------------------------------
/// §5.11.7 baseline: segmentation disabled + CDEF allowed but
/// `cdef_bits = 0` + `read_deltas = false` + `delta_lf_present =
/// false`. None of the composed reads consumes a bit (skip uses
/// a normal `S()` but its ctx-0 cdf forces it through a real
/// read — we instead suppress it via the seg-skip short-circuit
/// in a sibling test). For this test we rig the §5.11.11 skip
/// cdf to force `skip = 0`, then verify every other field
/// short-circuits in the expected place.
///
/// This is the minimum-bit path: only the §5.11.11 `S()` is
/// consumed (a single 2-symbol read).
#[test]
fn decode_intra_frame_mode_info_prefix_minimum_path() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig only the §5.11.11 skip cdf — every other inner method
// short-circuits via its caller-passed gates and reads zero
// bits. Forcing symbol 0 means `skip = 0`.
let rigged_skip = force_binary_cdf(0);
for ctx_idx in 0..SKIP_CONTEXTS {
cdfs.skip[ctx_idx] = rigged_skip;
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let prefix = walker
.decode_intra_frame_mode_info_prefix(
&mut dec,
&mut cdfs,
/* mi_row = */ 0,
/* mi_col = */ 0,
BLOCK_8X8,
/* seg_id_pre_skip = */ false,
/* segmentation_enabled = */ false,
/* seg_skip_active = */ &[false; MAX_SEGMENTS],
/* last_active_seg_id = */ 0,
&lossless_array,
/* coded_lossless = */ false,
/* enable_cdef = */ true,
/* allow_intrabc = */ false,
/* cdef_bits = */ 0,
/* read_deltas = */ false,
/* use_128x128_superblock = */ false,
/* delta_q_res = */ 0,
/* delta_lf_present = */ false,
/* delta_lf_multi = */ false,
/* mono_chrome = */ false,
/* delta_lf_res = */ 0,
)
.unwrap();
// §5.11.11 fired; rigged cdf returned 0.
assert_eq!(prefix.skip, 0);
// §5.11.7 line 4: intra-frame walk never sets skip_mode.
assert_eq!(prefix.skip_mode, 0);
// !segmentation_enabled ⇒ segment_id forced to 0.
assert_eq!(prefix.segment_id, 0);
// lossless_array[0] = false ⇒ Lossless = false.
assert!(!prefix.lossless);
// §5.11.56: skip=0 short-circuit doesn't fire, but
// cdef_bits=0 ⇒ L(0) returns 0 and the anchor is stamped
// with 0.
assert_eq!(prefix.cdef_idx, 0);
// read_deltas = false ⇒ no delta_qindex / delta_lf read;
// accumulators stay at their construction defaults (0 / [0;4]).
assert_eq!(prefix.current_q_index, 0);
assert_eq!(prefix.current_delta_lf, [0; FRAME_LF_COUNT]);
// §5.11.7 fixed RefFrame assignments.
assert_eq!(prefix.ref_frame, [0, -1]);
// Grids stamped over the BLOCK_8X8 footprint (2×2 at (0,0)).
for r in 0..2 {
for c in 0..2 {
assert_eq!(walker.skips()[r * 16 + c], 0);
assert_eq!(walker.segment_ids()[r * 16 + c], 0);
}
}
}
/// §5.11.7 `SegIdPreSkip = true` arm: `intra_segment_id` is
/// called BEFORE `read_skip` with `skip = 0` (the §5.11.7 line-1
/// init). With segmentation enabled the §5.11.9 non-skip path
/// fires, reading a `diff` symbol against
/// `TileSegmentIdCdf[ctx]`. Then `read_skip` reads its own bit.
/// Verifies the ordering by checking the bitstream position after
/// both reads — the pre-skip arm reads `segment_id` first, then
/// `skip`.
#[test]
fn decode_intra_frame_mode_info_prefix_pre_skip_arm_reads_segment_first() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig the §5.11.9 segment_id cdf to force diff = 2 on every
// ctx; pred = 0 at frame origin ⇒ segment_id = 2.
let rigged_seg = force_segment_id_cdf(2);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged_seg;
}
// Rig §5.11.11 skip cdf to force symbol 1 (skip = 1).
let rigged_skip = force_binary_cdf(1);
for ctx_idx in 0..SKIP_CONTEXTS {
cdfs.skip[ctx_idx] = rigged_skip;
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[2] = true;
let prefix = walker
.decode_intra_frame_mode_info_prefix(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* seg_id_pre_skip = */ true,
/* segmentation_enabled = */ true,
/* seg_skip_active = */ &[false; MAX_SEGMENTS],
/* last_active_seg_id = */ 7,
&lossless_array,
/* coded_lossless = */ false,
/* enable_cdef = */ true,
/* allow_intrabc = */ false,
/* cdef_bits = */ 0,
/* read_deltas = */ false,
/* use_128x128_superblock = */ false,
/* delta_q_res = */ 0,
/* delta_lf_present = */ false,
/* delta_lf_multi = */ false,
/* mono_chrome = */ false,
/* delta_lf_res = */ 0,
)
.unwrap();
// segment_id read first (in the pre-skip arm), then skip.
assert_eq!(prefix.segment_id, 2, "diff=2, pred=0 ⇒ segment_id=2");
assert!(prefix.lossless, "lossless_array[2] = true");
assert_eq!(prefix.skip, 1, "rigged skip cdf forces symbol 1");
// skip didn't trigger the cdef short-circuit because
// enable_cdef=true and cdef_bits=0, so the anchor was
// stamped with the L(0)=0 read. But the §5.11.56 spec also
// short-circuits on skip != 0 — verify cdef_idx is the
// pre-existing -1 sentinel from §5.11.55.
assert_eq!(
prefix.cdef_idx, -1,
"skip != 0 ⇒ §5.11.56 short-circuit returns the -1 sentinel"
);
}
/// §5.11.7 `SegIdPreSkip = false` arm: `read_skip` fires first,
/// then `intra_segment_id` with the just-decoded `skip`. When
/// `skip = 1` the §5.11.9 skip-short-circuit fires
/// (`segment_id = pred` with no `S()` consumed).
#[test]
fn decode_intra_frame_mode_info_prefix_post_skip_arm_segment_after_skip() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig §5.11.11 skip cdf to force symbol 1.
let rigged_skip = force_binary_cdf(1);
for ctx_idx in 0..SKIP_CONTEXTS {
cdfs.skip[ctx_idx] = rigged_skip;
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[0] = true; // pred = 0 at origin ⇒ segment 0
let prefix = walker
.decode_intra_frame_mode_info_prefix(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* seg_id_pre_skip = */ false,
/* segmentation_enabled = */ true,
/* seg_skip_active = */ &[false; MAX_SEGMENTS],
/* last_active_seg_id = */ 7,
&lossless_array,
/* coded_lossless = */ false,
/* enable_cdef = */ true,
/* allow_intrabc = */ false,
/* cdef_bits = */ 0,
/* read_deltas = */ false,
/* use_128x128_superblock = */ false,
/* delta_q_res = */ 0,
/* delta_lf_present = */ false,
/* delta_lf_multi = */ false,
/* mono_chrome = */ false,
/* delta_lf_res = */ 0,
)
.unwrap();
assert_eq!(prefix.skip, 1);
// skip = 1 ⇒ §5.11.9 short-circuit: segment_id = pred = 0
// at frame origin (no neighbours).
assert_eq!(prefix.segment_id, 0);
assert!(prefix.lossless, "lossless_array[0] = true");
}
/// §5.11.7 with SEG_LVL_SKIP active on the block's segment:
/// `read_skip` short-circuits to `skip = 1` without reading any
/// bit (the §5.11.11 outer arm). r426: the arm derives per BLOCK
/// — it requires the spec-consistent `SegIdPreSkip = 1` +
/// `segmentation_enabled = 1` configuration (§5.9.14 forces
/// SegIdPreSkip whenever a feature `>= SEG_LVL_REF_FRAME` is
/// active), and the pre-skip §5.11.8 read resolves the segment
/// id FIRST (consuming its §5.11.9 `S()` bits), so only the skip
/// element itself is bit-silent.
#[test]
fn decode_intra_frame_mode_info_prefix_seg_skip_active_no_skip_bit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let lossless_array = [false; MAX_SEGMENTS];
// Reference run: identical configuration with SEG_LVL_SKIP
// INACTIVE — consumes the same pre-skip segment-id bits PLUS
// one §5.11.11 skip S().
let bytes = [0xFFu8; 8];
let mut dec_ref = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let mut cdfs_ref = TileCdfContext::new_from_defaults();
walker
.decode_intra_frame_mode_info_prefix(
&mut dec_ref,
&mut cdfs_ref,
0,
0,
BLOCK_16X16,
/* seg_id_pre_skip = */ true,
/* segmentation_enabled = */ true,
/* seg_skip = */ &[false; MAX_SEGMENTS],
/* last_active_seg_id = */ 7,
&lossless_array,
/* coded_lossless = */ false,
/* enable_cdef = */ false, // short-circuit cdef
/* allow_intrabc = */ false,
/* cdef_bits = */ 0,
/* read_deltas = */ false,
/* use_128x128_superblock = */ false,
/* delta_q_res = */ 0,
/* delta_lf_present = */ false,
/* delta_lf_multi = */ false,
/* mono_chrome = */ false,
/* delta_lf_res = */ 0,
)
.unwrap();
let pos_with_skip_bit = dec_ref.position();
let mut walker2 = PartitionWalker::new(16, 16, geom).unwrap();
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let prefix = walker2
.decode_intra_frame_mode_info_prefix(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* seg_id_pre_skip = */ true,
/* segmentation_enabled = */ true,
/* seg_skip = */ &[true; MAX_SEGMENTS],
/* last_active_seg_id = */ 7,
&lossless_array,
/* coded_lossless = */ false,
/* enable_cdef = */ false, // short-circuit cdef
/* allow_intrabc = */ false,
/* cdef_bits = */ 0,
/* read_deltas = */ false,
/* use_128x128_superblock = */ false,
/* delta_q_res = */ 0,
/* delta_lf_present = */ false,
/* delta_lf_multi = */ false,
/* mono_chrome = */ false,
/* delta_lf_res = */ 0,
)
.unwrap();
assert_eq!(
prefix.skip, 1,
"seg_feature_active(SEG_LVL_SKIP) ⇒ skip = 1"
);
assert!(
dec.position() < pos_with_skip_bit,
"the seg-skip arm must consume strictly fewer bits than \
the coded-skip reference ({} vs {})",
dec.position(),
pos_with_skip_bit,
);
// §5.11.56 short-circuit returned the pre-existing -1
// sentinel (skip=1 and !enable_cdef both fire).
assert_eq!(prefix.cdef_idx, -1);
}
/// §5.11.7 prefix returns the §5.11.7 `RefFrame[0..2]` =
/// `[INTRA_FRAME, NONE]` = `[0, -1]` regardless of path taken.
#[test]
fn decode_intra_frame_mode_info_prefix_ref_frame_fixed() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let lossless_array = [true; MAX_SEGMENTS];
let prefix = walker
.decode_intra_frame_mode_info_prefix(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
false,
false,
/* seg_skip_active = */ &[true; MAX_SEGMENTS],
0,
&lossless_array,
false,
false,
false,
0,
false,
false,
0,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(prefix.ref_frame, [0, -1]);
}
/// §5.11.7 with `read_deltas = true` + `delta_lf_present = true`
/// drives the delta_qindex and delta_lf reads. Rig the
/// `delta_q_abs` cdf to force `0` (one `S()` read, no escape, no
/// sign bit ⇒ `CurrentQIndex` unchanged). Rig `delta_lf_abs` cdf
/// likewise. With base accumulators at `0`, the post-call values
/// stay at `0`.
///
/// Verifies that read_deltas-true wires through to BOTH reads.
#[test]
fn decode_intra_frame_mode_info_prefix_read_deltas_routes_through() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig skip cdf to force 0 (so delta_q / delta_lf paths fire;
// sub_size != sbSize anyway so they would too, but this
// keeps the path explicit).
let rigged_skip = force_binary_cdf(0);
for ctx_idx in 0..SKIP_CONTEXTS {
cdfs.skip[ctx_idx] = rigged_skip;
}
// Rig delta_q cdf to force symbol 0 (no escape, no sign bit
// ⇒ delta_q_abs = 0 ⇒ CurrentQIndex unchanged).
let mut rigged_delta_q = [0u16; DELTA_Q_SMALL + 2];
for slot in rigged_delta_q.iter_mut().take(DELTA_Q_SMALL + 1) {
*slot = 1 << 15;
}
cdfs.delta_q = rigged_delta_q;
// Same for delta_lf.
let mut rigged_delta_lf = [0u16; DELTA_LF_SMALL + 2];
for slot in rigged_delta_lf.iter_mut().take(DELTA_LF_SMALL + 1) {
*slot = 1 << 15;
}
cdfs.delta_lf = rigged_delta_lf;
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let pos_before = dec.position();
let prefix = walker
.decode_intra_frame_mode_info_prefix(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
false,
false,
&[false; MAX_SEGMENTS],
0,
&lossless_array,
false,
false,
false,
0,
/* read_deltas = */ true,
false,
/* delta_q_res = */ 0,
/* delta_lf_present = */ true,
/* delta_lf_multi = */ false,
false,
/* delta_lf_res = */ 0,
)
.unwrap();
assert_eq!(prefix.current_q_index, 0, "delta_q_abs = 0 ⇒ no change");
assert_eq!(
prefix.current_delta_lf, [0; FRAME_LF_COUNT],
"delta_lf_abs = 0 ⇒ no change",
);
// Three S() reads happened (skip + delta_q + delta_lf); the
// exact bit count depends on the decoder geometry but the
// position must have advanced at least 1.
assert!(dec.position() > pos_before, "S() reads advanced position");
}
/// §5.11.7 range guards on the dispatcher level fire before any
/// inner method is called. Checks out-of-range mi_row, mi_col,
/// sub_size, last_active_seg_id, and cdef_bits.
#[test]
fn decode_intra_frame_mode_info_prefix_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let call_with = |walker: &mut PartitionWalker,
dec: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
mi_row: u32,
mi_col: u32,
sub_size: usize,
last_active_seg_id: u8,
cdef_bits: u32|
-> Result<IntraFrameModeInfoPrefix, crate::Error> {
walker.decode_intra_frame_mode_info_prefix(
dec,
cdfs,
mi_row,
mi_col,
sub_size,
false,
false,
&[false; MAX_SEGMENTS],
last_active_seg_id,
&lossless_array,
false,
false,
false,
cdef_bits,
false,
false,
0,
false,
false,
false,
0,
)
};
assert_eq!(
call_with(&mut walker, &mut dec, &mut cdfs, 8, 0, BLOCK_4X4, 0, 0),
Err(crate::Error::PartitionWalkOutOfRange),
"mi_row >= MiRows",
);
assert_eq!(
call_with(&mut walker, &mut dec, &mut cdfs, 0, 8, BLOCK_4X4, 0, 0),
Err(crate::Error::PartitionWalkOutOfRange),
"mi_col >= MiCols",
);
assert_eq!(
call_with(&mut walker, &mut dec, &mut cdfs, 0, 0, BLOCK_SIZES, 0, 0),
Err(crate::Error::PartitionWalkOutOfRange),
"sub_size >= BLOCK_SIZES",
);
assert_eq!(
call_with(&mut walker, &mut dec, &mut cdfs, 0, 0, BLOCK_4X4, 8, 0),
Err(crate::Error::PartitionWalkOutOfRange),
"last_active_seg_id >= MAX_SEGMENTS",
);
assert_eq!(
call_with(&mut walker, &mut dec, &mut cdfs, 0, 0, BLOCK_4X4, 0, 4),
Err(crate::Error::PartitionWalkOutOfRange),
"cdef_bits > 3",
);
}
/// §5.11.7 verifies that on the pre-skip arm with
/// `segmentation_enabled = false` and `seg_skip_active = false`,
/// the §5.11.8 no-bit path is taken for segment_id, then
/// `read_skip` reads its `S()` bit. Validates ordering by
/// confirming the skip cdf adapted (was read) but the segment_id
/// cdf did not.
#[test]
fn decode_intra_frame_mode_info_prefix_skip_mode_field_always_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged_skip = force_binary_cdf(0);
for ctx_idx in 0..SKIP_CONTEXTS {
cdfs.skip[ctx_idx] = rigged_skip;
}
let bytes = [0u8; 8];
let lossless_array = [false; MAX_SEGMENTS];
// Both arms (pre-skip and post-skip) — skip_mode is always 0
// in either path because the dispatcher never calls
// `decode_skip_mode`. SkipModes[] grid stays at its
// construction default.
for &pre_skip in &[true, false] {
let mut walker_local = PartitionWalker::new(16, 16, geom).unwrap();
let mut dec_local = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let prefix = walker_local
.decode_intra_frame_mode_info_prefix(
&mut dec_local,
&mut cdfs,
0,
0,
BLOCK_8X8,
pre_skip,
/* segmentation_enabled = */ false,
/* seg_skip_active = */ &[false; MAX_SEGMENTS],
0,
&lossless_array,
false,
true,
false,
0,
false,
false,
0,
false,
false,
false,
0,
)
.unwrap();
assert_eq!(prefix.skip_mode, 0);
// §5.11.7 line 4 left SkipModes[] untouched.
assert_eq!(walker_local.skip_modes()[0], 0, "SkipModes[0][0] = 0");
}
}
// -----------------------------------------------------------------
// Round 164 — §5.11.7 `decode_use_intrabc` syntax-element tests.
// -----------------------------------------------------------------
/// §5.11.7 fall-through arm: `allow_intrabc = false` ⇒
/// `use_intrabc = 0` with zero bits consumed. The hostile `0xFF`
/// buffer must not be touched.
#[test]
fn decode_use_intrabc_allow_false_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let v = walker
.decode_use_intrabc(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* allow_intrabc = */ false,
)
.unwrap();
assert_eq!(v, 0, "allow_intrabc = false ⇒ use_intrabc = 0");
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read on the fall-through arm",
);
}
/// §5.11.7 `S()` arm with a rigged CDF forcing symbol 0
/// (`use_intrabc = 0`): the decoder advances past the S() symbol
/// and the returned value matches.
#[test]
fn decode_use_intrabc_allow_true_returns_symbol_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.intrabc = force_binary_cdf(0);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_use_intrabc(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* allow_intrabc = */ true,
)
.unwrap();
assert_eq!(v, 0, "rigged S() returns 0");
}
/// §5.11.7 `S()` arm with a rigged CDF forcing symbol 1
/// (`use_intrabc = 1`): the returned value matches.
#[test]
fn decode_use_intrabc_allow_true_returns_symbol_one() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.intrabc = force_binary_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_use_intrabc(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* allow_intrabc = */ true,
)
.unwrap();
assert_eq!(v, 1, "rigged S() returns 1");
}
/// §5.11.7 caller-bug guards fire before any read: an out-of-range
/// `sub_size` or anchor past the frame's mi extent yields
/// `PartitionWalkOutOfRange` without consuming any bit of the
/// hostile `0xFF` buffer.
#[test]
fn decode_use_intrabc_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
// Out-of-range sub_size (== BLOCK_SIZES).
assert_eq!(
walker.decode_use_intrabc(&mut dec, &mut cdfs, 0, 0, BLOCK_SIZES, true),
Err(crate::Error::PartitionWalkOutOfRange),
);
// mi_row past the frame extent.
assert_eq!(
walker.decode_use_intrabc(&mut dec, &mut cdfs, 16, 0, BLOCK_4X4, true),
Err(crate::Error::PartitionWalkOutOfRange),
);
// mi_col past the frame extent.
assert_eq!(
walker.decode_use_intrabc(&mut dec, &mut cdfs, 0, 16, BLOCK_4X4, true),
Err(crate::Error::PartitionWalkOutOfRange),
);
}
/// §5.11.7 `S()` arm uses the contextless `TileIntrabcCdf` — the
/// same CDF row is selected regardless of `(mi_row, mi_col)` or
/// `sub_size`. We verify by rigging `intrabc` to force symbol 1
/// and confirming the symbol fires from three distinct anchor /
/// size combinations.
#[test]
fn decode_use_intrabc_contextless_cdf_selection() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.intrabc = force_binary_cdf(1);
for &(r, c, sz) in &[
(0u32, 0u32, BLOCK_16X16),
(8, 12, BLOCK_8X8),
(20, 4, BLOCK_4X8),
] {
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let v = walker
.decode_use_intrabc(&mut dec, &mut cdfs, r, c, sz, true)
.unwrap();
assert_eq!(v, 1, "contextless selection at ({r}, {c}) sub_size={sz}",);
}
}
/// §8.3.1 init: `TileIntrabcCdf` is set to a copy of
/// `Default_Intrabc_Cdf`. The fresh-walker `intrabc` field must
/// equal the spec's `{ 30531, 32768, 0 }` listing, and the
/// `intrabc_cdf` accessor must return that same row.
#[test]
fn default_intrabc_cdf_layout_and_accessor() {
// §9.4 listing (av1-spec p.430).
assert_eq!(DEFAULT_INTRABC_CDF, [30531, 32768, 0]);
let mut cdfs = TileCdfContext::new_from_defaults();
assert_eq!(cdfs.intrabc, DEFAULT_INTRABC_CDF);
let accessor_view = cdfs.intrabc_cdf();
assert_eq!(accessor_view.len(), 3, "binary CDF row width");
assert_eq!(accessor_view[0], 30531);
assert_eq!(accessor_view[1], 32768);
assert_eq!(accessor_view[2], 0);
}
// -----------------------------------------------------------------
// Round 165 — §5.11.7 / §5.11.22 `decode_intra_frame_y_mode` tests.
// -----------------------------------------------------------------
/// §5.11.5 fresh `YModes[]` grid is filled with `DC_PRED == 0`
/// (the §3 intra-mode enumeration's ordinal-zero value, which
/// the §8.3.2 `intra_frame_y_mode` ctx walk's "neighbour
/// unavailable" arm also produces).
#[test]
fn fresh_walker_y_modes_grid_is_dc_pred() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
assert_eq!(walker.y_modes().len(), 16 * 16);
for &v in walker.y_modes() {
assert_eq!(v, 0, "fresh YModes[] = DC_PRED (== 0)");
}
}
/// Build a rigged `Default_Intra_Frame_Y_Mode_Cdf`-shaped row
/// (`INTRA_MODES + 1 = 14` entries) that forces the next §8.2.6
/// `S()` read to return `symbol ∈ 0..INTRA_MODES`. The
/// `read_symbol` decoder scans for the first symbol whose
/// cumulative `cur > 0` is strictly below `SymbolValue = 32767`;
/// setting every slot `< symbol` to `0` and every slot `>=
/// symbol` (up to the trailing `INTRA_MODES`-th, which is the
/// `1 << 15` cap) puts `cur = 0` for every smaller symbol and
/// the cap for the target.
fn force_intra_frame_y_mode_cdf(symbol: u8) -> [u16; INTRA_MODES + 1] {
assert!(
(symbol as usize) < INTRA_MODES,
"force_intra_frame_y_mode_cdf supports 0..INTRA_MODES only"
);
let mut row = [0u16; INTRA_MODES + 1];
for slot in row.iter_mut().take(INTRA_MODES).skip(symbol as usize) {
*slot = 1 << 15;
}
// The N-th slot (INTRA_MODES) is the §8.2.6 counter; leave at 0.
row
}
/// §5.11.7 / §5.11.22 caller-bug bounds: `sub_size >=
/// BLOCK_SIZES`, `mi_row >= MiRows`, `mi_col >= MiCols` all surface
/// `PartitionWalkOutOfRange` before any bit is read.
#[test]
fn decode_intra_frame_y_mode_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
assert!(matches!(
walker.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_SIZES),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
walker.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 16, 0, BLOCK_4X4),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
walker.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, 16, BLOCK_4X4),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert_eq!(
dec.position(),
pos_before,
"no symbol bit read on the bounds-rejection path"
);
}
/// §5.11.7 / §5.11.22 happy path with a rigged CDF forcing
/// `symbol = 0` (`DC_PRED`): the decoder returns the forced
/// symbol and writes it into the `YModes[]` grid over the
/// block's `bh4 * bw4` footprint.
#[test]
fn decode_intra_frame_y_mode_returns_symbol_zero_and_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force every (abovemode, leftmode) ctx pair to select symbol 0.
let rigged = force_intra_frame_y_mode_cdf(0);
for a in 0..INTRA_MODE_CONTEXTS {
for l in 0..INTRA_MODE_CONTEXTS {
cdfs.intra_frame_y_mode[a][l] = rigged;
}
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let y_mode = walker
.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16)
.unwrap();
assert_eq!(y_mode, 0, "rigged CDF returns DC_PRED");
// BLOCK_16X16 footprint is 4x4 mi cells; every cell carries 0.
let bw4 = NUM_4X4_BLOCKS_WIDE[BLOCK_16X16] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[BLOCK_16X16] as u32;
for dr in 0..bh4 {
for dc in 0..bw4 {
let idx = (dr * walker.mi_cols() + dc) as usize;
assert_eq!(
walker.y_modes()[idx],
0,
"footprint cell ({dr},{dc}) stamped with YMode=0"
);
}
}
}
/// §5.11.7 / §5.11.22 happy path with a rigged CDF forcing
/// `symbol = 12` (`PAETH_PRED`, the largest valid `YMode`): the
/// decoder returns the forced symbol and stamps it across the
/// block's footprint. Confirms the full 13-symbol range is
/// reachable through the accessor.
#[test]
fn decode_intra_frame_y_mode_returns_symbol_max_and_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_intra_frame_y_mode_cdf(12);
for a in 0..INTRA_MODE_CONTEXTS {
for l in 0..INTRA_MODE_CONTEXTS {
cdfs.intra_frame_y_mode[a][l] = rigged;
}
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let y_mode = walker
.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 4, 4, BLOCK_8X8)
.unwrap();
assert_eq!(y_mode, 12, "rigged CDF returns PAETH_PRED");
// BLOCK_8X8 footprint is 2x2 mi cells anchored at (4,4).
let bw4 = NUM_4X4_BLOCKS_WIDE[BLOCK_8X8] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[BLOCK_8X8] as u32;
for dr in 0..bh4 {
for dc in 0..bw4 {
let idx = ((4 + dr) * walker.mi_cols() + (4 + dc)) as usize;
assert_eq!(
walker.y_modes()[idx],
12,
"footprint cell stamped with YMode=12"
);
}
}
// A cell outside the footprint stays at its initial DC_PRED == 0.
assert_eq!(
walker.y_modes()[0],
0,
"non-footprint cell (0,0) remains DC_PRED"
);
}
/// §8.3.2 ctx derivation against §5.11.51 `AvailU` / `AvailL`:
/// the (0,0) top-left block always has both neighbours
/// unavailable, so `abovemode_ctx = leftmode_ctx =
/// Intra_Mode_Context[ DC_PRED == 0 ] = 0`. The CDF row used
/// must be `intra_frame_y_mode[0][0]`.
#[test]
fn decode_intra_frame_y_mode_corner_uses_dc_pred_neighbours() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig only the [0][0] row to return symbol 7 (SMOOTH_PRED);
// every other row keeps the §9.4 default. If the corner truly
// picks (0,0), the returned symbol is 7.
cdfs.intra_frame_y_mode[0][0] = force_intra_frame_y_mode_cdf(7);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let y_mode = walker
.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_4X4)
.unwrap();
assert_eq!(y_mode, 7, "top-left corner block routes through ctx (0, 0)");
}
/// §8.3.2 ctx derivation reads the §5.11.5-stamped `YModes[]`
/// grid: after a first block at (0,0) stamps `YMode = V_PRED = 1`
/// across its footprint, the next block at (0, bw4) sees that
/// value as its left neighbour, so `leftmode_ctx =
/// Intra_Mode_Context[ 1 ] = 1` (and `abovemode_ctx = 0`,
/// since `AvailU` is false on the top row). We verify by rigging
/// `intra_frame_y_mode[0][1]` to a distinctive symbol and
/// confirming the second call returns it.
#[test]
fn decode_intra_frame_y_mode_reads_neighbour_ymodes_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// First call at (0,0), BLOCK_8X8 (2x2 footprint), force YMode=1.
cdfs.intra_frame_y_mode[0][0] = force_intra_frame_y_mode_cdf(1);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let first = walker
.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_8X8)
.unwrap();
assert_eq!(first, 1, "first block stamps YMode=V_PRED=1");
// Now the second block at (0, bw4) sees the stamped value as its
// left neighbour; ctx = (abovemode=0, leftmode=1). Rig that
// exact row to return symbol 5 (D113_PRED); other rows
// (including the contaminated [0][0]) stay rigged at 1.
cdfs.intra_frame_y_mode[0][1] = force_intra_frame_y_mode_cdf(5);
let bw4 = NUM_4X4_BLOCKS_WIDE[BLOCK_8X8] as u32;
let second = walker
.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, bw4, BLOCK_8X8)
.unwrap();
assert_eq!(
second, 5,
"second block routes through ctx (abovemode=0, leftmode=1)"
);
}
/// §8.3.2 `Intra_Mode_Context[]` mapping is applied at ctx
/// derivation time, not at write time: stamping a `YMode = 4`
/// (`D203_PRED`) neighbour selects a ctx of `4` per the §8.3.2
/// table (`[0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]`). We rig
/// `intra_frame_y_mode[0][4]` to a distinctive symbol and confirm
/// the second call observes that row.
#[test]
fn decode_intra_frame_y_mode_applies_intra_mode_context_mapping() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// First block at (0,0), BLOCK_4X4, force YMode=4 (D203_PRED).
cdfs.intra_frame_y_mode[0][0] = force_intra_frame_y_mode_cdf(4);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let first = walker
.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, 0, BLOCK_4X4)
.unwrap();
assert_eq!(first, 4);
// Per §8.3.2 `Intra_Mode_Context[4] = 4`; the next block at
// (0, 1) (BLOCK_4X4 is 1x1 mi) should route through
// (abovemode=0, leftmode=4). Rig that row distinctively.
cdfs.intra_frame_y_mode[0][4] = force_intra_frame_y_mode_cdf(9);
let second = walker
.decode_intra_frame_y_mode(&mut dec, &mut cdfs, 0, 1, BLOCK_4X4)
.unwrap();
assert_eq!(
second, 9,
"Intra_Mode_Context[4] = 4 routes second block to ctx (0, 4)"
);
}
/// §5.11.7 `decode_use_intrabc` is **stateless** in the §5.11.5
/// grid sense: unlike `decode_skip` / `decode_skip_mode` /
/// `decode_is_inter`, the function writes nothing to
/// `Skips[]` / `SkipModes[]` / `IsInters[]` / `SegmentIds[]`.
/// AV1 has no per-block `UseIntrabc[][]` map — the value is
/// consumed locally by the §5.11.7 follow-on arm. We verify the
/// walker grids are unchanged after the call.
#[test]
fn decode_use_intrabc_does_not_stamp_grids() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.intrabc = force_binary_cdf(1);
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let _ = walker
.decode_use_intrabc(&mut dec, &mut cdfs, 0, 0, BLOCK_16X16, true)
.unwrap();
// The walker exposes its §5.11.5 grids via the existing
// accessors; none of them should carry a non-zero value after
// a use_intrabc call.
for &v in walker.skips() {
assert_eq!(v, 0, "decode_use_intrabc must not write Skips[]");
}
for &v in walker.skip_modes() {
assert_eq!(v, 0, "decode_use_intrabc must not write SkipModes[]");
}
for &v in walker.is_inters() {
assert_eq!(v, 0, "decode_use_intrabc must not write IsInters[]");
}
}
// -----------------------------------------------------------------
// Round 162 — §5.11.19 `inter_segment_id( preSkip )` tests.
// -----------------------------------------------------------------
/// Fresh walker exposes zeroed §5.11.19 / §8.3.2
/// `AboveSegPredContext[]` and `LeftSegPredContext[]` arrays of
/// length `MiCols` and `MiRows` respectively.
#[test]
fn fresh_walker_seg_pred_context_is_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
assert_eq!(walker.above_seg_pred_context().len(), 16);
assert_eq!(walker.left_seg_pred_context().len(), 16);
for &slot in walker.above_seg_pred_context() {
assert_eq!(slot, 0, "fresh AboveSegPredContext[] = 0");
}
for &slot in walker.left_seg_pred_context() {
assert_eq!(slot, 0, "fresh LeftSegPredContext[] = 0");
}
}
/// §5.11.19 `!segmentation_enabled` outer branch: `segment_id = 0`
/// with no bits read; grid stamped over the block footprint; the
/// `seg_pred_context` arrays stay untouched.
#[test]
fn decode_inter_segment_id_segmentation_disabled_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[0] = true;
for &pre_skip in &[true, false] {
let (sid, lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
pre_skip,
0,
/* seg_id_pre_skip = */ true,
/* segmentation_enabled = */ false,
/* segmentation_update_map = */ true,
/* segmentation_temporal_update = */ true,
/* predicted_segment_id = */ 5,
/* last_active_seg_id = */ 7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0, "!segmentation_enabled ⇒ segment_id = 0");
assert!(lossless, "Lossless = LosslessArray[0] = true");
assert_eq!(dec.position(), pos_before, "no bits consumed");
for &slot in walker.above_seg_pred_context() {
assert_eq!(slot, 0, "seg_pred context untouched");
}
for &slot in walker.left_seg_pred_context() {
assert_eq!(slot, 0, "seg_pred context untouched");
}
}
// Grid-fill: footprint of BLOCK_8X8 (2×2) at (0, 0) is stamped.
for r in 0..2 {
for c in 0..2 {
assert_eq!(walker.segment_ids()[r * 16 + c], 0);
}
}
assert_eq!(walker.segment_ids()[2 * 16], -1);
}
/// §5.11.19 `!segmentation_update_map` inner branch:
/// `segment_id = predictedSegmentId` with no bits read; grid
/// stamped over the footprint; the `seg_pred_context` arrays stay
/// untouched. `Lossless = LosslessArray[ predictedSegmentId ]`.
#[test]
fn decode_inter_segment_id_no_update_map_adopts_predicted() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[3] = true;
let (sid, lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* pre_skip = */ true,
0,
/* seg_id_pre_skip = */ true,
/* segmentation_enabled = */ true,
/* segmentation_update_map = */ false,
/* segmentation_temporal_update = */ true,
/* predicted_segment_id = */ 3,
/* last_active_seg_id = */ 7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 3, "predictedSegmentId adopted on !update_map");
assert!(lossless, "Lossless = LosslessArray[3] = true");
assert_eq!(dec.position(), pos_before, "no bits consumed");
for &slot in walker.above_seg_pred_context() {
assert_eq!(slot, 0);
}
for &slot in walker.left_seg_pred_context() {
assert_eq!(slot, 0);
}
// Grid-fill: footprint stamped with predicted id 3.
assert_eq!(walker.segment_ids()[0], 3);
assert_eq!(walker.segment_ids()[1], 3);
assert_eq!(walker.segment_ids()[16], 3);
assert_eq!(walker.segment_ids()[17], 3);
assert_eq!(walker.segment_ids()[2 * 16], -1);
}
/// §5.11.19 `if ( preSkip && !SegIdPreSkip ) { segment_id = 0;
/// return }` early-exit: the pre-skip caller sees `segment_id = 0`
/// with no bits read; context arrays untouched; grid stamped to 0.
#[test]
fn decode_inter_segment_id_pre_skip_with_post_skip_pre_skip_flag_returns_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[0] = true;
let (sid, lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* pre_skip = */ true,
0,
/* seg_id_pre_skip = */ false,
true,
true,
true,
/* predicted_segment_id = */ 4,
7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0, "pre_skip && !SegIdPreSkip ⇒ segment_id = 0");
assert!(lossless);
assert_eq!(dec.position(), pos_before, "no bits consumed");
for &slot in walker.above_seg_pred_context() {
assert_eq!(slot, 0);
}
for &slot in walker.left_seg_pred_context() {
assert_eq!(slot, 0);
}
// Grid stamped to 0 so subsequent neighbour lookups see a
// real zero rather than the -1 sentinel.
assert_eq!(walker.segment_ids()[0], 0);
assert_eq!(walker.segment_ids()[17], 0);
}
/// §5.11.19 `if ( !preSkip ) if ( skip ) { seg_id_predicted = 0;
/// stamp arrays; read_segment_id(); return }`: post-skip arm with
/// `skip = 1`. `seg_id_predicted = 0` so the context arrays are
/// stamped to 0 (their initial value, but the explicit write is
/// observable on a non-origin block — verify by pre-poisoning).
/// The §5.11.9 skip short-circuit fires inside `decode_segment_id`
/// (no `S()` read; `segment_id = pred = 0` at the frame origin).
#[test]
fn decode_inter_segment_id_post_skip_with_skip_clears_context_and_short_circuits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Poison the context arrays so the explicit `seg_id_predicted
// = 0` write is observable (otherwise the initial-zero state
// would mask the spec write).
for slot in walker.above_seg_pred_context.iter_mut() {
*slot = 1;
}
for slot in walker.left_seg_pred_context.iter_mut() {
*slot = 1;
}
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let lossless_array = [true; MAX_SEGMENTS];
let (sid, lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* pre_skip = */ false,
/* skip = */ 1,
/* seg_id_pre_skip = */ false,
true,
true,
true,
/* predicted_segment_id = */ 5,
7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 0, "§5.11.9 skip-short-circuit: pred = 0 at origin");
assert!(lossless);
assert_eq!(
dec.position(),
pos_before,
"no `S()` read on the inner skip-short-circuit",
);
// Block footprint (BLOCK_8X8 = 2x2) re-stamped to 0.
for i in 0..2 {
assert_eq!(walker.above_seg_pred_context()[i], 0);
assert_eq!(walker.left_seg_pred_context()[i], 0);
}
// Columns / rows outside the footprint retain the poison.
assert_eq!(walker.above_seg_pred_context()[2], 1);
assert_eq!(walker.left_seg_pred_context()[2], 1);
}
/// §5.11.19 `temporal_update == 1, seg_id_predicted == 1` arm:
/// reads one `S()` against the rigged binary cdf forcing the `1`
/// symbol, then adopts `predictedSegmentId` with no further read.
/// Context arrays stamped to 1 over the footprint.
#[test]
fn decode_inter_segment_id_temporal_update_predicted_adopts_predicted_id() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig every ctx row to force `seg_id_predicted = 1`.
let rigged = force_binary_cdf(1);
for ctx_idx in 0..SEGMENT_ID_PREDICTED_CONTEXTS {
cdfs.segment_id_predicted[ctx_idx] = rigged;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[6] = true;
let (sid, lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* pre_skip = */ true,
0,
/* seg_id_pre_skip = */ true,
true,
true,
/* segmentation_temporal_update = */ true,
/* predicted_segment_id = */ 6,
7,
&lossless_array,
)
.unwrap();
assert_eq!(
sid, 6,
"seg_id_predicted = 1 ⇒ segment_id = predictedSegmentId"
);
assert!(lossless, "Lossless = LosslessArray[6] = true");
// Context arrays stamped to 1 over BLOCK_8X8 (2x2).
for i in 0..2 {
assert_eq!(walker.above_seg_pred_context()[i], 1);
assert_eq!(walker.left_seg_pred_context()[i], 1);
}
// Grid stamped with id = 6 — the §5.11.19 `seg_id_predicted ==
// 1` arm adopts predictedSegmentId without descending into
// `read_segment_id()`, so no §5.11.5 grid stamp from the
// inner call; the helper's explicit `stamp_segment_id_grid`
// covers it.
assert_eq!(walker.segment_ids()[0], 6);
assert_eq!(walker.segment_ids()[17], 6);
}
/// §5.11.19 `temporal_update == 1, seg_id_predicted == 0` arm:
/// reads the binary `S()` returning 0, then descends into
/// `read_segment_id()` (the §5.11.9 path). Context arrays stamped
/// to 0. The exact `decode_segment_id` return depends on the
/// chained arithmetic-coder state after the binary read; the
/// invariants are (i) `sid` is in the `0..=last_active_seg_id`
/// range, (ii) the `seg_id_predicted == 0` context-array write
/// over the block footprint is observable (we poison the arrays
/// first), and (iii) the grid was stamped over the footprint.
#[test]
fn decode_inter_segment_id_temporal_update_unpredicted_reads_segment_id() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig seg_id_predicted to `0`.
let rigged_pred = force_binary_cdf(0);
for ctx_idx in 0..SEGMENT_ID_PREDICTED_CONTEXTS {
cdfs.segment_id_predicted[ctx_idx] = rigged_pred;
}
// Poison context arrays so the explicit `0` write is visible.
for slot in walker.above_seg_pred_context.iter_mut() {
*slot = 1;
}
for slot in walker.left_seg_pred_context.iter_mut() {
*slot = 1;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let (sid, _lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* pre_skip = */ true,
0,
/* seg_id_pre_skip = */ true,
true,
true,
/* segmentation_temporal_update = */ true,
/* predicted_segment_id = */ 2,
/* last_active_seg_id = */ 7,
&lossless_array,
)
.unwrap();
// Invariant (i): sid is bounded.
assert!(sid <= 7, "sid = {sid} > last_active_seg_id");
// Invariant (ii): context arrays stamped to 0 over BLOCK_8X8.
for i in 0..2 {
assert_eq!(
walker.above_seg_pred_context()[i],
0,
"AboveSegPredContext[{i}] = 0 from seg_id_predicted write",
);
assert_eq!(
walker.left_seg_pred_context()[i],
0,
"LeftSegPredContext[{i}] = 0 from seg_id_predicted write",
);
}
// Columns / rows outside the footprint retain the poison.
assert_eq!(walker.above_seg_pred_context()[2], 1);
assert_eq!(walker.left_seg_pred_context()[2], 1);
// Invariant (iii): grid stamped over footprint — the
// descent into `decode_segment_id` did write the cells.
assert_ne!(
walker.segment_ids()[0],
-1,
"segment_ids[0] stamped (not the -1 sentinel)",
);
assert_eq!(walker.segment_ids()[0], sid as i32);
}
/// §5.11.19 `temporal_update == 0` fall-through: straight
/// `read_segment_id()` call; context arrays untouched per spec
/// (the writes happen only on the two arms above).
#[test]
fn decode_inter_segment_id_no_temporal_update_reads_segment_id_only() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let rigged = force_segment_id_cdf(2);
for ctx_idx in 0..SEGMENT_ID_CONTEXTS {
cdfs.segment_id[ctx_idx] = rigged;
}
// Poison context arrays — should remain poisoned (no spec
// write on this arm).
for slot in walker.above_seg_pred_context.iter_mut() {
*slot = 1;
}
for slot in walker.left_seg_pred_context.iter_mut() {
*slot = 1;
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
let (sid, _lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* pre_skip = */ true,
0,
/* seg_id_pre_skip = */ true,
true,
true,
/* segmentation_temporal_update = */ false,
/* predicted_segment_id = */ 1,
7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 2);
// Context arrays unchanged from the poison value (the spec
// writes them only on the `!preSkip && skip` arm and the
// `temporal_update == 1` arm).
for &slot in walker.above_seg_pred_context() {
assert_eq!(slot, 1, "no spec write on the no-temporal-update arm");
}
for &slot in walker.left_seg_pred_context() {
assert_eq!(slot, 1, "no spec write on the no-temporal-update arm");
}
}
/// §5.11.19 § range guards fire on all arms (out-of-range
/// `sub_size`, `mi_row` / `mi_col`, `last_active_seg_id`, and the
/// `predicted_segment_id > last_active_seg_id` invariant) without
/// reading the bitstream.
#[test]
fn decode_inter_segment_id_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
let lossless_array = [false; MAX_SEGMENTS];
// mi_row out of range.
assert_eq!(
walker.decode_inter_segment_id(
&mut dec,
&mut cdfs,
8,
0,
BLOCK_4X4,
true,
0,
true,
true,
true,
false,
0,
0,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange),
);
// mi_col out of range.
assert_eq!(
walker.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
8,
BLOCK_4X4,
true,
0,
true,
true,
true,
false,
0,
0,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange),
);
// sub_size out of range.
assert_eq!(
walker.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
true,
0,
true,
true,
true,
false,
0,
0,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange),
);
// last_active_seg_id >= MAX_SEGMENTS.
assert_eq!(
walker.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
true,
0,
true,
true,
true,
false,
0,
8,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange),
);
// predicted_segment_id > last_active_seg_id.
assert_eq!(
walker.decode_inter_segment_id(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
true,
0,
true,
true,
true,
false,
5,
4,
&lossless_array,
),
Err(crate::Error::PartitionWalkOutOfRange),
);
}
/// §8.3.1 init: `TileSegmentIdPredictedCdf` is set to a copy of
/// `Default_Segment_Id_Predicted_Cdf` per av1-spec p.155. Verify
/// the field is laid out per §9.4 (three rows of `[16384, 32768,
/// 0]`).
#[test]
fn default_segment_id_predicted_cdf_layout() {
let ctx = TileCdfContext::new_from_defaults();
for ctx_idx in 0..SEGMENT_ID_PREDICTED_CONTEXTS {
assert_eq!(ctx.segment_id_predicted[ctx_idx], [16384, 32768, 0]);
}
}
/// §8.3.2 selector: `segment_id_predicted_cdf(ctx)` returns the
/// `ctx`-th row of the working CDF. Mutating through the returned
/// reference is observable.
#[test]
fn segment_id_predicted_cdf_accessor_round_trip() {
let mut ctx = TileCdfContext::new_from_defaults();
for i in 0..SEGMENT_ID_PREDICTED_CONTEXTS {
let row = ctx.segment_id_predicted_cdf(i);
row[0] = (10_000u16 + i as u16).wrapping_add(0);
}
for i in 0..SEGMENT_ID_PREDICTED_CONTEXTS {
assert_eq!(ctx.segment_id_predicted[i][0], 10_000u16 + i as u16);
}
}
// §5.11.21 `get_segment_id()` (av1-spec p.72) free-function tests.
/// A uniform `PrevSegmentIds[][]` of `0` returns `0` regardless of
/// block position or size — the §5.11.21 `Min` reduction over the
/// whole window finds `0 < 7`, then never beats it.
#[test]
fn get_segment_id_uniform_zero_returns_zero() {
let prev = vec![0i32; 8 * 8];
for sub in &[BLOCK_4X4, BLOCK_8X8, BLOCK_16X16, BLOCK_32X32] {
let v = get_segment_id(&prev, 8, 8, 8, 8, 0, 0, *sub).unwrap();
assert_eq!(v, 0, "uniform-0 prev grid must reduce to 0 for sub={sub}");
}
}
/// A uniform `PrevSegmentIds[][]` of `7` returns `7` — the
/// §5.11.21 sentinel `seg = 7` never gets beaten.
#[test]
fn get_segment_id_uniform_seven_returns_sentinel() {
let prev = vec![7i32; 4 * 4];
let v = get_segment_id(&prev, 4, 4, 4, 4, 0, 0, BLOCK_4X4).unwrap();
assert_eq!(v, 7);
// Also for a BLOCK_8X8 anchored at (0,0) covering 2x2 = 4 cells
// of the 4x4 prev grid; still 7.
let v8 = get_segment_id(&prev, 4, 4, 4, 4, 0, 0, BLOCK_8X8).unwrap();
assert_eq!(v8, 7);
}
/// The window reduction takes the **minimum** of the covered
/// cells. A `BLOCK_8X8` (bw4 = bh4 = 2) at (0,0) over a prev grid
/// whose top-left 2x2 holds `[5, 3, 4, 2]` (row-major) must return
/// `2` — the §5.11.21 `Min` over those four cells. Cells outside
/// the 2x2 window (positions `(0, 2..)`, `(1, 2..)`, `(2.., *)`)
/// must not contribute, even if they hold smaller values.
#[test]
fn get_segment_id_window_min_over_block_8x8() {
let mut prev = vec![7i32; 4 * 4];
let idx = |r: usize, c: usize| r * 4 + c;
// Window cells (top-left 2x2):
prev[idx(0, 0)] = 5;
prev[idx(0, 1)] = 3;
prev[idx(1, 0)] = 4;
prev[idx(1, 1)] = 2;
// Out-of-window decoy cells: smaller than the window min but
// must NOT contribute, proving the bw4/bh4 clipping bounds.
prev[idx(0, 2)] = 0;
prev[idx(2, 0)] = 0;
prev[idx(2, 2)] = 0;
prev[idx(3, 3)] = 0;
let v = get_segment_id(&prev, 4, 4, 4, 4, 0, 0, BLOCK_8X8).unwrap();
assert_eq!(v, 2);
}
/// Frame-edge clipping: a `BLOCK_16X16` (bw4 = bh4 = 4) anchored
/// at `(mi_row = 2, mi_col = 2)` of a 4x4 mi-grid covers
/// `xMis = Min(4 - 2, 4) = 2` and `yMis = Min(4 - 2, 4) = 2`
/// cells — only the bottom-right 2x2 of the prev grid contributes,
/// NOT the (uncovered, off-frame) cells the bw4/bh4 product would
/// imply.
#[test]
fn get_segment_id_clips_x_mis_y_mis_to_frame_edge() {
// Fill the prev grid such that only the bottom-right 2x2 sub-
// grid (positions (2,2)..(4,4)) holds the actual minimum;
// top-left cells hold a smaller "decoy" that must be ignored
// (the §5.11.21 walk starts at MiRow=2, MiCol=2, never visits
// (0,0)).
let mut prev = vec![7i32; 4 * 4];
let idx = |r: usize, c: usize| r * 4 + c;
prev[idx(0, 0)] = 0; // decoy — out of window
prev[idx(2, 2)] = 3;
prev[idx(2, 3)] = 5;
prev[idx(3, 2)] = 4;
prev[idx(3, 3)] = 6;
let v = get_segment_id(&prev, 4, 4, 4, 4, 2, 2, BLOCK_16X16).unwrap();
// Min over the bottom-right 2x2 = Min(3,5,4,6) = 3, NOT 0
// (the (0,0) decoy never enters the reduction).
assert_eq!(v, 3);
}
/// The `-1` sentinel (the [`PartitionWalker::segment_ids`]
/// initial-state value for a not-yet-decoded cell of the previous
/// frame's walker) round-trips: a `-1` cell inside the window
/// wins the `Min` reduction. The §5.11.19 caller's
/// `predicted_segment_id > last_active_seg_id` guard fires on the
/// returned `-1` (cast to `u8` becomes `255`), so callers will
/// reject this as an invariant violation rather than feed it
/// blindly into `decode_inter_segment_id`.
#[test]
fn get_segment_id_surfaces_minus_one_sentinel() {
let mut prev = vec![7i32; 2 * 2];
prev[0] = -1;
let v = get_segment_id(&prev, 2, 2, 2, 2, 0, 0, BLOCK_4X4).unwrap();
assert_eq!(v, -1);
}
/// The `prev_mi_cols` extent governs the row stride into
/// `prev_segment_ids`. A previous frame that is **wider** than
/// the current frame must still index correctly: the current
/// frame's `(mi_row, mi_col)` is unchanged, but each prev-frame
/// row spans `prev_mi_cols` cells.
#[test]
fn get_segment_id_handles_wider_prev_frame() {
// current frame: 2 rows x 2 cols. prev frame: 2 rows x 4 cols.
let mut prev = vec![7i32; 2 * 4];
// Prev row 0: [7, 7, 7, 7]; Prev row 1: [2, 7, 7, 7]
let idx = |r: usize, c: usize| r * 4 + c;
prev[idx(1, 0)] = 2;
let v = get_segment_id(&prev, 2, 4, 2, 2, 0, 0, BLOCK_8X8).unwrap();
// BLOCK_8X8 = bw4 = bh4 = 2, covers (0..2, 0..2) of prev =
// [7, 7, 2, 7] = Min = 2.
assert_eq!(v, 2);
}
/// A single-cell `BLOCK_4X4` window touches exactly one prev
/// cell — the value at `prev[MiRow][MiCol]`. No averaging, no
/// neighbour contribution.
#[test]
fn get_segment_id_single_cell_block_4x4() {
let mut prev = vec![7i32; 4 * 4];
let idx = |r: usize, c: usize| r * 4 + c;
prev[idx(2, 1)] = 3;
let v = get_segment_id(&prev, 4, 4, 4, 4, 2, 1, BLOCK_4X4).unwrap();
assert_eq!(v, 3);
// Neighbour cell must not contribute:
prev[idx(2, 2)] = 0; // adjacent to (2,1) but outside the 1x1 window
let v2 = get_segment_id(&prev, 4, 4, 4, 4, 2, 1, BLOCK_4X4).unwrap();
assert_eq!(v2, 3);
}
/// Out-of-range `sub_size` rejected.
#[test]
fn get_segment_id_rejects_invalid_sub_size() {
let prev = vec![0i32; 4 * 4];
assert!(get_segment_id(&prev, 4, 4, 4, 4, 0, 0, BLOCK_SIZES).is_none());
assert!(get_segment_id(&prev, 4, 4, 4, 4, 0, 0, BLOCK_SIZES + 1).is_none());
}
/// Out-of-range current-frame anchor rejected. `mi_row == mi_rows`
/// or `mi_col == mi_cols` is past the frame's mi extent.
#[test]
fn get_segment_id_rejects_anchor_outside_current_frame() {
let prev = vec![0i32; 4 * 4];
assert!(get_segment_id(&prev, 4, 4, 4, 4, 4, 0, BLOCK_4X4).is_none());
assert!(get_segment_id(&prev, 4, 4, 4, 4, 0, 4, BLOCK_4X4).is_none());
assert!(get_segment_id(&prev, 4, 4, 4, 4, 5, 5, BLOCK_4X4).is_none());
}
/// Previous-frame extent smaller than the current frame's →
/// rejected. The §6.10 reference-frame walk would have produced
/// an inconsistent surface in that case.
#[test]
fn get_segment_id_rejects_prev_extent_smaller_than_current() {
let prev = vec![0i32; 2 * 2];
assert!(get_segment_id(&prev, 2, 2, 4, 4, 0, 0, BLOCK_4X4).is_none());
assert!(get_segment_id(&prev, 2, 4, 4, 4, 0, 0, BLOCK_4X4).is_none());
assert!(get_segment_id(&prev, 4, 2, 4, 4, 0, 0, BLOCK_4X4).is_none());
}
/// `prev_segment_ids.len()` not matching `prev_mi_rows *
/// prev_mi_cols` → rejected.
#[test]
fn get_segment_id_rejects_prev_length_mismatch() {
let prev_short = vec![0i32; 3]; // declared 2x2 = 4
assert!(get_segment_id(&prev_short, 2, 2, 2, 2, 0, 0, BLOCK_4X4).is_none());
let prev_long = vec![0i32; 5]; // declared 2x2 = 4
assert!(get_segment_id(&prev_long, 2, 2, 2, 2, 0, 0, BLOCK_4X4).is_none());
}
/// The return value composes with the §5.11.19 caller axis: a
/// `get_segment_id` value of `0` fed into
/// `decode_inter_segment_id` on the `!segmentation_update_map`
/// arm is adopted as the literal `segment_id` with no bit reads.
/// This proves the §5.11.19 → §5.11.21 wiring is correct end-to-
/// end on the no-read happy path.
#[test]
fn get_segment_id_composes_with_decode_inter_segment_id_no_read_path() {
let prev = vec![3i32; 4 * 4];
let predicted = get_segment_id(&prev, 4, 4, 4, 4, 0, 0, BLOCK_4X4).unwrap();
assert_eq!(predicted, 3);
// Now feed `predicted` into decode_inter_segment_id on the
// `!segmentation_update_map` arm; expect the function to
// return `(3, lossless_array[3])` with zero bits consumed.
let mi_rows: u32 = 4;
let mi_cols: u32 = 4;
let geometry = TileGeometry {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
};
let mut walker = PartitionWalker::new(mi_rows, mi_cols, geometry).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Hostile buffer the function must NOT touch on this arm.
let buf = [0xFFu8; 4];
let mut dec =
crate::symbol_decoder::SymbolDecoder::init_symbol(&buf, buf.len(), false).unwrap();
let mut lossless_array = [false; MAX_SEGMENTS];
lossless_array[3] = true;
let (sid, lossless) = walker
.decode_inter_segment_id(
&mut dec,
&mut cdfs,
/* mi_row = */ 0,
/* mi_col = */ 0,
BLOCK_4X4,
/* pre_skip = */ true,
/* skip = */ 0,
/* seg_id_pre_skip = */ true,
/* segmentation_enabled = */ true,
/* segmentation_update_map = */ false,
/* segmentation_temporal_update = */ false,
predicted as u8,
/* last_active_seg_id = */ 7,
&lossless_array,
)
.unwrap();
assert_eq!(sid, 3, "no-update-map arm must adopt predicted id");
assert!(lossless, "lossless table lookup must use the adopted id");
}
// ===================================================================
// §5.11.22 decode_intra_block_mode_info — direct-call coverage (r169).
// ===================================================================
/// Build a `BLOCK_SIZES`-wide rigged CDF that forces an `S()` read
/// against a row of width `WIDTH` to return `symbol`.
fn rigged_row<const N: usize>(symbol: u8) -> [u16; N] {
let nm1 = N - 1;
assert!((symbol as usize) < nm1, "symbol out of row range");
let mut row = [0u16; N];
for slot in row.iter_mut().take(nm1).skip(symbol as usize) {
*slot = 1 << 15;
}
// The N-th slot stays at 0 (the §8.2.6 counter).
row
}
/// Helper: build a §5.11.22-ready walker + cdfs + a 32-byte
/// zero-buffer SymbolDecoder.
fn intra_block_mode_info_fixture() -> (PartitionWalker, TileCdfContext, [u8; 32]) {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let walker = PartitionWalker::new(32, 32, geom).unwrap();
let cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 32];
(walker, cdfs, bytes)
}
/// §5.11.22 caller-bug guards: out-of-range `sub_size` / `mi_row` /
/// `mi_col` surface `PartitionWalkOutOfRange` before any bit is
/// read.
#[test]
fn decode_intra_block_mode_info_rejects_out_of_range() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
assert!(matches!(
walker.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
false,
true,
false,
false,
false,
false,
false,
false,
8,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
walker.decode_intra_block_mode_info(
&mut dec, &mut cdfs, 32, 0, BLOCK_4X4, false, true, false, false, false, false,
false, false, 8,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert!(matches!(
walker.decode_intra_block_mode_info(
&mut dec, &mut cdfs, 0, 32, BLOCK_4X4, false, true, false, false, false, false,
false, false, 8,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
assert_eq!(
dec.position(),
pos_before,
"no bit read on the bounds-rejection path"
);
}
/// §5.11.22 happy path: rig every CDF the §5.11.22 body might read
/// against to force `y_mode = 0 = DC_PRED` (non-directional ⇒ no
/// angle-delta-y read), `uv_mode = 0 = DC_PRED` (also
/// non-directional ⇒ no angle-delta-uv read and not UV_CFL_PRED ⇒
/// no read_cfl_alphas), with palette + filter-intra gates OFF.
/// The reader returns successfully with every leaf in its
/// short-circuit value.
#[test]
fn decode_intra_block_mode_info_dc_pred_happy_path_all_gates_off() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
// Rig y_mode rows (BLOCK_SIZE_GROUPS) → symbol 0.
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0);
}
// Rig both uv_mode tables → symbol 0.
for row in cdfs.uv_mode_cfl_not_allowed.iter_mut() {
*row = rigged_row::<{ UV_INTRA_MODES_CFL_NOT_ALLOWED + 1 }>(0);
}
for row in cdfs.uv_mode_cfl_allowed.iter_mut() {
*row = rigged_row::<{ UV_INTRA_MODES_CFL_ALLOWED + 1 }>(0);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
/* mi_row = */ 4,
/* mi_col = */ 4,
/* sub_size = */ BLOCK_16X16,
/* lossless = */ false,
/* has_chroma = */ true,
/* allow_screen_content_tools = */ false,
/* enable_filter_intra = */ false,
/* subsampling_x = */ false,
/* subsampling_y = */ false,
/* above_palette_y = */ false,
/* left_palette_y = */ false,
/* bit_depth = */ 8,
)
.unwrap();
assert_eq!(info.y_mode, 0, "rigged y_mode = DC_PRED");
assert_eq!(info.ref_frame, [0, -1], "INTRA_FRAME / NONE");
// DC_PRED is not directional → angle_delta_y short-circuit.
assert_eq!(info.angle_delta_y, 0);
// HasChroma = true → uv_mode is read.
assert_eq!(info.uv_mode, Some(0));
// DC_PRED is not directional → angle_delta_uv short-circuit
// also returns 0 (the §5.11.43 spec body initialises
// AngleDeltaUV = 0 unconditionally in the HasChroma arm).
assert_eq!(info.angle_delta_uv, Some(0));
// UVMode != UV_CFL_PRED → cfl_alphas not read.
assert_eq!(info.cfl_alpha_u, None);
assert_eq!(info.cfl_alpha_v, None);
// Palette + filter-intra gates off → all None.
assert_eq!(info.has_palette_y, None);
assert_eq!(info.palette_size_y, None);
assert_eq!(info.has_palette_uv, None);
assert_eq!(info.palette_size_uv, None);
assert_eq!(info.use_filter_intra, None);
assert_eq!(info.filter_intra_mode, None);
// Footprint of BLOCK_16X16 is 4×4 mi cells anchored at (4,4).
let bw4 = NUM_4X4_BLOCKS_WIDE[BLOCK_16X16] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[BLOCK_16X16] as u32;
for dr in 0..bh4 {
for dc in 0..bw4 {
let idx = ((4 + dr) * walker.mi_cols() + (4 + dc)) as usize;
assert_eq!(walker.y_modes()[idx], 0, "footprint stamped with YMode=0");
}
}
}
/// §5.11.22 HasChroma == false short-circuit: uv_mode /
/// cfl_alpha_* / angle_delta_uv all stay `None`. Useful for
/// monochrome streams where the §5.11.5 `HasChroma = NumPlanes > 1`
/// derivation yields 0. The §5.11.22 reader only consumes the
/// y_mode bit(s); the bit position must advance past the §8.2.6
/// initial state.
#[test]
fn decode_intra_block_mode_info_monochrome_skips_chroma_reads() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
/* has_chroma = */ false,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
let pos_after = dec.position();
// Only the y_mode S() bit(s) consumed.
assert!(
pos_after > pos_before,
"y_mode S() advanced the bit position"
);
assert_eq!(info.y_mode, 0);
assert_eq!(info.uv_mode, None);
assert_eq!(info.cfl_alpha_u, None);
assert_eq!(info.cfl_alpha_v, None);
assert_eq!(info.angle_delta_uv, None);
// Palette / filter-intra gates also off.
assert_eq!(info.has_palette_y, None);
assert_eq!(info.has_palette_uv, None);
assert_eq!(info.use_filter_intra, None);
}
/// §5.11.42 angle_delta_y arm structure: a non-directional `YMode`
/// (e.g. DC_PRED) plus a `MiSize >= BLOCK_8X8` block must
/// short-circuit to `AngleDeltaY = 0` without an `S()` read; the
/// reader's behaviour is verified by comparing the bit position
/// before/after a HasChroma=false run with rigged y_mode = 0.
#[test]
fn decode_intra_block_mode_info_dc_pred_skips_angle_delta_y() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
false,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.angle_delta_y, 0, "DC_PRED → §5.11.42 short-circuit");
}
/// §5.11.42 angle_delta_y small-block short-circuit: `MiSize <
/// BLOCK_8X8` also forces `AngleDeltaY = 0` regardless of `YMode`.
/// BLOCK_4X4 is the smallest size and exercises the
/// `sub_size >= BLOCK_8X8` outer guard.
#[test]
fn decode_intra_block_mode_info_small_block_skips_angle_delta_y() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
// Rig y_mode to V_PRED (directional, would normally trigger
// angle-delta read) but the BLOCK_4X4 size short-circuits.
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(V_PRED as u8);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec, &mut cdfs, 0, 0, BLOCK_4X4, false, false, false, false, false, false,
false, false, 8,
)
.unwrap();
assert_eq!(info.y_mode, V_PRED as u8);
assert_eq!(
info.angle_delta_y, 0,
"BLOCK_4X4 < BLOCK_8X8 → §5.11.42 short-circuit"
);
}
/// §5.11.46 outer-gate short-circuit: with
/// `allow_screen_content_tools = false`, the palette path is not
/// reached regardless of `YMode == DC_PRED`. `has_palette_*` stay
/// `None`. Verified by running on a DC_PRED block with the gate
/// off.
#[test]
fn decode_intra_block_mode_info_palette_gate_off_skips_palette_reads() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0); // DC_PRED
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
false,
/* allow_screen_content_tools = */ false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.has_palette_y, None);
assert_eq!(info.palette_size_y, None);
assert_eq!(info.has_palette_uv, None);
assert_eq!(info.palette_size_uv, None);
}
/// §5.11.46 outer-gate block-size short-circuit: a non-DC y_mode
/// must also bypass the palette path even when
/// `allow_screen_content_tools = true`. (The §5.11.46 luma arm
/// is `if ( YMode == DC_PRED )`; a non-DC mode skips the
/// has_palette_y read.)
#[test]
fn decode_intra_block_mode_info_non_dc_y_mode_skips_palette_luma_arm() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
// Rig y_mode → V_PRED (non-DC).
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(V_PRED as u8);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
false,
/* allow_screen_content_tools = */ true,
false,
false,
false,
false,
false,
8,
)
.unwrap();
// Luma palette arm short-circuited (YMode != DC_PRED).
assert_eq!(info.has_palette_y, None);
}
/// §5.11.24 outer-gate short-circuit: `enable_filter_intra =
/// false` skips the entire §5.11.24 read regardless of `YMode`.
#[test]
fn decode_intra_block_mode_info_filter_intra_disabled_short_circuit() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
false,
false,
/* enable_filter_intra = */ false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.use_filter_intra, None);
assert_eq!(info.filter_intra_mode, None);
}
/// §5.11.24 outer-gate Y-mode short-circuit: a non-DC `YMode`
/// also short-circuits the §5.11.24 read (regardless of
/// `enable_filter_intra`), since the gate is `YMode == DC_PRED`.
#[test]
fn decode_intra_block_mode_info_non_dc_y_mode_skips_filter_intra() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
// Rig y_mode → V_PRED.
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(V_PRED as u8);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
false,
false,
/* enable_filter_intra = */ true,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.use_filter_intra, None);
assert_eq!(info.filter_intra_mode, None);
}
/// §5.11.24 outer-gate block-size short-circuit: a block with
/// `Max(Block_Width, Block_Height) > 32` (BLOCK_64X64 ⇒ 64)
/// short-circuits the §5.11.24 read.
#[test]
fn decode_intra_block_mode_info_large_block_skips_filter_intra() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_64X64,
false,
false,
false,
/* enable_filter_intra = */ true,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.use_filter_intra, None);
}
/// §5.11.22 RefFrame derivation: always `[INTRA_FRAME = 0, NONE
/// = -1]` regardless of `MiSize` / `HasChroma`.
#[test]
fn decode_intra_block_mode_info_ref_frame_always_intra_none() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, false, false, false, false, false, false,
false, false, 8,
)
.unwrap();
assert_eq!(info.ref_frame, [0, -1]);
}
/// §5.11.22 grid stamping: `YModes[r + y][c + x] = YMode` over the
/// `bh4 * bw4` footprint, mirroring [`Self::decode_intra_frame_y_mode`].
#[test]
fn decode_intra_block_mode_info_stamps_y_modes_grid() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0); // DC_PRED
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
walker
.decode_intra_block_mode_info(
&mut dec, &mut cdfs, /* mi_row = */ 2, /* mi_col = */ 2,
/* sub_size = */ BLOCK_8X8, false, false, false, false, false, false, false,
false, 8,
)
.unwrap();
// BLOCK_8X8 footprint is 2x2 mi cells anchored at (2,2);
// every cell carries 0 (DC_PRED, which also matches the
// pre-fill identity but we cover the path).
let bw4 = NUM_4X4_BLOCKS_WIDE[BLOCK_8X8] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[BLOCK_8X8] as u32;
for dr in 0..bh4 {
for dc in 0..bw4 {
let idx = ((2 + dr) * walker.mi_cols() + (2 + dc)) as usize;
assert_eq!(walker.y_modes()[idx], 0, "footprint stamped");
}
}
}
/// §5.11.22 grid stamping with a non-zero `YMode` value (forced
/// V_PRED): every cell in the `bh4 * bw4` footprint carries the
/// decoded value, distinguishable from the pre-fill identity (0).
#[test]
fn decode_intra_block_mode_info_stamps_non_dc_y_mode() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(V_PRED as u8);
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
false,
false,
false,
false,
false,
false,
false,
8,
)
.unwrap();
let bw4 = NUM_4X4_BLOCKS_WIDE[BLOCK_16X16] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[BLOCK_16X16] as u32;
for dr in 0..bh4 {
for dc in 0..bw4 {
let idx = (dr * walker.mi_cols() + dc) as usize;
assert_eq!(walker.y_modes()[idx], V_PRED as u8);
}
}
// A cell outside the footprint stays at its initial 0.
let outside_idx = ((bh4) * walker.mi_cols() + bw4) as usize;
assert_eq!(walker.y_modes()[outside_idx], 0);
}
/// §5.11.44 is_directional_mode: V_PRED..=D67_PRED return true,
/// every other mode false.
#[test]
fn is_directional_matches_spec_range() {
for mode in 0u8..INTRA_MODES as u8 {
let want = (mode as usize) >= V_PRED && (mode as usize) <= D67_PRED;
assert_eq!(
is_directional(mode),
want,
"is_directional({mode}) must match §5.11.44 spec",
);
}
// Out-of-enumeration upper bound also returns false.
assert!(!is_directional(255));
}
/// §8.3.2 `cfl_allowed_for_uv_mode`: explicit truth-table check
/// against the three spec arms.
#[test]
fn cfl_allowed_truth_table_matches_spec() {
// Lossless = true + post-subsampling 4×4 chroma → allowed.
// BLOCK_4X4 luma (4×4) + no subsampling → chroma 4×4.
assert!(cfl_allowed_for_uv_mode(true, BLOCK_4X4, false, false));
// BLOCK_8X8 luma (8×8) + 4:2:0 subsampling → chroma 4×4 →
// allowed under lossless.
assert!(cfl_allowed_for_uv_mode(true, BLOCK_8X8, true, true));
// BLOCK_8X8 luma + no subsampling → chroma 8×8 → NOT allowed
// under lossless (residual exceeds 4×4).
assert!(!cfl_allowed_for_uv_mode(true, BLOCK_8X8, false, false));
// Lossless = false: gate is Max(W, H) <= 32.
assert!(cfl_allowed_for_uv_mode(false, BLOCK_16X16, false, false));
assert!(cfl_allowed_for_uv_mode(false, BLOCK_32X32, false, false));
// BLOCK_64X64 → Max = 64 → not allowed.
assert!(!cfl_allowed_for_uv_mode(false, BLOCK_64X64, false, false));
// BLOCK_128X128 → Max = 128 → not allowed.
assert!(!cfl_allowed_for_uv_mode(false, BLOCK_128X128, false, false));
// Out-of-range mi_size → false (caller-bug guard).
assert!(!cfl_allowed_for_uv_mode(false, BLOCK_SIZES, false, false));
}
// ===================================================================
// r171 — §5.11.46 palette_colors_y[] / _u[] / _v[] readers +
// §5.11.49 get_palette_cache() merge. The §5.11.22 wrapper now
// accepts `bit_depth` and stamps `PaletteSizes` / `PaletteColors`
// grids on the success path; `PaletteEntriesUnsupported` is no
// longer returned by the conformant code path.
// ===================================================================
/// Sanity test for the `rigged_row(1)` convention against a 3-wide
/// (binary) CDF: forcing symbol=1 should decode the first read of
/// the row to 1.
#[test]
fn rigged_row_binary_one_decodes_one() {
let bytes = [0u8; 32];
let mut row = rigged_row::<3>(1);
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let s = dec.read_symbol(&mut row).unwrap();
assert_eq!(s, 1, "rigged_row(1) on a binary CDF must decode to 1");
}
/// §5.5.2 `bit_depth` caller-bug guard: anything outside {8, 10,
/// 12} surfaces `PartitionWalkOutOfRange` before any bit is read.
#[test]
fn decode_intra_block_mode_info_rejects_bad_bit_depth() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
for bad in [0u8, 7, 9, 11, 13, 16, 255] {
assert!(matches!(
walker.decode_intra_block_mode_info(
&mut dec, &mut cdfs, 0, 0, BLOCK_8X8, false, false, false, false, false, false,
false, false, bad,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
assert_eq!(
dec.position(),
pos_before,
"no bit read on the bit_depth-rejection path"
);
}
/// §5.11.49 `get_palette_cache(plane)` on a fresh walker: no
/// neighbour palettes have been written, so both `aboveN` and
/// `leftN` collapse to 0 and the merged cache is empty.
#[test]
fn get_palette_cache_fresh_walker_returns_empty() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cache = [0u16; 2 * PALETTE_COLORS];
for plane in 0..3 {
// Even at non-superblock-top mi_row, an unwritten neighbour
// returns 0.
for mi_row in [0, 1, 4, 8, 16] {
for mi_col in [0, 1, 4, 8] {
let n = walker.get_palette_cache(plane, mi_row, mi_col, &mut cache);
assert_eq!(n, 0, "fresh walker has no neighbour palette state");
}
}
}
}
/// §4.7 `Clip1( x )` truth table at 8 / 10 / 12 bits — the helper
/// the §5.11.46 delta loop calls. Cumulatively-overflowed inputs
/// clamp to `(1 << BitDepth) - 1`.
#[test]
fn clip1_to_bit_depth_truth_table() {
// 8-bit
assert_eq!(clip1_to_bit_depth(0, 8), 0);
assert_eq!(clip1_to_bit_depth(127, 8), 127);
assert_eq!(clip1_to_bit_depth(255, 8), 255);
assert_eq!(clip1_to_bit_depth(256, 8), 255);
assert_eq!(clip1_to_bit_depth(1 << 20, 8), 255);
// 10-bit
assert_eq!(clip1_to_bit_depth(0, 10), 0);
assert_eq!(clip1_to_bit_depth(512, 10), 512);
assert_eq!(clip1_to_bit_depth(1023, 10), 1023);
assert_eq!(clip1_to_bit_depth(1024, 10), 1023);
// 12-bit
assert_eq!(clip1_to_bit_depth(4095, 12), 4095);
assert_eq!(clip1_to_bit_depth(4096, 12), 4095);
}
/// §4.7 `CeilLog2( x )` truth table — the helper the §5.11.46
/// paletteBits refinement consults. Defined to return 0 on 0 / 1.
#[test]
fn ceil_log2_av1_truth_table() {
assert_eq!(ceil_log2_av1(0), 0);
assert_eq!(ceil_log2_av1(1), 0);
assert_eq!(ceil_log2_av1(2), 1);
assert_eq!(ceil_log2_av1(3), 2);
assert_eq!(ceil_log2_av1(4), 2);
assert_eq!(ceil_log2_av1(5), 3);
assert_eq!(ceil_log2_av1(8), 3);
assert_eq!(ceil_log2_av1(9), 4);
assert_eq!(ceil_log2_av1(256), 8);
assert_eq!(ceil_log2_av1(257), 9);
assert_eq!(ceil_log2_av1(1024), 10);
}
/// §5.11.46 happy-path with `has_palette_y == 0` does not enter
/// the entry-reads loop; on the conformant path the reader returns
/// `Ok` with `has_palette_y = Some(0)` and the entry / size fields
/// stay `None`.
#[test]
fn decode_intra_block_mode_info_has_palette_y_zero_skips_entries() {
let (mut walker, mut cdfs, bytes) = intra_block_mode_info_fixture();
// y_mode → DC_PRED.
for row in cdfs.y_mode.iter_mut() {
*row = rigged_row::<{ INTRA_MODES + 1 }>(0);
}
// palette_y_mode_cdf is `[3]` per ctx → rig to symbol 0
// (`has_palette_y = 0`) for every (bsize, ctx).
for bs in cdfs.palette_y_mode.iter_mut() {
for row in bs.iter_mut() {
*row = rigged_row::<3>(0);
}
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let info = walker
.decode_intra_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
false,
/* has_chroma = */ false,
/* allow_screen_content_tools = */ true,
false,
false,
false,
false,
false,
8,
)
.unwrap();
assert_eq!(info.has_palette_y, Some(0));
assert_eq!(info.palette_size_y, None);
assert_eq!(info.palette_colors_y, None);
assert_eq!(walker.palette_sizes()[0], 0, "PaletteSizes[0][0][0] = 0");
}
/// §5.11.46 Y-plane direct-call: with an empty cache (fresh
/// walker) and a zero bitstream, `PaletteSizeY = 2` produces
/// `palette_colors_y[0] = L(BitDepth) = 0` and `palette_colors_y[1]
/// = Clip1(prev=0 + (delta_raw + 1)) = 1` after the Y-specific
/// `++` step. After the trailing sort the array stays `[0, 1, …]`.
#[test]
fn read_palette_entries_y_size_2_zero_bitstream() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let entries = read_palette_entries_y(&mut dec, 8, 2, &walker, 0, 0).unwrap();
// entries[0] = L(8) = 0 (zero bitstream); entries[1] = Clip1(0
// + (0 + 1)) = 1 with the Y-specific delta++ step. After the
// trailing sort the result is [0, 1, 0, …].
assert_eq!(entries[0], 0);
assert_eq!(entries[1], 1);
for &slot in entries.iter().skip(2) {
assert_eq!(slot, 0, "unused entry stays 0");
}
}
/// §5.11.46 U-plane direct-call: differs from Y by NOT applying
/// the `++` to the delta and using `range = (1<<BitDepth) -
/// palette_colors_u[idx]` (no `- 1`). With a fresh walker plus
/// zero bitstream plus PaletteSizeUV = 2: `u[0] = L(8) = 0`,
/// `u[1] = Clip1(0 + 0) = 0` (no ++); sort leaves `[0, 0, …]`.
/// Then the V plane reads `delta_encode_palette_colors_v L(1) = 0`
/// and takes the direct-literal arm with PaletteSizeUV
/// `L(BitDepth)` reads producing zero entries.
#[test]
fn read_palette_entries_uv_size_2_zero_bitstream_direct_literal_arm() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let walker = PartitionWalker::new(32, 32, geom).unwrap();
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let (u, v) = read_palette_entries_uv(&mut dec, 8, 2, &walker, 0, 0).unwrap();
// U: same shape as Y but no delta++ ⇒ u[0]=0, u[1]=0.
assert_eq!(u[0], 0);
assert_eq!(u[1], 0);
// V: delta_encode_bit = 0 ⇒ direct-literal arm; both L(8) = 0.
assert_eq!(v[0], 0);
assert_eq!(v[1], 0);
}
/// §5.11.46 grid stamping via direct-call: stamp a luma palette
/// into (0, 0) by inserting it directly into the walker's grids,
/// then verify `get_palette_cache(0, 0, bw4)` returns the entries
/// via the §5.11.49 left-neighbour read.
#[test]
fn get_palette_cache_left_neighbour_only_via_stamped_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
// Hand-stamp a luma palette into (0, 0): size 3, entries [10,
// 50, 200]. We use the public read-only accessors to verify
// and the private fields via tests-internal access.
let mi_cols = walker.mi_cols() as usize;
let area = (walker.mi_rows() as usize) * mi_cols;
// (mi_row=0, mi_col=0) ⇒ flat index 0.
let cell: usize = 0;
let _ = mi_cols;
walker.palette_sizes[cell] = 3;
walker.ensure_palette_colors();
walker.palette_colors[cell * PALETTE_COLORS] = 10;
walker.palette_colors[cell * PALETTE_COLORS + 1] = 50;
walker.palette_colors[cell * PALETTE_COLORS + 2] = 200;
// U plane also gets a 2-entry palette so we can verify plane
// separation.
walker.palette_sizes[area + cell] = 2;
walker.ensure_palette_colors();
walker.palette_colors[(area + cell) * PALETTE_COLORS] = 30;
walker.palette_colors[(area + cell) * PALETTE_COLORS + 1] = 70;
// Reading from neighbour (0, 1): mi_row=0 ⇒ above gated out
// (0 * 4 % 64 = 0); left = (0, 0) palette.
let mut cache = [0u16; 2 * PALETTE_COLORS];
let n_y = walker.get_palette_cache(0, 0, 1, &mut cache);
assert_eq!(n_y, 3);
assert_eq!(cache[0], 10);
assert_eq!(cache[1], 50);
assert_eq!(cache[2], 200);
// U plane: 2 entries.
let n_u = walker.get_palette_cache(1, 0, 1, &mut cache);
assert_eq!(n_u, 2);
assert_eq!(cache[0], 30);
assert_eq!(cache[1], 70);
// V plane: nothing stamped → 0.
let n_v = walker.get_palette_cache(2, 0, 1, &mut cache);
assert_eq!(n_v, 0);
}
/// §5.11.49 above + left merge with no duplicates: above palette
/// `[5, 30]` + left palette `[10, 30, 50]` → merged-deduplicated
/// cache `[5, 10, 30, 50]` (the 30 in both lists appears once).
#[test]
fn get_palette_cache_merge_above_and_left_with_duplicate() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mi_cols = walker.mi_cols() as usize;
// Stamp above palette at (mi_row=0, mi_col=1) so that when we
// read at (mi_row=1, mi_col=1), the above neighbour is (0, 1)
// and mi_row=1 ⇒ 1*4 % 64 = 4 ≠ 0 (above gate open).
let above_cell: usize = 1; // (0, 1) ⇒ 0 * mi_cols + 1
walker.palette_sizes[above_cell] = 2;
walker.ensure_palette_colors();
walker.palette_colors[above_cell * PALETTE_COLORS] = 5;
walker.palette_colors[above_cell * PALETTE_COLORS + 1] = 30;
// Stamp left palette at (mi_row=1, mi_col=0).
let left_cell: usize = mi_cols;
walker.palette_sizes[left_cell] = 3;
walker.ensure_palette_colors();
walker.palette_colors[left_cell * PALETTE_COLORS] = 10;
walker.palette_colors[left_cell * PALETTE_COLORS + 1] = 30;
walker.palette_colors[left_cell * PALETTE_COLORS + 2] = 50;
// Read cache at (1, 1).
let mut cache = [0u16; 2 * PALETTE_COLORS];
let n = walker.get_palette_cache(0, 1, 1, &mut cache);
assert_eq!(n, 4, "deduplicated merge of {{5, 30}} + {{10, 30, 50}}");
assert_eq!(cache[0], 5);
assert_eq!(cache[1], 10);
assert_eq!(cache[2], 30);
assert_eq!(cache[3], 50);
}
/// §5.11.49 cache visibility: a hand-stamped left-neighbour
/// palette is surfaced by `get_palette_cache`. This isolates the
/// §5.11.49 cache-merge path from the §5.11.46 bitstream-driven
/// entry reader, which is covered separately by the
/// `read_palette_entries_y_size_2_zero_bitstream` test.
#[test]
fn read_palette_entries_y_cache_visible_via_get_palette_cache() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
// (mi_row=0, mi_col=0) ⇒ flat index 0.
let cell: usize = 0;
walker.palette_sizes[cell] = 2;
walker.ensure_palette_colors();
walker.palette_colors[cell * PALETTE_COLORS] = 11;
walker.palette_colors[cell * PALETTE_COLORS + 1] = 22;
// A subsequent block at (0, 1) sees this as the left neighbour
// via get_palette_cache.
let mut cache = [0u16; 2 * PALETTE_COLORS];
let n = walker.get_palette_cache(0, 0, 1, &mut cache);
assert_eq!(n, 2);
assert_eq!(cache[0], 11);
assert_eq!(cache[1], 22);
}
/// §5.11.49 superblock-top boundary suppresses the above-neighbour
/// read at `MiRow * MI_SIZE % 64 == 0`. We don't need a populated
/// above neighbour to verify the gate — a fresh walker at mi_row =
/// 16 (the next superblock top) reads 0 from the gated-out above
/// regardless of state.
#[test]
fn get_palette_cache_superblock_top_suppresses_above() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 64,
mi_col_start: 0,
mi_col_end: 64,
};
let walker = PartitionWalker::new(64, 64, geom).unwrap();
let mut cache = [0u16; 2 * PALETTE_COLORS];
// mi_row = 16: 16 * MI_SIZE (= 4) = 64 → 64 % 64 = 0 → gated.
let n_gated = walker.get_palette_cache(0, 16, 8, &mut cache);
assert_eq!(n_gated, 0);
// mi_row = 17: 17 * 4 = 68 → 68 % 64 = 4 → not gated.
let n_open = walker.get_palette_cache(0, 17, 8, &mut cache);
assert_eq!(n_open, 0, "fresh walker has no above palette state");
}
/// §8.3.2 `check_backward`: BWDREF (5), ALTREF2 (6), ALTREF (7) are
/// backward; INTRA (0), LAST (1)..GOLDEN (4) are forward; NONE
/// (-1) is not backward.
#[test]
fn check_backward_matches_spec() {
assert!(!check_backward(-1));
assert!(!check_backward(0));
assert!(!check_backward(1));
assert!(!check_backward(2));
assert!(!check_backward(3));
assert!(!check_backward(4));
assert!(check_backward(5));
assert!(check_backward(6));
assert!(check_backward(7));
// Out-of-enumeration values fall outside the inclusive band.
assert!(!check_backward(8));
}
/// §8.3.2 `is_samedir_ref_pair`: forward+forward = true,
/// backward+backward = true, mixed = false.
#[test]
fn is_samedir_ref_pair_matches_spec() {
assert!(is_samedir_ref_pair(1, 4)); // LAST+GOLDEN — both fwd
assert!(is_samedir_ref_pair(5, 7)); // BWDREF+ALTREF — both bwd
assert!(!is_samedir_ref_pair(1, 7)); // LAST+ALTREF — mixed
assert!(!is_samedir_ref_pair(4, 5)); // GOLDEN+BWDREF — mixed
// INTRA (0) counts as forward (0 < 5).
assert!(is_samedir_ref_pair(0, 1));
// NONE (-1) counts as forward too.
assert!(is_samedir_ref_pair(-1, 0));
}
/// §8.3.2 `count_refs`: counts matching entries in the four
/// available `(above|left)RefFrame[0..2]` slots, gated by
/// `availU` / `availL`.
#[test]
fn count_refs_matches_spec() {
// No neighbours available ⇒ count is always 0.
assert_eq!(count_refs(1, false, [1, 1], false, [1, 1]), 0);
// Just above available with both slots = LAST ⇒ 2.
assert_eq!(count_refs(1, true, [1, 1], false, [1, 1]), 2);
// Both available, all four slots = LAST ⇒ 4.
assert_eq!(count_refs(1, true, [1, 1], true, [1, 1]), 4);
// Mixed: above[0] = LAST, above[1] = NONE, left[0] = LAST,
// left[1] = ALTREF; query LAST ⇒ 2.
assert_eq!(count_refs(1, true, [1, -1], true, [1, 7]), 2);
// Query ALTREF on the same setup ⇒ 1.
assert_eq!(count_refs(7, true, [1, -1], true, [1, 7]), 1);
// Query GOLDEN ⇒ 0.
assert_eq!(count_refs(4, true, [1, -1], true, [1, 7]), 0);
}
/// §8.3.2 `comp_mode_ctx` corner cases — explicit truth-table
/// verification of the spec's nine-arm dispatch (av1-spec p.366).
#[test]
fn comp_mode_ctx_matches_spec_corners() {
// Neither neighbour available ⇒ ctx = 1.
assert_eq!(
comp_mode_ctx(false, false, true, true, true, true, [0, -1], [0, -1]),
1
);
// Only above available, AboveSingle ⇒ ctx = check_backward(above[0]).
// above[0] = LAST_FRAME = 1 ⇒ 0.
assert_eq!(
comp_mode_ctx(true, false, true, true, true, true, [1, -1], [0, -1]),
0
);
// above[0] = ALTREF_FRAME = 7 ⇒ 1.
assert_eq!(
comp_mode_ctx(true, false, true, true, true, true, [7, -1], [0, -1]),
1
);
// Only above available, !AboveSingle ⇒ ctx = 3.
assert_eq!(
comp_mode_ctx(true, false, false, true, true, true, [1, 4], [0, -1]),
3
);
// Both available, both Single, above LAST + left LAST ⇒ XOR
// (0 ^ 0) = 0.
assert_eq!(
comp_mode_ctx(true, true, true, true, true, true, [1, -1], [1, -1]),
0
);
// Both available, both Single, above LAST + left ALTREF ⇒
// 0 ^ 1 = 1.
assert_eq!(
comp_mode_ctx(true, true, true, true, true, true, [1, -1], [7, -1]),
1
);
// Both available, both !Single ⇒ ctx = 4.
assert_eq!(
comp_mode_ctx(true, true, false, false, false, false, [1, 4], [1, 4]),
4
);
}
/// §5.11.23 caller-bug guards: out-of-range arguments surface
/// `PartitionWalkOutOfRange` before any bit is read.
#[test]
fn decode_inter_block_mode_info_rejects_out_of_range() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec.position();
// Out-of-range sub_size.
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
// Out-of-range mi_row.
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
8,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
// Out-of-range mi_col.
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
8,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
// Out-of-range seg_ref_frame_data.
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
8,
false,
false,
false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
// Out-of-range skip_mode_frame[0].
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[8, -1],
false,
0,
false,
false,
false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
assert_eq!(
dec.position(),
pos_before,
"no bit read on the bounds-rejection paths"
);
}
/// §5.11.25 arm 1: `skip_mode == 1` ⇒ `RefFrame[0..2] = SkipModeFrame[0..2]`
/// with no `S()` reads. The §5.11.23 reader then short-circuits at
/// §7.10 with `FindMvStackUnsupported`; the walker `RefFrames[][]`
/// grid is stamped over the block footprint.
#[test]
fn decode_inter_block_mode_info_skip_mode_no_reads_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 32]; // hostile buffer the reader must not touch
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
/* mi_row = */ 4,
/* mi_col = */ 4,
/* sub_size = */ BLOCK_8X8,
/* skip_mode = */ 1,
/* avail_u = */ true,
/* avail_l = */ true,
/* above_ref_frame = */ [0, -1],
/* left_ref_frame = */ [0, -1],
/* above_intra = */ true,
/* left_intra = */ true,
/* above_single = */ true,
/* left_single = */ true,
/* skip_mode_frame = */ [1, 7], // LAST + ALTREF (conformant compound)
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_data = */ 0,
/* seg_skip_active = */ false,
/* seg_globalmv_active = */ false,
/* reference_select = */ true, // ignored on the skip_mode arm
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert_eq!(
dec.position(),
pos_before,
"skip_mode arm reads 0 bits from §5.11.25"
);
// RefFrames grid stamped over the 2×2 BLOCK_8X8 footprint at (4, 4).
let mi_cols = walker.mi_cols() as usize;
for dr in 0..2 {
for dc in 0..2 {
let cell = ((4 + dr) * mi_cols + (4 + dc)) * 2;
assert_eq!(walker.ref_frames()[cell], 1, "RefFrame[0] = LAST_FRAME");
assert_eq!(
walker.ref_frames()[cell + 1],
7,
"RefFrame[1] = ALTREF_FRAME"
);
}
}
// A cell outside the footprint stays at the pre-fill identity
// [INTRA_FRAME, NONE] = [0, -1].
let outside_cell = (6 * mi_cols + 6) * 2;
assert_eq!(walker.ref_frames()[outside_cell], 0);
assert_eq!(walker.ref_frames()[outside_cell + 1], -1);
}
/// §5.11.25 arm 2: `seg_ref_frame_active == 1` (with
/// `skip_mode == 0`) ⇒ `RefFrame[0] = seg_ref_frame_data, RefFrame[1]
/// = NONE` per §5.11.25 (no S() in that arm). Post-r173 the
/// §5.11.23 YMode dispatch Arm 4 then runs the single-pred
/// `new_mv` / `zero_mv` / `ref_mv` cascade (consuming bits), so
/// `dec.position()` advances past `pos_before` and the §5.11.31
/// stub fires.
#[test]
fn decode_inter_block_mode_info_seg_ref_frame_active_runs_cascade() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
/* skip_mode = */ 0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
/* seg_ref_frame_active = */ true,
/* seg_ref_frame_data = */ 4, // GOLDEN_FRAME
false,
false,
true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
// §5.11.25 itself reads 0 bits on the seg_ref_frame_active arm;
// the §5.11.23 Arm 4 single-pred cascade then reads at least the
// `new_mv` symbol.
assert!(
dec.position() > pos_before,
"§5.11.23 Arm 4 single-pred cascade must consume at least one bit"
);
// RefFrames[0][0] = [GOLDEN_FRAME, NONE].
assert_eq!(walker.ref_frames()[0], 4);
assert_eq!(walker.ref_frames()[1], -1);
}
/// §5.11.25 arm 3: `seg_skip_active || seg_globalmv_active` ⇒
/// `RefFrame[0] = LAST_FRAME, RefFrame[1] = NONE`, no `S()` reads.
#[test]
fn decode_inter_block_mode_info_seg_skip_or_globalmv_no_reads() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
/* seg_skip_active = */ true,
/* seg_globalmv_active = */ false,
true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert_eq!(dec.position(), pos_before);
// RefFrames[0][0] = [LAST_FRAME = 1, NONE = -1].
assert_eq!(walker.ref_frames()[0], 1);
assert_eq!(walker.ref_frames()[1], -1);
// Same outcome with globalmv-active path on a fresh walker.
let mut walker2 = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs2 = TileCdfContext::new_from_defaults();
let mut dec2 = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec2.position();
let r = walker2.decode_inter_block_mode_info(
&mut dec2,
&mut cdfs2,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
/* seg_globalmv_active = */ true,
true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert_eq!(dec2.position(), pos_before);
assert_eq!(walker2.ref_frames()[0], 1);
assert_eq!(walker2.ref_frames()[1], -1);
}
/// §5.11.25 arm 4 (default), `!reference_select` ⇒ `comp_mode =
/// SINGLE_REFERENCE` (no S() for comp_mode), then the
/// `single_ref_p1` cascade runs. Default CDFs may produce any
/// conformant single-ref outcome; assert only the structural
/// invariants: `RefFrame[1] = NONE = -1` and `RefFrame[0] ∈
/// LAST_FRAME..=ALTREF_FRAME` (i.e. `1..=7`).
#[test]
fn decode_inter_block_mode_info_default_single_ref_arm_runs_to_completion() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
/* skip_mode = */ 0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
/* reference_select = */ false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
// The single-ref cascade reads at least the single_ref_p1
// symbol, which is a conformant binary S() against the §9.4
// default CDF — at minimum one bit gets consumed (the §8.2.6
// post-symbol renormalisation may shift in additional bits
// depending on range state).
assert!(
dec.position() > pos_before,
"single-ref cascade must consume at least one bit"
);
// Structural invariant: RefFrame[0] ∈ 1..=7 (one of the seven
// inter-prediction refs), RefFrame[1] = NONE = -1 (the spec's
// single-pred sentinel).
let r0 = walker.ref_frames()[0];
let r1 = walker.ref_frames()[1];
assert!(
(1..=7).contains(&(r0 as i32)),
"RefFrame[0] must be a valid inter ref, got {r0}"
);
assert_eq!(r1, -1, "RefFrame[1] = NONE on the single-ref arm");
}
/// §5.11.25 default arm with `reference_select = true` AND
/// `Min(bw4, bh4) >= 2`: the `comp_mode` S() read fires.
/// Whichever arm comp_mode steers into (single vs compound), the
/// resulting `RefFrame[0..2]` pair is structurally conformant.
#[test]
fn decode_inter_block_mode_info_default_with_reference_select_runs_to_completion() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
/* sub_size = */ BLOCK_8X8,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
/* reference_select = */ true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
let r0 = walker.ref_frames()[0];
let r1 = walker.ref_frames()[1];
// Either single-pred (r0 in 1..=7, r1 = NONE = -1) or compound
// (both in 1..=7). RefFrame[0] is never NONE / INTRA_FRAME on
// this arm.
assert!(
(1..=7).contains(&(r0 as i32)),
"RefFrame[0] must be valid inter ref, got {r0}"
);
assert!(
r1 == -1 || (1..=7).contains(&(r1 as i32)),
"RefFrame[1] must be NONE or a valid inter ref, got {r1}"
);
}
/// r359 §5.11.27 / §5.11.28 / §5.11.29 side-data grid stamp: after
/// `decode_inter_block_mode_info` returns, the `motion_modes` /
/// `compound_types` / inter-intra grids must carry the returned
/// `DecodedInterBlockModeInfo`'s already-resolved values over the
/// block's `bh4 * bw4` footprint, so the §5.11.33 frame walk can
/// read them back out per leaf-origin. The `!is_motion_mode_
/// switchable` / `!enable_interintra_compound` short-circuit arms
/// here resolve to `motion_mode == SIMPLE` and `interintra == 0`,
/// and the compound type to one of the §5.11.29 short-circuit
/// defaults — every grid cell of the footprint must match the
/// readout exactly (and cells outside it stay at their pre-fill).
#[test]
fn decode_inter_block_mode_info_stamps_side_data_grids() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let sub_size = BLOCK_8X8;
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
sub_size,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
/* reference_select = */ true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
)
.expect("inter cascade short-circuits to SIMPLE/no-interintra");
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
let mi_cols = walker.mi_cols();
// Footprint cells carry the readout values.
for dr in 0..bh4 {
for dc in 0..bw4 {
let cell = ((dr * mi_cols) + dc) as usize;
assert_eq!(
walker.motion_modes()[cell],
info.motion_mode,
"motion_modes[{dr}][{dc}] must equal the decoded motion_mode"
);
assert_eq!(
walker.compound_types()[cell],
info.compound_type.compound_type,
"compound_types[{dr}][{dc}] must equal the decoded compound_type"
);
assert_eq!(
walker.interintra_modes()[cell],
info.interintra.interintra_mode.unwrap_or(0),
"interintra_modes[{dr}][{dc}] must equal the decoded interintra_mode (0 default)"
);
assert_eq!(
walker.wedge_interintras()[cell],
info.interintra.wedge_interintra.unwrap_or(0),
"wedge_interintras[{dr}][{dc}] must match the readout"
);
}
}
// A cell well outside the footprint keeps its pre-fill default.
let outside = ((10 * mi_cols) + 10) as usize;
assert_eq!(
walker.motion_modes()[outside],
MOTION_MODE_SIMPLE,
"out-of-footprint cell keeps the MOTION_MODE_SIMPLE pre-fill"
);
assert_eq!(
walker.compound_types()[outside],
crate::inter_pred::COMPOUND_AVERAGE,
"out-of-footprint cell keeps the COMPOUND_AVERAGE pre-fill"
);
}
/// §5.11.25 default-arm small-block gate: with `reference_select =
/// true` but `Min(bw4, bh4) < 2` (BLOCK_4X4 has bw4 = bh4 = 1),
/// the `comp_mode` S() is suppressed and `comp_mode =
/// SINGLE_REFERENCE` is forced. The result must be single-pred
/// (RefFrame[1] = NONE).
#[test]
fn decode_inter_block_mode_info_default_small_block_forces_single_ref() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
/* reference_select = */ true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
/* gm_params = */ identity_gm_params(),
/* ref_frame_sign_bias = */ [0; 8],
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
/* use_ref_frame_mvs = */ false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
// Small-block gate forces single-pred → RefFrame[1] = NONE.
assert_eq!(
walker.ref_frames()[1],
-1,
"Min(bw4, bh4) < 2 forces comp_mode = SINGLE_REFERENCE ⇒ RefFrame[1] = NONE"
);
let r0 = walker.ref_frames()[0];
assert!((1..=7).contains(&(r0 as i32)));
}
/// §5.11.23 isCompound derivation: the §5.11.25 skip_mode arm with
/// SkipModeFrame = [LAST, NONE] produces RefFrame[1] = NONE ⇒
/// isCompound = false. With SkipModeFrame = [LAST, ALTREF] →
/// RefFrame[1] = ALTREF > INTRA_FRAME ⇒ isCompound = true.
/// (Verified indirectly via the walker grid stamp; the
/// `DecodedInterBlockModeInfo` aggregate itself is gated behind
/// the `Ok` arm which doesn't return on r170.)
#[test]
fn decoded_inter_block_mode_info_struct_public_api_smoke() {
let info = DecodedInterBlockModeInfo {
mi_row: 0,
mi_col: 0,
mi_size: BLOCK_4X4,
ref_frame: [1, 7],
is_compound: true,
y_mode: MODE_NEAREST_NEARESTMV,
ref_mv_idx: 0,
num_mv_found: 0,
new_mv_context: 0,
ref_mv_context: 0,
zero_mv_context: 0,
mv: [[0, 0], [0, 0]],
motion_mode: MOTION_MODE_SIMPLE,
num_warp_samples: 0,
interintra: InterIntraReadout {
interintra: 0,
interintra_mode: None,
wedge_interintra: None,
wedge_index: None,
},
compound_type: CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 1,
compound_type: COMPOUND_AVERAGE,
wedge_index: None,
wedge_sign: None,
mask_type: None,
},
interp_filter: InterpolationFilterReadout {
interp_filter: [EIGHTTAP, EIGHTTAP],
read_from_bitstream: [false, false],
},
};
assert_eq!(info.ref_frame[0], 1);
assert_eq!(info.ref_frame[1], 7);
assert!(info.is_compound);
assert_eq!(info.y_mode, MODE_NEAREST_NEARESTMV);
assert_eq!(info.mv, [[0, 0], [0, 0]]);
assert_eq!(info.motion_mode, MOTION_MODE_SIMPLE);
assert_eq!(info.num_warp_samples, 0);
assert_eq!(info.interintra.interintra, 0);
// Single-pred case.
let info_single = DecodedInterBlockModeInfo {
mi_row: 0,
mi_col: 0,
mi_size: BLOCK_4X4,
ref_frame: [1, -1],
is_compound: false,
y_mode: MODE_NEARESTMV,
ref_mv_idx: 0,
num_mv_found: 0,
new_mv_context: 0,
ref_mv_context: 0,
zero_mv_context: 0,
mv: [[12, -8], [0, 0]],
motion_mode: MOTION_MODE_SIMPLE,
num_warp_samples: 0,
interintra: InterIntraReadout {
interintra: 1,
interintra_mode: Some(II_DC_PRED),
wedge_interintra: Some(0),
wedge_index: None,
},
compound_type: CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 1,
compound_type: COMPOUND_INTRA,
wedge_index: None,
wedge_sign: None,
mask_type: None,
},
interp_filter: InterpolationFilterReadout {
interp_filter: [EIGHTTAP_SMOOTH, EIGHTTAP_SHARP],
read_from_bitstream: [true, true],
},
};
assert!(!info_single.is_compound);
assert_eq!(info_single.y_mode, MODE_NEARESTMV);
assert_eq!(info_single.mv[0], [12, -8]);
// Slot 1 stays at the §5.11.5 pre-fill on single-pred.
assert_eq!(info_single.mv[1], [0, 0]);
assert_eq!(info_single.interintra.interintra, 1);
assert_eq!(info_single.interintra.interintra_mode, Some(II_DC_PRED));
assert_eq!(info_single.interintra.wedge_interintra, Some(0));
assert_eq!(info_single.interintra.wedge_index, None);
}
/// §5.11.18 prologue + §5.11.23 grid composition: stamp
/// `RefFrames[0][0..2][..]` via a first §5.11.23 call, then verify
/// the next §5.11.18 prologue at `(0, 2)` reads the stamped values
/// through `ref_frame_at` (the §8.3.2 ref-frame ctx walks rely on
/// this propagation).
#[test]
fn ref_frames_grid_propagates_to_subsequent_neighbour_lookup() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
// Pre-fill identity: every cell starts at [INTRA_FRAME, NONE].
assert_eq!(walker.ref_frame_at(0, 0, 0), 0);
assert_eq!(walker.ref_frame_at(0, 0, 1), -1);
// Out-of-grid coordinates return the spec's unavailable
// fallback.
assert_eq!(walker.ref_frame_at(-1, 0, 0), 0);
assert_eq!(walker.ref_frame_at(-1, 0, 1), -1);
assert_eq!(walker.ref_frame_at(0, 100, 0), 0);
assert_eq!(walker.ref_frame_at(0, 100, 1), -1);
}
// ===================================================================
// §5.11.23 post-find_mv_stack cascade tests (round 173)
// ===================================================================
/// §5.11.23 Arm 1 (`skip_mode == 1`): YMode = NEAREST_NEARESTMV
/// (no `compound_mode` / `new_mv` / `zero_mv` / `ref_mv` S());
/// `RefMvIdx = 0` and the drl_mode loops do NOT run because
/// NEAREST_NEARESTMV is neither NEWMV/NEW_NEWMV nor has_nearmv.
/// Position MUST equal `pos_before` (no bit was consumed past the
/// §5.11.25 skip_mode arm, which itself reads 0 bits). Stack
/// summary reflects spatial-only fresh-walker state
/// (`num_mv_found = 0`, contexts = (0, 0, 0)).
#[test]
fn cascade_skip_mode_arm1_no_bits_consumed_y_mode_nearest_nearestmv() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 32]; // hostile — Arm 1 must not touch it.
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
4,
4,
BLOCK_8X8,
/* skip_mode = */ 1,
true,
true,
[0, -1],
[0, -1],
true,
true,
true,
true,
/* skip_mode_frame = */ [1, 7],
false,
0,
false,
false,
true,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert_eq!(
dec.position(),
pos_before,
"Arm 1 (skip_mode=1) ⇒ NEAREST_NEARESTMV reads no bits"
);
}
/// §5.11.23 Arm 2 (`seg_skip_active || seg_globalmv_active`):
/// YMode = GLOBALMV (no S()); single-pred RefFrame = [LAST, NONE]
/// (read_ref_frames arm 3 with seg_skip_active). `GLOBALMV` is
/// neither NEWMV/NEW_NEWMV nor has_nearmv, so the drl_mode loops
/// do NOT run; `RefMvIdx = 0`. Position MUST equal `pos_before`.
#[test]
fn cascade_seg_skip_arm2_no_bits_consumed_y_mode_globalmv() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
/* seg_skip_active = */ true,
false,
true,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert_eq!(
dec.position(),
pos_before,
"Arm 2 (seg_skip_active) ⇒ GLOBALMV reads no bits"
);
}
/// §5.11.23 Arm 3 (compound): §5.11.25 skip_mode arm produces
/// `RefFrame = [LAST, ALTREF]` ⇒ isCompound = true. The cascade
/// must then take Arm 3 (`compound_mode` S() against
/// `TileCompoundModeCdf[ctx]`), but on `skip_mode == 1` the §5.11.23
/// Arm 1 takes precedence (NEAREST_NEARESTMV) — so this test seeds
/// a compound ref-frame via the §5.11.25 default arm (rigging
/// comp_mode and comp_ref_type to land on COMPOUND_REFERENCE +
/// UNIDIR + RefFrame[0..2] = [BWDREF, ALTREF]) and confirms the
/// `compound_mode` S() fires (position advances) and the
/// MotionModeUnsupported stub is reached (post-r174 the §5.11.31
/// `assign_mv` body fully decoded; the live gap moved to
/// §5.11.27 `read_motion_mode`).
#[test]
fn cascade_compound_arm3_consumes_compound_mode_bit() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig §5.11.25 default arm: comp_mode = COMPOUND_REFERENCE (1).
for row in cdfs.comp_mode.iter_mut() {
*row = [0, 1 << 15, 0];
}
// comp_ref_type = UNIDIR_COMP_REFERENCE (0).
for row in cdfs.comp_ref_type.iter_mut() {
*row = [1 << 15, 1 << 15, 0];
}
// uni_comp_ref = 1 ⇒ BWDREF / ALTREF (no more uni_comp_ref reads).
for row in cdfs.uni_comp_ref.iter_mut() {
row[0] = [0, 1 << 15, 0];
}
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* skip_mode = */ 0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
/* reference_select = */ true,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert!(
dec.position() > pos_before,
"Arm 3 ⇒ compound_mode S() must consume bits"
);
// RefFrame[0..2] = [BWDREF_FRAME = 5, ALTREF_FRAME = 7].
assert_eq!(walker.ref_frames()[0], 5);
assert_eq!(walker.ref_frames()[1], 7);
}
/// §5.11.23 Arm 4 (single-pred): with §5.11.25 forcing the default
/// arm into single-pred (`reference_select = false`), the cascade
/// reads `new_mv` S(). Rigging `new_mv` ⇒ symbol 0 forces YMode =
/// NEWMV. Because YMode = NEWMV AND the fresh-walker NumMvFound
/// starts at 0, the drl_mode loop's `NumMvFound > idx + 1`
/// condition fails immediately for `idx = 0` and `idx = 1` ⇒ the
/// loop runs zero iterations ⇒ RefMvIdx = 0.
#[test]
fn cascade_single_pred_arm4_new_mv_zero_yields_newmv_no_drl() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig new_mv ⇒ symbol 0 ⇒ YMode = NEWMV.
for row in cdfs.new_mv.iter_mut() {
*row = [0, 1 << 15, 0];
}
// Rig single_ref CDFs to a deterministic outcome (force
// single_ref_p1 ⇒ 0 so the cascade lands on LAST_FRAME / LAST2_FRAME).
for row in cdfs.single_ref.iter_mut() {
for slot in row.iter_mut() {
*slot = [1 << 15, 1 << 15, 0];
}
}
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
/* reference_select = */ false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert!(
dec.position() > pos_before,
"Arm 4 ⇒ new_mv + single_ref cascade must consume bits"
);
// single-pred ⇒ RefFrame[1] = NONE.
assert_eq!(walker.ref_frames()[1], -1);
}
/// §5.11.23 Arm 4 single-pred, `new_mv` ⇒ symbol 1 then `zero_mv`
/// ⇒ symbol 0 ⇒ YMode = GLOBALMV. The drl_mode loops do NOT run
/// because GLOBALMV is not NEWMV/NEW_NEWMV/NEARMV/NEAR_NEARMV.
#[test]
fn cascade_single_pred_arm4_zero_mv_zero_yields_globalmv() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// new_mv ⇒ 1 (NEWMV not chosen).
for row in cdfs.new_mv.iter_mut() {
*row = [1 << 15, 1 << 15, 0];
}
// zero_mv ⇒ 0 ⇒ GLOBALMV.
for row in cdfs.zero_mv.iter_mut() {
*row = [0, 1 << 15, 0];
}
for row in cdfs.single_ref.iter_mut() {
for slot in row.iter_mut() {
*slot = [1 << 15, 1 << 15, 0];
}
}
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert!(dec.position() > pos_before);
}
/// §5.11.23 Arm 4 single-pred, `new_mv` ⇒ 1, `zero_mv` ⇒ 1,
/// `ref_mv` ⇒ 0 ⇒ YMode = NEARESTMV. RefMvIdx loops skipped
/// (NEARESTMV is not has_nearmv either).
#[test]
fn cascade_single_pred_arm4_ref_mv_zero_yields_nearestmv() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
for row in cdfs.new_mv.iter_mut() {
*row = [1 << 15, 1 << 15, 0];
}
for row in cdfs.zero_mv.iter_mut() {
*row = [1 << 15, 1 << 15, 0];
}
// ref_mv ⇒ 0 ⇒ NEARESTMV.
for row in cdfs.ref_mv.iter_mut() {
*row = [0, 1 << 15, 0];
}
for row in cdfs.single_ref.iter_mut() {
for slot in row.iter_mut() {
*slot = [1 << 15, 1 << 15, 0];
}
}
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
}
/// §5.11.23 drl_mode short-circuit: `NumMvFound = 0` on a fresh
/// walker means the `NumMvFound > idx + 1` predicate fails for
/// every iteration. Confirm by selecting YMode = NEWMV and
/// verifying the position advance reflects ONLY the new_mv read,
/// not any drl_mode reads.
#[test]
fn cascade_drl_mode_loop_short_circuits_when_num_mv_found_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// new_mv ⇒ 0 ⇒ NEWMV (triggers drl_mode loop guard).
for row in cdfs.new_mv.iter_mut() {
*row = [0, 1 << 15, 0];
}
// Rig drl_mode CDFs to a value that would be DETECTABLE if a
// read fired (favoring symbol 1 — a "search again" signal). On
// the conformant path the loop never reads it.
for row in cdfs.drl_mode.iter_mut() {
*row = [0, 1 << 15, 0];
}
for row in cdfs.single_ref.iter_mut() {
for slot in row.iter_mut() {
*slot = [1 << 15, 1 << 15, 0];
}
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
// drl_mode CDFs must NOT have adapted (no read fired).
for row in &cdfs.drl_mode {
assert_eq!(
row[2], 0,
"drl_mode CDF counter must stay 0 (no read on NumMvFound=0)"
);
}
let _ = pos_before;
}
/// `has_nearmv` helper covers the four NEARMV-bearing Y modes per
/// §5.11.23 spec (av1-spec p.75): NEARMV / NEAR_NEARMV /
/// NEAR_NEWMV / NEW_NEARMV. Everything else returns false.
#[test]
fn has_nearmv_helper_matches_spec_set() {
assert!(has_nearmv(MODE_NEARMV));
assert!(has_nearmv(MODE_NEAR_NEARMV));
assert!(has_nearmv(MODE_NEAR_NEWMV));
assert!(has_nearmv(MODE_NEW_NEARMV));
// Negatives.
assert!(!has_nearmv(MODE_NEAREST_NEARESTMV));
assert!(!has_nearmv(MODE_NEAREST_NEWMV));
assert!(!has_nearmv(MODE_NEW_NEARESTMV));
assert!(!has_nearmv(MODE_GLOBAL_GLOBALMV));
assert!(!has_nearmv(MODE_NEW_NEWMV));
assert!(!has_nearmv(MODE_NEARESTMV));
assert!(!has_nearmv(MODE_GLOBALMV));
assert!(!has_nearmv(MODE_NEWMV));
}
/// §5.11.23 caller-bug guards remain in place when the new r173
/// args are passed: out-of-range `gm_params` array (we accept any
/// shape here — no validation), but the existing `sub_size`,
/// `mi_row` / `mi_col`, `seg_ref_frame_data`, `skip_mode_frame`
/// guards still fire as before.
#[test]
fn cascade_caller_bug_guards_unchanged_with_r173_args() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES, // out-of-range sub_size
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
}
/// §5.11.23 r205: `use_ref_frame_mvs == true` with an
/// all-`MFMV_INVALID` motion-field grid runs the §7.10.2 driver
/// (including §7.10.2.5 temporal scan + §7.10.2.6 temporal
/// sample) to completion — the §7.10.2.6 short-circuit on the
/// sentinel makes the temporal scan observably a no-op so the
/// cascade behaves identically to `use_ref_frame_mvs == false`.
#[test]
fn cascade_use_ref_frame_mvs_with_invalid_grid_runs_to_completion() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
/* skip_mode = */ 1,
true,
true,
[0, -1],
[0, -1],
true,
true,
true,
true,
[1, 7],
false,
0,
false,
false,
true,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
/* use_ref_frame_mvs = */ true,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
// §5.11.23 + §7.10.2 cascade now runs to completion with the
// §7.10.2.5 temporal scan no-op'd by the sentinel grid. The
// skip_mode == 1 arm short-circuits YMode to
// NEAREST_NEARESTMV without reading any extra bits, so the
// cascade returns a fully-decoded aggregate.
let info = r.expect("§7.10.2 driver with invalid grid runs to completion");
assert_eq!(info.y_mode, MODE_NEAREST_NEARESTMV);
}
// ===================================================================
// §5.11.30 get_mode + §5.11.31 assign_mv + §5.11.32
// read_mv_component tests (round 174)
// ===================================================================
/// Test helper: build a `CDF_LEN`-entry CDF row that forces
/// [`crate::symbol_decoder::SymbolDecoder::read_symbol`] to return
/// `target_sym`. Convention: `cdf[i] = 1` for `i < target_sym`
/// (tiny probability — the §8.2.6 `cur` at those positions stays
/// close to `range`, so `value < cur` and the loop continues), then
/// `cdf[i] = 1 << 15` for `i >= target_sym` (`cur` is tiny, the
/// loop breaks at the first such `i = target_sym`). The final
/// counter entry stays at `0`. Robust across decoder-state
/// evolution (independent of `value` / `range` after multiple
/// reads).
fn force_cdf<const N: usize>(target_sym: usize) -> [u16; N] {
let mut out = [0u16; N];
let last = N - 1;
for (i, slot) in out.iter_mut().enumerate().take(last) {
*slot = if i < target_sym { 1 } else { 1 << 15 };
}
// counter (slot N-1) stays at 0
out
}
/// §5.11.30 `get_mode( refList )`: single-pred YModes (`YMode <
/// NEAREST_NEARESTMV = 18`) are the identity on `refList = 0`. The
/// four ordinals `MODE_NEARESTMV..=MODE_NEWMV = 14..=17` map to
/// themselves.
#[test]
fn get_mode_single_pred_identity_on_ref_list_0() {
for &m in &[MODE_NEARESTMV, MODE_NEARMV, MODE_GLOBALMV, MODE_NEWMV] {
assert_eq!(
get_mode(m, 0),
m,
"single-pred YMode is the identity on refList = 0"
);
}
}
/// §5.11.30 compound-mode mapping table (av1-spec p.81 — the
/// `else` arm of the spec body when `refList == 0` and the explicit
/// `refList == 1` arm). One row per §6.10.22 compound Y mode.
#[test]
fn get_mode_compound_table_matches_spec() {
// [y_mode, get_mode(.., 0), get_mode(.., 1)]
let table: &[(u8, u8, u8)] = &[
(MODE_NEAREST_NEARESTMV, MODE_NEARESTMV, MODE_NEARESTMV),
(MODE_NEAR_NEARMV, MODE_NEARMV, MODE_NEARMV),
(MODE_NEAREST_NEWMV, MODE_NEARESTMV, MODE_NEWMV),
(MODE_NEW_NEARESTMV, MODE_NEWMV, MODE_NEARESTMV),
(MODE_NEAR_NEWMV, MODE_NEARMV, MODE_NEWMV),
(MODE_NEW_NEARMV, MODE_NEWMV, MODE_NEARMV),
(MODE_GLOBAL_GLOBALMV, MODE_GLOBALMV, MODE_GLOBALMV),
(MODE_NEW_NEWMV, MODE_NEWMV, MODE_NEWMV),
];
for &(y_mode, expect_l0, expect_l1) in table {
assert_eq!(
get_mode(y_mode, 0),
expect_l0,
"§5.11.30 ref_list=0 mismatch for YMode={y_mode}"
);
assert_eq!(
get_mode(y_mode, 1),
expect_l1,
"§5.11.30 ref_list=1 mismatch for YMode={y_mode}"
);
}
}
/// §5.11.23 r174 lift verification: with §5.11.25 forcing the
/// skip_mode arm + skip_mode_frame = [LAST, ALTREF] (compound) and
/// YMode = NEAREST_NEARESTMV (§5.11.23 Arm 1), `assign_mv` reads
/// `compMode = NEARESTMV` for BOTH lists and uses `RefStackMv[0][0..1]`
/// without reading any MV bits. The fresh walker's `find_mv_stack`
/// produces `RefStackMv[0] = [[0,0], [0,0]]` for the identity-GM
/// path, so `Mv = [[0,0], [0,0]]`. Position MUST equal pos_before
/// (skip_mode + NEAREST_NEAREST is a fully-non-reading path).
#[test]
fn assign_mv_skip_mode_nearest_nearest_reads_no_mv_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
4,
4,
BLOCK_8X8,
/* skip_mode = */ 1,
true,
true,
[0, -1],
[0, -1],
true,
true,
true,
true,
/* skip_mode_frame = */ [1, 7],
false,
0,
false,
false,
true,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert_eq!(
dec.position(),
pos_before,
"§5.11.31 NEAREST/NEAREST + identity GM ⇒ no S() reads"
);
// §5.11.5 grid stamp: Mvs[r][c][list][comp] = 0 over the 2×2
// BLOCK_8X8 footprint at (4, 4) — the stamped value matches the
// fresh-walker RefStackMv[0][list] = [0, 0] for identity GM.
let mi_cols = walker.mi_cols() as usize;
for dr in 0..2 {
for dc in 0..2 {
let cell = (4 + dr) * mi_cols + (4 + dc);
assert_eq!(walker.mvs()[(cell * 2) * 2], 0);
assert_eq!(walker.mvs()[(cell * 2) * 2 + 1], 0);
assert_eq!(walker.mvs()[(cell * 2 + 1) * 2], 0);
assert_eq!(walker.mvs()[(cell * 2 + 1) * 2 + 1], 0);
}
}
}
/// §5.11.23 r174: §5.11.23 Arm 2 (seg_globalmv_active) ⇒ YMode =
/// GLOBALMV ⇒ `assign_mv`'s compMode = GLOBALMV uses GlobalMvs[0]
/// without reading any MV bits. With identity-GM
/// (`GmType[ref] = IDENTITY`) the §7.10.2.1 setup-global-mv returns
/// `GlobalMvs[0] = [0, 0]`, so the stamped Mv is `[0, 0]`.
#[test]
fn assign_mv_seg_globalmv_uses_global_mvs_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec.position();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
/* seg_globalmv_active = */ true,
true,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
assert_eq!(
dec.position(),
pos_before,
"§5.11.31 GLOBALMV + identity-GM ⇒ no S() reads"
);
// Grid: single-pred ⇒ Mvs[0][0][0][0..2] = [0, 0]; Mvs[0][0][1][..] = [0, 0] (pre-fill).
// cell = 0 (row 0, col 0) ⇒ flat index = (0 * mi_cols + 0) = 0.
let cell: usize = 0;
assert_eq!(walker.mvs()[(cell * 2) * 2], 0);
assert_eq!(walker.mvs()[(cell * 2) * 2 + 1], 0);
assert_eq!(walker.mvs()[(cell * 2 + 1) * 2], 0);
assert_eq!(walker.mvs()[(cell * 2 + 1) * 2 + 1], 0);
}
/// §5.11.31 NEWMV + `mv_joint = MV_JOINT_ZERO`: rig the §5.11.23
/// Arm 4 single-pred cascade to YMode = NEWMV (`new_mv` ⇒ 0), then
/// rig `mv_joint` ⇒ symbol 0 (MV_JOINT_ZERO). With diffMv = [0, 0]
/// the final Mv = PredMv. Fresh walker on identity-GM has
/// `RefStackMv[0][0] = [0, 0]`, so the stamped Mv is also [0, 0].
///
/// Verifies that the §5.11.31 cascade consumed exactly the
/// `new_mv` bit (the single-ref + zero/ref_mv arm reads don't fire
/// because NEWMV is the first arm), the `mv_joint` bit, and ZERO
/// `read_mv_component` calls — no `mv_sign` / `mv_class` reads.
#[test]
fn read_mv_newmv_with_mv_joint_zero_yields_zero_diff_mv() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig §5.11.23 Arm 4 single-pred for YMode = NEWMV.
for row in cdfs.new_mv.iter_mut() {
*row = force_binary_cdf(0);
}
// Single-ref deterministic — any conformant inter ref is fine.
for row in cdfs.single_ref.iter_mut() {
for slot in row.iter_mut() {
*slot = force_binary_cdf(0);
}
}
// §5.11.31 mv_joint ⇒ MV_JOINT_ZERO (symbol 0). 5-entry CDF
// (MV_JOINTS + 1 = 5). For an all-zero bitstream
// (SymbolValue ≈ range - 1) the §8.2.6 loop breaks at the first
// index `s` where `cdf[s] = (1 << 15)` (which makes `cur`
// tiny), so to force symbol 0 every CDF entry except the
// counter is `(1 << 15)`.
for row in cdfs.mv_joint.iter_mut() {
*row = [1 << 15, 1 << 15, 1 << 15, 1 << 15, 0];
}
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
/* reference_select = */ false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
// Mvs grid: identity-GM PredMv = [0, 0], MV_JOINT_ZERO diff =
// [0, 0], so final Mv[0] = [0, 0].
assert_eq!(walker.mvs()[0], 0);
assert_eq!(walker.mvs()[1], 0);
// mv_sign / mv_class CDF counters MUST stay 0 (no read fired).
for row in &cdfs.mv_sign {
for slot in row {
assert_eq!(
slot[2], 0,
"mv_sign CDF must not have adapted on MV_JOINT_ZERO"
);
}
}
for row in &cdfs.mv_class {
for slot in row {
assert_eq!(
slot[11], 0,
"mv_class CDF must not have adapted on MV_JOINT_ZERO"
);
}
}
}
/// §5.11.31 cascade structural smoke: when the §5.11.23 dispatch
/// reaches the inter cascade and the §5.11.31 assign_mv body
/// fires, the resulting Mv values are constrained by the
/// §6.10.25 conformance bound (`|Mv[i][c]| < (1 << 14)`). The
/// per-arc rigs above (NEWMV cascade with `mv_joint = MV_JOINT_ZERO`
/// plus identity GM) exercise the zero-diff path; this test
/// verifies the larger-diff path stays within the bound after a
/// full rigged cascade. Symbol-by-symbol assertions on the rigged
/// CDFs are not robust because the AV1 arithmetic coder is
/// stateful; instead we assert structural invariants.
#[test]
fn cascade_assign_mv_produces_in_range_mv_values() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// YMode = NEWMV ⇒ assign_mv invokes read_mv ⇒ read_mv_component.
for row in cdfs.new_mv.iter_mut() {
*row = force_binary_cdf(0);
}
for row in cdfs.single_ref.iter_mut() {
for slot in row.iter_mut() {
*slot = force_binary_cdf(0);
}
}
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r = walker.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
);
assert!(r.is_ok(), "r175: §5.11.27 read_motion_mode short-circuits to SIMPLE (no S()) on !is_motion_mode_switchable; the §5.11.23 dispatcher returns Ok(DecodedInterBlockModeInfo {{ motion_mode: SIMPLE, .. }}) — got {r:?}");
// §6.10.25 `is_mv_valid`: |Mv[i][c]| < (1 << 14) = 16384.
for list in 0..2 {
for comp in 0..2 {
let v = walker.mvs()[((list) * 2) * 2 + comp].unsigned_abs() as i32;
assert!(
v < (1 << 14),
"|Mv[{list}][{comp}]| = {v} must be < (1 << 14)"
);
}
}
}
/// §5.11.32 `read_mv_component` direct exercise — all five reads
/// rigged to symbol 0 (sign=0, class=0, class0_bit=0,
/// class0_fr=0, class0_hp=0 when allow_hp=true). The §8.2.6 cdf
/// rig `[1 << 15; CDF_LEN - 1] + [0]` (built by [`force_cdf`])
/// puts `cur = 4 * (N - 1)` at `s = 0` which is always <= the
/// decoder's `value` (regardless of state), so symbol 0 is
/// deterministic.
///
/// With sign=0, class=0, class0_bit=0, class0_fr=0,
/// allow_high_precision_mv=true ⇒ class0_hp read fires and = 0.
/// mag = ((0 << 3) | (0 << 1) | 0) + 1 = 1.
#[test]
fn read_mv_component_direct_class0_all_zero_yields_mag_one() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force every symbol read to 0.
for row in cdfs.mv_sign.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
for row in cdfs.mv_class.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<12>(0);
}
}
for row in cdfs.mv_class0_bit.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
for row in cdfs.mv_class0_fr.iter_mut() {
for slot in row.iter_mut() {
for inner in slot.iter_mut() {
*inner = force_cdf::<5>(0);
}
}
}
for row in cdfs.mv_class0_hp.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let r = walker.read_mv_component(
&mut dec, &mut cdfs, /* comp = */ 0, /* mv_ctx = */ 0,
/* allow_high_precision_mv = */ true, /* force_integer_mv = */ false,
);
assert_eq!(r, Ok(1), "all-sym-0 class-0 ladder ⇒ mag = 0 + 1 = 1");
}
/// §5.11.32 `read_mv_component` direct exercise — same as the
/// all-zero test but with `allow_high_precision_mv = false`. The
/// `mv_class0_hp` read is skipped and the §5.11.32 fall-through
/// `mv_class0_hp = 1` applies. mag = ((0<<3)|(0<<1)|1) + 1 = 2.
#[test]
fn read_mv_component_direct_class0_no_hp_yields_mag_two() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
for row in cdfs.mv_sign.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
for row in cdfs.mv_class.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<12>(0);
}
}
for row in cdfs.mv_class0_bit.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
for row in cdfs.mv_class0_fr.iter_mut() {
for slot in row.iter_mut() {
for inner in slot.iter_mut() {
*inner = force_cdf::<5>(0);
}
}
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let r = walker.read_mv_component(
&mut dec, &mut cdfs, 0, 0, /* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
);
assert_eq!(
r,
Ok(2),
"fall-through hp=1 ⇒ mag = ((0<<3)|(0<<1)|1)+1 = 2"
);
}
/// §5.11.32 `read_mv_component` — `mv_sign = 1` negates the
/// magnitude. Same setup as the all-zero class-0 test but with
/// `mv_sign` rigged to symbol 1 via `[1, 1<<15, 0]` (which forces
/// sym 1 on the first read off a fresh decoder: value=32767,
/// cur(s=0) = ((128*511)>>1)+4*1 = 32704+4 = 32708, value >= cur
/// → wait, value=32767 ≥ 32708 ⇒ break, sym=0). For reliable
/// sym=1 on the FIRST read we need cur(s=0) > value(32767).
/// `cdf[0]=0` ⇒ cur = (128*512)>>1 + 4 = 32768+4 = 32772 > 32767
/// ⇒ continue. Then cdf[1]=(1<<15) ⇒ cur tiny ⇒ break with sym=1.
/// This pattern is deterministic ONLY for the first read off a
/// fresh decoder. (Subsequent reads' `value`/`range` evolve and
/// the cdf[0]=0 trick may then under-cur.)
#[test]
fn read_mv_component_direct_with_sign_negates_mag() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// mv_sign is the FIRST read; rig to symbol 1 via the
// fresh-state-only `[0, 1<<15, 0]` pattern (matches
// `force_binary_cdf(1)`).
for row in cdfs.mv_sign.iter_mut() {
for slot in row.iter_mut() {
*slot = [0, 1 << 15, 0];
}
}
// After the first read (mv_sign), subsequent rig back to
// robust sym-0.
for row in cdfs.mv_class.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<12>(0);
}
}
for row in cdfs.mv_class0_bit.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
for row in cdfs.mv_class0_fr.iter_mut() {
for slot in row.iter_mut() {
for inner in slot.iter_mut() {
*inner = force_cdf::<5>(0);
}
}
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let r = walker.read_mv_component(
&mut dec, &mut cdfs, 0, 0, /* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
);
// mag = 2 (as above), sign = 1 ⇒ -2.
assert_eq!(r, Ok(-2), "mv_sign = 1 negates mag = 2 to -2");
}
/// §5.11.32 `read_mv_component` — `force_integer_mv = true`
/// short-circuits the `mv_class0_fr` read (forces value = 3) and
/// also the `mv_fr` read for class > 0. Combined with all other
/// reads forced to sym 0 (sign=0, class=0, class0_bit=0,
/// allow_high_precision_mv=false → class0_hp fall-through=1):
/// mag = ((0 << 3) | (3 << 1) | 1) + 1 = 7 + 1 = 8.
#[test]
fn read_mv_component_direct_force_integer_uses_fr_three() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
for row in cdfs.mv_sign.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
for row in cdfs.mv_class.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<12>(0);
}
}
for row in cdfs.mv_class0_bit.iter_mut() {
for slot in row.iter_mut() {
*slot = force_cdf::<3>(0);
}
}
// mv_class0_fr is rigged to symbol 0 to verify the §5.11.32
// `force_integer_mv = 1` path SKIPS the read entirely (so the
// rig is irrelevant — fr is always 3 on this path).
for row in cdfs.mv_class0_fr.iter_mut() {
for slot in row.iter_mut() {
for inner in slot.iter_mut() {
*inner = force_cdf::<5>(0);
}
}
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let r = walker.read_mv_component(
&mut dec, &mut cdfs, 0, 0, /* allow_high_precision_mv = */ false,
/* force_integer_mv = */ true,
);
assert_eq!(
r,
Ok(8),
"force_integer_mv ⇒ fr=3 ⇒ mag = ((0<<3)|(3<<1)|1)+1 = 8"
);
}
/// §5.11.31 assign_mv defensive guard: passing `use_intrabc =
/// true` through the internal helper surfaces
/// `PartitionWalkOutOfRange`. The inter dispatcher hard-codes
/// `use_intrabc = false`; this test directly exercises the guard.
#[test]
fn assign_mv_rejects_use_intrabc_in_inter_arm() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let stack: [[[i32; 2]; 2]; MAX_REF_MV_STACK_SIZE] = [[[0, 0]; 2]; MAX_REF_MV_STACK_SIZE];
let globals: [[i32; 2]; 2] = [[0, 0], [0, 0]];
let r = walker.assign_mv(
&mut dec,
&mut cdfs,
false,
/* use_intrabc = */ true,
MODE_NEARESTMV,
0,
0,
&stack,
&globals,
false,
false,
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
}
/// Helper: build the per-ref identity global-motion matrices
/// (`GmType[ref] = IDENTITY` for every ref). The §7.10.2.1
/// short-circuit path returns `[0, 0]` for any ref under this
/// configuration.
fn identity_gm() -> ([i32; 8], [[i32; 6]; 8]) {
let mut params = [[0i32; 6]; 8];
// §5.9.24 identity default: gm_params[ref][2] = gm_params[ref][5]
// = 1 << WARPEDMODEL_PREC_BITS; other slots = 0.
for p in &mut params {
p[2] = 1 << WARPEDMODEL_PREC_BITS;
p[5] = 1 << WARPEDMODEL_PREC_BITS;
}
([GM_TYPE_IDENTITY; 8], params)
}
/// Helper: a sign-bias array (§5.9.20) — all zero (every ref same
/// direction). The §7.10.2.13 negation arm depends on this.
fn zero_sign_bias() -> [i32; 8] {
[0; 8]
}
/// §7.10.2.10 lower-precision contract: with `allow_high_precision_mv
/// == true`, the MV passes through unchanged.
#[test]
fn lower_mv_precision_high_precision_passthrough() {
let mv = lower_mv_precision([7, -5], true, false);
assert_eq!(mv, [7, -5]);
let mv2 = lower_mv_precision([7, -5], true, true);
// §7.10.2.10 explicitly terminates on `allow_high_precision_mv
// == 1` before checking force_integer_mv.
assert_eq!(mv2, [7, -5]);
}
/// §7.10.2.10 with high_precision off: only the least significant
/// bit is rounded toward zero.
#[test]
fn lower_mv_precision_strips_lsb_when_high_precision_off() {
assert_eq!(lower_mv_precision([7, 5], false, false), [6, 4]);
assert_eq!(lower_mv_precision([-7, -5], false, false), [-6, -4]);
// Already-even values pass through.
assert_eq!(lower_mv_precision([6, -4], false, false), [6, -4]);
assert_eq!(lower_mv_precision([0, 0], false, false), [0, 0]);
}
/// §7.10.2.10 with `force_integer_mv == 1`: all three fractional
/// bits are stripped — `(|a| + 3) >> 3 << 3`.
#[test]
fn lower_mv_precision_force_integer_strips_all_fractional() {
// |11| = 11 → (11+3)>>3 = 1 → 1<<3 = 8
assert_eq!(lower_mv_precision([11, -11], false, true), [8, -8]);
// |0| = 0 → 0
assert_eq!(lower_mv_precision([0, 0], false, true), [0, 0]);
// |4| = 4 → (4+3)>>3 = 0 → 0
assert_eq!(lower_mv_precision([4, -4], false, true), [0, 0]);
// |12| = 12 → (12+3)>>3 = 1 → 8
assert_eq!(lower_mv_precision([12, -12], false, true), [8, -8]);
// |16| = 16 → (16+3)>>3 = 2 → 16
assert_eq!(lower_mv_precision([16, -16], false, true), [16, -16]);
}
/// §5.11.53 clamp_mv_row: clamps to `mbToTopEdge - border` /
/// `mbToBottomEdge + border`.
#[test]
fn clamp_mv_row_brackets_to_frame_with_border() {
// 8×8 mi grid, bh4 = 1 (BLOCK_4X4), mi_row = 4.
// mbToTopEdge = -(4 * 4 * 8) = -128.
// mbToBottomEdge = ((8 - 1 - 4) * 4 * 8) = 96.
// border = MV_BORDER (128) + 4 * 8 = 160 (for bh = 4).
let row_border = MV_BORDER + 4 * 8;
let mi_rows = 8;
let bh4 = 1;
let mi_row = 4;
// In-range: 0 passes through.
assert_eq!(clamp_mv_row(0, row_border, mi_rows, bh4, mi_row), 0);
// Out-of-range top: clamps to mbToTopEdge - border = -128 - 160 = -288.
assert_eq!(clamp_mv_row(-9999, row_border, mi_rows, bh4, mi_row), -288);
// Out-of-range bottom: clamps to mbToBottomEdge + border = 96 + 160 = 256.
assert_eq!(clamp_mv_row(9999, row_border, mi_rows, bh4, mi_row), 256);
}
/// §5.11.54 clamp_mv_col: same shape as clamp_mv_row but on the
/// horizontal axis.
#[test]
fn clamp_mv_col_brackets_to_frame_with_border() {
let col_border = MV_BORDER + 4 * 8;
let mi_cols = 8;
let bw4 = 1;
let mi_col = 4;
assert_eq!(clamp_mv_col(0, col_border, mi_cols, bw4, mi_col), 0);
assert_eq!(clamp_mv_col(-9999, col_border, mi_cols, bw4, mi_col), -288);
assert_eq!(clamp_mv_col(9999, col_border, mi_cols, bw4, mi_col), 256);
}
/// §7.10.2.11 sorting: stable descending sort of WeightStack pulls
/// the heaviest entry to the front. The companion RefStackMv rows
/// swap in lockstep.
#[test]
fn sort_stack_segment_descends_by_weight() {
let mut state = MvStackState::new();
state.num_mv_found = 4;
state.weight_stack[0] = 10;
state.weight_stack[1] = 50;
state.weight_stack[2] = 30;
state.weight_stack[3] = 20;
state.ref_stack_mv[0][0] = [100, 0];
state.ref_stack_mv[1][0] = [200, 0];
state.ref_stack_mv[2][0] = [300, 0];
state.ref_stack_mv[3][0] = [400, 0];
sort_stack_segment(&mut state, 0, 4, false);
assert_eq!(state.weight_stack[0], 50);
assert_eq!(state.weight_stack[1], 30);
assert_eq!(state.weight_stack[2], 20);
assert_eq!(state.weight_stack[3], 10);
assert_eq!(state.ref_stack_mv[0][0], [200, 0]);
assert_eq!(state.ref_stack_mv[1][0], [300, 0]);
assert_eq!(state.ref_stack_mv[2][0], [400, 0]);
assert_eq!(state.ref_stack_mv[3][0], [100, 0]);
}
/// §7.10.2.11 sort: equal weights preserve original order (stable).
#[test]
fn sort_stack_segment_is_stable_on_equal_weights() {
let mut state = MvStackState::new();
state.num_mv_found = 3;
state.weight_stack[0] = 10;
state.weight_stack[1] = 10;
state.weight_stack[2] = 10;
state.ref_stack_mv[0][0] = [1, 0];
state.ref_stack_mv[1][0] = [2, 0];
state.ref_stack_mv[2][0] = [3, 0];
sort_stack_segment(&mut state, 0, 3, false);
// Equal weights ⇒ no swaps; order preserved.
assert_eq!(state.ref_stack_mv[0][0], [1, 0]);
assert_eq!(state.ref_stack_mv[1][0], [2, 0]);
assert_eq!(state.ref_stack_mv[2][0], [3, 0]);
}
/// §7.10.2.1 setup-global-mv on `INTRA_FRAME` (refList = 0
/// pointing at the implicit intra ref ordinal): returns `[0, 0]`.
#[test]
fn setup_global_mv_intra_frame_is_zero() {
let (gm_type, gm_params) = identity_gm();
let mv = setup_global_mv(0, 0, BLOCK_8X8, 0, &gm_type, &gm_params, false, false);
assert_eq!(mv, [0, 0]);
}
/// §7.10.2.1 setup-global-mv on `IDENTITY` (the §5.9.24
/// `FrameIsIntra` default): returns `[0, 0]` regardless of which
/// inter ref is selected.
#[test]
fn setup_global_mv_identity_is_zero_for_inter_refs() {
let (gm_type, gm_params) = identity_gm();
for ref_idx in 1..=7 {
let mv = setup_global_mv(0, 0, BLOCK_8X8, ref_idx, &gm_type, &gm_params, false, false);
assert_eq!(mv, [0, 0], "ref {ref_idx} with IDENTITY GmType");
}
}
/// §7.10.2.1 setup-global-mv on `TRANSLATION`: returns
/// `[gm_params[ref][0] >> (PREC - 3), gm_params[ref][1] >> (PREC - 3)]`.
/// With high-precision off, the LSB is then stripped.
#[test]
fn setup_global_mv_translation_shifts_params() {
let (mut gm_type, mut gm_params) = identity_gm();
// LAST_FRAME = 1 with TRANSLATION; gm_params[1][0] = 8192 (= 1
// << 13), gm_params[1][1] = -8192. WARPEDMODEL_PREC_BITS - 3 =
// 13, so the shift produces ±1 raw → after high-precision-off
// strip-LSB: 0. With high-precision-on: ±1.
gm_type[1] = GM_TYPE_TRANSLATION;
gm_params[1][0] = 1 << 13;
gm_params[1][1] = -(1 << 13);
let mv_hi = setup_global_mv(0, 0, BLOCK_8X8, 1, &gm_type, &gm_params, true, false);
assert_eq!(mv_hi, [1, -1]);
let mv_lo = setup_global_mv(0, 0, BLOCK_8X8, 1, &gm_type, &gm_params, false, false);
// After lower-precision strip: 1 has LSB set → 1 → 0 (toward zero).
assert_eq!(mv_lo, [0, 0]);
}
// ===================================================================
// §7.10 find_mv_stack tests (round 172)
// ===================================================================
/// §7.10 driver on an empty walker (no inter neighbours), single
/// prediction with IDENTITY global motion. Expected outcome:
/// `NumMvFound == 0`, `NewMvCount == 0`, all global MVs zero,
/// CloseMatches / TotalMatches == 0, NewMvContext == 0,
/// RefMvContext == 0.
#[test]
fn find_mv_stack_empty_walker_single_pred_identity_gm() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1], // LAST_FRAME, NONE — single pred
false,
false, // use_ref_frame_mvs
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
)
.unwrap();
assert_eq!(res.num_mv_found, 0, "no inter neighbours ⇒ stack empty");
assert_eq!(res.new_mv_count, 0);
assert_eq!(res.close_matches, 0);
assert_eq!(res.total_matches, 0);
assert_eq!(res.num_nearest, 0);
assert_eq!(res.num_new, 0);
assert_eq!(res.zero_mv_context, 0);
assert_eq!(res.new_mv_context, 0);
assert_eq!(res.ref_mv_context, 0);
assert_eq!(res.global_mvs[0], [0, 0]);
}
/// §7.10 driver on an empty walker, compound prediction with
/// IDENTITY global motion. The §7.10.2.12 extra-search compound
/// fill emits two global MVs into the stack: `NumMvFound == 2`,
/// every slot zero, weight 2.
#[test]
fn find_mv_stack_empty_walker_compound_identity_gm_fills_two() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, 7], // LAST_FRAME, ALTREF_FRAME — compound
true,
false,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
)
.unwrap();
// §7.10.2.12 compound: NumMvFound was 0 ⇒ extra-search emits
// both combinedMvs slots, each populated with GlobalMvs
// because RefIdMvs/RefDiffMvs are both empty.
assert_eq!(res.num_mv_found, 2);
assert_eq!(res.ref_stack_mv[0][0], [0, 0]);
assert_eq!(res.ref_stack_mv[0][1], [0, 0]);
assert_eq!(res.ref_stack_mv[1][0], [0, 0]);
assert_eq!(res.ref_stack_mv[1][1], [0, 0]);
assert_eq!(res.weight_stack[0], 2);
assert_eq!(res.weight_stack[1], 2);
// CloseMatches still 0 — no spatial neighbours were inter.
assert_eq!(res.close_matches, 0);
assert_eq!(res.num_nearest, 0);
}
/// §7.10 driver caller-bug guards.
#[test]
fn find_mv_stack_caller_bug_guards() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
// sub_size >= BLOCK_SIZES
assert!(matches!(
walker.find_mv_stack(
0,
0,
BLOCK_SIZES,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// mi_row out of grid
assert!(matches!(
walker.find_mv_stack(
99,
0,
BLOCK_4X4,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// ref_frame[0] out of range
assert!(matches!(
walker.find_mv_stack(
0,
0,
BLOCK_4X4,
[-1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// ref_frame[1] out of range
assert!(matches!(
walker.find_mv_stack(
0,
0,
BLOCK_4X4,
[1, 9],
true,
false,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// is_compound mismatch (claims compound but ref_frame[1] = NONE)
assert!(matches!(
walker.find_mv_stack(
0,
0,
BLOCK_4X4,
[1, -1],
true,
false,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// §7.10 r205 temporal-scan opt-in with an all-sentinel
/// motion-field grid runs to completion. The §7.10.2.6
/// `candMv[0] == MFMV_INVALID` early-return fires at every
/// position, so num_mv_found / weight_stack / ref_stack_mv are
/// not perturbed. The only observable side effect is the
/// §7.10.2.6 centre-cell ZeroMvContext stamp (`deltaRow == 0
/// && deltaCol == 0` ⇒ `ZeroMvContext = 1`), which the spec
/// sets BEFORE the invalid-sentinel return.
#[test]
fn find_mv_stack_temporal_scan_invalid_grid_only_stamps_zero_mv_context() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let r_opt_in = walker
.find_mv_stack(
0,
0,
BLOCK_4X4,
[1, -1],
false,
true, // use_ref_frame_mvs
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
)
.expect("invalid-grid opt-in runs to completion");
let r_opt_out = walker
.find_mv_stack(
0,
0,
BLOCK_4X4,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
)
.expect("opt-out path runs to completion");
// Stack contents identical: no candidate landed.
assert_eq!(r_opt_in.num_mv_found, r_opt_out.num_mv_found);
assert_eq!(r_opt_in.new_mv_count, r_opt_out.new_mv_count);
assert_eq!(r_opt_in.ref_stack_mv, r_opt_out.ref_stack_mv);
assert_eq!(r_opt_in.weight_stack, r_opt_out.weight_stack);
assert_eq!(r_opt_in.global_mvs, r_opt_out.global_mvs);
assert_eq!(r_opt_in.new_mv_context, r_opt_out.new_mv_context);
assert_eq!(r_opt_in.ref_mv_context, r_opt_out.ref_mv_context);
// §7.10.2.6 centre-cell stamp on the opt-in path:
// ZeroMvContext = 1.
assert_eq!(r_opt_in.zero_mv_context, 1);
// Opt-out path leaves the §7.10.2 step-16 default of 0.
assert_eq!(r_opt_out.zero_mv_context, 0);
}
/// r205 §7.9.1 motion-field grid initialisation:
/// `MotionFieldMvs::new_invalid` sizes the grid to `(mi_rows >> 1)
/// × (mi_cols >> 1)` per the §7.9.1 setup and fills every cell
/// with the `MFMV_INVALID = -32768` sentinel.
#[test]
fn motion_field_mvs_new_invalid_sizes_and_sentinels() {
let grid = MotionFieldMvs::new_invalid(16, 8);
assert_eq!(grid.h8(), 8);
assert_eq!(grid.w8(), 4);
for ref_frame in 1..=7 {
for y in 0..grid.h8() {
for x in 0..grid.w8() {
assert_eq!(grid.get(ref_frame, y, x), [MFMV_INVALID, MFMV_INVALID]);
}
}
}
}
/// r205 §7.9.2 motion-field stamp: `set` overwrites both
/// components; `get` returns the stamped value. Out-of-grid
/// reads return the sentinel.
#[test]
fn motion_field_mvs_set_get_round_trip_and_out_of_grid_sentinel() {
let mut grid = MotionFieldMvs::new_invalid(8, 8);
grid.set(/* LAST_FRAME = */ 1, 2, 3, [123, -456]);
assert_eq!(grid.get(1, 2, 3), [123, -456]);
// Other refs at (2, 3) unchanged.
assert_eq!(grid.get(2, 2, 3), [MFMV_INVALID, MFMV_INVALID]);
// Out-of-grid reads return the sentinel (the §7.10.2.6
// outer is_inside check is the caller's responsibility, but
// out-of-grid still doesn't panic).
assert_eq!(grid.get(1, 100, 0), [MFMV_INVALID, MFMV_INVALID]);
assert_eq!(grid.get(1, 0, 100), [MFMV_INVALID, MFMV_INVALID]);
// INTRA_FRAME (= 0) and out-of-range refs sentinel out.
assert_eq!(grid.get(0, 0, 0), [MFMV_INVALID, MFMV_INVALID]);
assert_eq!(grid.get(8, 0, 0), [MFMV_INVALID, MFMV_INVALID]);
}
/// r205 §7.9.2 `set_all_refs` stamps every reference frame at
/// the same `(y8, x8)` with the same MV — the §7.9.2 inner-loop
/// shape (`for dst = LAST_FRAME; dst <= ALTREF_FRAME; dst++`).
#[test]
fn motion_field_mvs_set_all_refs_stamps_every_ref() {
let mut grid = MotionFieldMvs::new_invalid(8, 8);
grid.set_all_refs(1, 2, [99, 100]);
for ref_frame in 1..=7 {
assert_eq!(grid.get(ref_frame, 1, 2), [99, 100]);
}
}
/// r205 §7.9.3 `get_mv_projection`: numerator == denominator
/// returns the source MV unchanged (modulo clamping). The
/// `Div_Mult[denom]` table is the round-to-nearest inverse:
/// `round(2^14 / denom)`. With numerator == denominator the
/// product `numerator * Div_Mult[denom]` ≈ 2^14, and the
/// `Round2Signed(mv * 2^14, 14)` recovers the input.
#[test]
fn get_mv_projection_identity_when_num_eq_denom() {
// numerator == denominator == 4: Div_Mult[4] = 4096 = 2^12.
// mv * 4 * 4096 = mv * 2^14 → Round2Signed(_, 14) = mv.
let mv = get_mv_projection([100, -100], 4, 4);
assert_eq!(mv, [100, -100]);
// denominator == 1 ⇒ Div_Mult[1] = 16384 = 2^14 → mv * 1 *
// 2^14, Round2Signed(_, 14) = mv.
let mv = get_mv_projection([42, 17], 1, 1);
assert_eq!(mv, [42, 17]);
}
/// r205 §7.9.3 clamp: projected components saturate at
/// `±((1 << MV_IN_USE_BITS) - 1)`. A large source MV scaled with
/// numerator > denominator overflows the §7.9.3 clamp range.
#[test]
fn get_mv_projection_clamps_into_mv_in_use_bits() {
// Source 30000 * 8 / 1 = 240000 ⇒ clamps to (1<<14)-1 = 16383.
let mv = get_mv_projection([30000, -30000], 8, 1);
assert_eq!(mv, [16383, -16383]);
}
/// r205 §7.9.4 `get_block_position` valid: a tiny projection
/// keeps the position inside both the §7.9.4 grid bound
/// (`max8 = mi_rows/cols >> 1`) and the per-direction
/// `MAX_OFFSET_*` window.
#[test]
fn get_block_position_valid_for_small_delta() {
// mi_rows = mi_cols = 16 ⇒ max_y = max_x = 8.
// y8 = x8 = 4. delta = 0 ⇒ project returns (4, 4).
let pos = get_block_position(4, 4, /* dst_sign = */ 1, [0, 0], 16, 16);
assert_eq!(pos, Some((4, 4)));
}
/// r205 §7.9.4 `get_block_position` invalid: a projection that
/// would move the position outside the §7.9.4 grid bound
/// returns `None` (= `posValid == 0`).
#[test]
fn get_block_position_invalid_when_outside_grid() {
// y8 = 7 (the bottom row). A large positive projMv[0] pushes
// the projected position past `max_y = 8`. delta = 1 << 14
// (=16384) ⇒ offset8 = 16384 >> 6 = 256, well past the row
// bound. Returns None.
let pos = get_block_position(4, 7, /* dst_sign = */ 1, [16384, 0], 16, 16);
assert_eq!(pos, None);
}
/// r205 §7.10.2.6 temporal-sample with a valid centre-cell MV
/// appends a candidate to the stack. Single-pred, BLOCK_4X4 at
/// (mi_row=2, mi_col=2): the §7.10.2.6 read site is
/// `((mi_row + 0) | 1) >> 1, ((mi_col + 0) | 1) >> 1` = `(1, 1)`.
/// Stamping `MotionFieldMvs[LAST_FRAME][1][1] = [40, -32]` makes
/// the temporal sample land a candidate with weight 2.
#[test]
fn temporal_scan_single_pred_centre_cell_lands_candidate() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let (gm_type, gm_params) = identity_gm();
// mi_rows = mi_cols = 8 ⇒ h8 = w8 = 4. Stamp at (y8=1, x8=1).
let mut mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
mfmvs.set(/* LAST_FRAME = */ 1, 1, 1, [40, -32]);
let res = walker
.find_mv_stack(
2,
2,
BLOCK_4X4,
[1, -1],
false,
/* use_ref_frame_mvs = */ true,
gm_type,
gm_params,
zero_sign_bias(),
/* allow_high_precision_mv = */ false,
/* force_integer_mv = */ false,
&mfmvs,
)
.expect("§7.10.2 driver with valid temporal candidate runs to completion");
assert!(
res.num_mv_found >= 1,
"temporal scan should land at least one candidate from the populated grid cell"
);
// §7.10.2.6 single-pred writes to slot [0][0..2]. Per
// §7.10.2.10 with `allow_high_precision_mv == 0` and
// `force_integer_mv == 0`, the LSBs are stripped: 40 stays,
// -32 stays (both are even). After §7.10.2.14 clamp the
// values lie inside the [-MV_BORDER, +MV_BORDER + bw*8] range.
assert_eq!(res.ref_stack_mv[0][0], [40, -32]);
}
/// r205 §7.10.2.6 temporal-sample compound: both reference
/// frames' MotionFieldMvs cells must be non-sentinel or the
/// candidate is dropped. Stamp only `LAST_FRAME` ⇒ the compound
/// branch returns early on `RefFrame[1]`'s invalid slot, no
/// candidate lands.
#[test]
fn temporal_scan_compound_drops_when_second_ref_sentinel() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let (gm_type, gm_params) = identity_gm();
let mut mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
// Only ref 1 stamped; ref 7 stays sentinel.
mfmvs.set(1, 1, 1, [40, -32]);
let res = walker
.find_mv_stack(
2,
2,
BLOCK_4X4,
[1, 7], // compound: LAST + ALTREF
true,
/* use_ref_frame_mvs = */ true,
gm_type,
gm_params,
zero_sign_bias(),
false,
false,
&mfmvs,
)
.expect("§7.10.2 driver with mixed-sentinel compound grid runs to completion");
// §7.10.2.12 extra-search compound fill still emits the two
// global-MV slots. The temporal sample dropped its candidate
// because slot 1's grid cell carries the sentinel.
for idx in 0..res.num_mv_found as usize {
// No temporal candidate (which would have been [40, -32]
// on slot [0]); only the global-MV fill (= [0, 0] in the
// identity-warp test fixture).
assert_ne!(res.ref_stack_mv[idx][0], [40, -32]);
}
}
/// §7.10.2 with a single stamped inter neighbour matching the
/// block's RefFrame. The §7.10.2.2 scan_row(-1) finds the
/// neighbour, the §7.10.2.8 search-stack appends it (NumMvFound =
/// 1), and the §7.10.2 step 15 stamp adds REF_CAT_LEVEL bonus.
/// After sort, the §7.10.2.12 extra-search runs (single-pred,
/// num_mv_found = 1 < 2) but only fills global slots (not
/// counted in NumMvFound).
#[test]
fn find_mv_stack_single_inter_neighbour_above_lands_in_stack() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
// Stamp a BLOCK_8X8 inter-block at (mi_row=2, mi_col=4) that
// uses RefFrame[0] = LAST_FRAME = 1, with NEWMV mode and a
// distinctive MV [32, 64].
walker.stamp_inter_neighbour(2, 4, BLOCK_8X8, [1, -1], MODE_NEWMV, [[32, 64], [0, 0]]);
let (gm_type, gm_params) = identity_gm();
// Decode block at (mi_row=4, mi_col=4) BLOCK_8X8 with same
// RefFrame[0] = 1. The scan_row(-1) walks 4x4 cells at row =
// 4 + (-1) = 3, cols 4..=4+bw4=4+2 = 4,5. Wait — scan_row(-1)
// reads MiSizes[3][4]; we stamped (2, 4) over 2x2 → cells
// (2..=3, 4..=5) so (3, 4) is covered.
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
true, // high-precision-on so MV passes through unchanged
false,
&mfmvs,
)
.unwrap();
assert_eq!(res.num_mv_found, 1, "one inter neighbour ⇒ one stack slot");
assert_eq!(res.ref_stack_mv[0][0], [32, 64]);
assert_eq!(res.new_mv_count, 1, "NEWMV mode increments NewMvCount");
assert_eq!(res.num_nearest, 1);
assert_eq!(res.num_new, 1);
// §7.10.2 step 15: REF_CAT_LEVEL bonus added for the close-match.
// The original weight from scan_row was len * 2 = bw4*2 = 4.
assert_eq!(res.weight_stack[0], REF_CAT_LEVEL + 4);
// CloseMatches: 1 (foundAboveMatch = 1, foundLeftMatch = 0).
assert_eq!(res.close_matches, 1);
// TotalMatches: 1 (same as close — outer rings find nothing).
assert_eq!(res.total_matches, 1);
// §7.10.2.14: CloseMatches=1 ⇒ NewMvContext = 3 - min(numNew, 1)
// = 3 - 1 = 2; RefMvContext = 2 + TotalMatches = 3.
assert_eq!(res.new_mv_context, 2);
assert_eq!(res.ref_mv_context, 3);
}
/// §7.10.2 with an inter neighbour to the LEFT. The §7.10.2.3
/// scan_col(-1) finds it; foundLeftMatch = 1.
#[test]
fn find_mv_stack_single_inter_neighbour_left() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(
4,
2,
BLOCK_8X8,
[1, -1],
MODE_NEARESTMV, // not NEWMV ⇒ new_mv_count stays 0
[[16, -32], [0, 0]],
);
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
true,
false,
&mfmvs,
)
.unwrap();
assert_eq!(res.num_mv_found, 1);
assert_eq!(res.ref_stack_mv[0][0], [16, -32]);
assert_eq!(res.new_mv_count, 0, "NEARESTMV is not has_newmv");
assert_eq!(res.close_matches, 1);
// §7.10.2.14: CloseMatches=1, numNew=0 ⇒ NewMvContext = 3 - 0 = 3.
assert_eq!(res.new_mv_context, 3);
assert_eq!(res.ref_mv_context, 3);
}
/// §7.10.2 with TWO inter neighbours (one above + one left). Both
/// found ⇒ CloseMatches = 2, NewMvContext / RefMvContext from the
/// CloseMatches >= 2 arm.
#[test]
fn find_mv_stack_above_and_left_neighbours() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(2, 4, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[10, 0], [0, 0]]);
walker.stamp_inter_neighbour(4, 2, BLOCK_8X8, [1, -1], MODE_NEWMV, [[20, 0], [0, 0]]);
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
true,
false,
&mfmvs,
)
.unwrap();
assert_eq!(res.num_mv_found, 2);
assert_eq!(res.close_matches, 2);
assert_eq!(res.total_matches, 2);
// After sort: both weights = REF_CAT_LEVEL + 4; identical, so
// order is preserved (stable). Above was found first (scan_row
// before scan_col), so it's index 0.
assert_eq!(res.ref_stack_mv[0][0], [10, 0]);
assert_eq!(res.ref_stack_mv[1][0], [20, 0]);
// numNew = 1 (only the left neighbour was NEWMV).
assert_eq!(res.num_new, 1);
// §7.10.2.14: CloseMatches >= 2 ⇒ NewMvContext = 5 - min(numNew, 1)
// = 5 - 1 = 4; RefMvContext = 5.
assert_eq!(res.new_mv_context, 4);
assert_eq!(res.ref_mv_context, 5);
}
/// §7.10.2 with an inter neighbour whose RefFrame does NOT match
/// the block's RefFrame: the §7.10.2.7 candidate gate excludes it
/// from the primary spatial scan (CloseMatches stays 0, numNearest
/// stays 0). The §7.10.2.12 extra-search single-pred branch
/// (§7.10.2.13) still picks it up as a fallback MV candidate
/// because all `candRef > INTRA_FRAME` neighbours qualify.
#[test]
fn find_mv_stack_mismatched_ref_excluded_from_close_matches_picked_up_by_extra_search() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
// Neighbour uses GOLDEN_FRAME (= 4); block wants LAST_FRAME
// (= 1).
walker.stamp_inter_neighbour(2, 4, BLOCK_8X8, [4, -1], MODE_NEWMV, [[42, 42], [0, 0]]);
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
true,
false,
&mfmvs,
)
.unwrap();
// §7.10.2 step 12-14: foundAboveMatch / foundLeftMatch only set
// when §7.10.2.7 add_ref_mv_candidate fires search_stack —
// which requires the cand_ref to match RefFrame[0]. Mismatched
// ref ⇒ no close-match.
assert_eq!(res.close_matches, 0);
assert_eq!(res.num_nearest, 0);
// §7.10.2.12 extra-search single-pred fallback still picks
// the neighbour up (the §7.10.2.13 branch adds any `candRef >
// INTRA_FRAME` MV regardless of ref match).
assert!(
res.num_mv_found >= 1,
"extra-search single-pred fallback adds mismatched-ref MV"
);
assert_eq!(res.ref_stack_mv[0][0], [42, 42]);
}
/// §7.10.2 dedupe: two identical inter neighbours (same MV, same
/// ref) ⇒ the second match increments the WeightStack rather than
/// creating a new slot. NumMvFound stays at 1.
#[test]
fn find_mv_stack_dedupes_identical_mv_neighbours() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
// Above and left both have the same RefFrame and MV.
walker.stamp_inter_neighbour(2, 4, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[7, 7], [0, 0]]);
walker.stamp_inter_neighbour(4, 2, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[7, 7], [0, 0]]);
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
true,
false,
&mfmvs,
)
.unwrap();
assert_eq!(res.num_mv_found, 1, "duplicate MV ⇒ accumulated weight");
assert_eq!(res.ref_stack_mv[0][0], [7, 7]);
// Both scan_row and scan_col matched ⇒ weight is 2 * (len*2) =
// 8 before the REF_CAT_LEVEL bonus, but we only get the bonus
// for slot 0 (numNearest = 1).
assert_eq!(res.weight_stack[0], REF_CAT_LEVEL + 8);
// Both Close matches still detected.
assert_eq!(res.close_matches, 2);
}
/// §7.10.2.12 extra-search on single-pred with one neighbour: the
/// global-MV tail extension does NOT increment NumMvFound for
/// single-pred (per the §7.10.2.12 note).
#[test]
fn find_mv_stack_single_pred_extra_search_does_not_increment_num_mv_found() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(2, 4, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[5, 5], [0, 0]]);
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
true,
false,
&mfmvs,
)
.unwrap();
// One spatial candidate. NumMvFound = 1; extra-search single-
// pred path does NOT add global-MV slots to the count.
assert_eq!(res.num_mv_found, 1);
}
/// §7.10 result struct surfaces every documented field — verify
/// the type-level shape.
#[test]
fn find_mv_stack_result_struct_shape() {
let r = FindMvStackResult {
num_mv_found: 0,
new_mv_count: 0,
ref_stack_mv: [[[0; 2]; 2]; MAX_REF_MV_STACK_SIZE],
weight_stack: [0; MAX_REF_MV_STACK_SIZE],
global_mvs: [[0, 0], [0, 0]],
new_mv_context: 0,
ref_mv_context: 0,
zero_mv_context: 0,
drl_ctx_stack: [0; MAX_REF_MV_STACK_SIZE],
close_matches: 0,
total_matches: 0,
num_nearest: 0,
num_new: 0,
};
assert_eq!(r.ref_stack_mv.len(), MAX_REF_MV_STACK_SIZE);
assert_eq!(r.weight_stack.len(), MAX_REF_MV_STACK_SIZE);
assert_eq!(r.drl_ctx_stack.len(), MAX_REF_MV_STACK_SIZE);
}
/// `has_newmv` truth table per §7.10.2.9.
#[test]
fn has_newmv_truth_table() {
// True modes.
assert!(has_newmv(MODE_NEWMV));
assert!(has_newmv(MODE_NEW_NEWMV));
assert!(has_newmv(MODE_NEAR_NEWMV));
assert!(has_newmv(MODE_NEW_NEARMV));
assert!(has_newmv(MODE_NEAREST_NEWMV));
assert!(has_newmv(MODE_NEW_NEARESTMV));
// False modes.
assert!(!has_newmv(MODE_NEARESTMV));
assert!(!has_newmv(MODE_NEARMV));
assert!(!has_newmv(MODE_GLOBALMV));
assert!(!has_newmv(MODE_NEAREST_NEARESTMV));
assert!(!has_newmv(MODE_NEAR_NEARMV));
assert!(!has_newmv(MODE_GLOBAL_GLOBALMV));
}
/// §7.10.2 driver — `MAX_REF_MV_STACK_SIZE` constant matches the
/// §3 spec value of 8.
#[test]
fn mv_stack_constants_match_spec() {
assert_eq!(MAX_REF_MV_STACK_SIZE, 8);
assert_eq!(REF_CAT_LEVEL, 640);
assert_eq!(MV_BORDER, 128);
assert_eq!(WARPEDMODEL_PREC_BITS, 16);
// Mode ordinals per §6.10.22 table.
assert_eq!(MODE_NEARESTMV, 14);
assert_eq!(MODE_NEWMV, 17);
assert_eq!(MODE_NEAREST_NEARESTMV, 18);
assert_eq!(MODE_NEW_NEWMV, 25);
}
/// §7.10.2.14 `DrlCtxStack[]` derivation matrix. Tests every
/// combination of (`w0 >= REF_CAT_LEVEL`, `w1 >= REF_CAT_LEVEL`)
/// at the (idx, idx+1) boundary.
#[test]
fn drl_ctx_stack_derivation_matrix() {
// We can't easily drive `drl_ctx_stack` derivation in isolation
// (it's inlined in find_mv_stack). Verify via a synthetic
// walker with carefully crafted neighbours.
// Easier: drive find_mv_stack with a single neighbour to
// produce NumMvFound = 1; DrlCtxStack[0] = 0 (no idx+1).
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(2, 4, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[1, 0], [0, 0]]);
let (gm_type, gm_params) = identity_gm();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let res = walker
.find_mv_stack(
4,
4,
BLOCK_8X8,
[1, -1],
false,
false,
gm_type,
gm_params,
zero_sign_bias(),
true,
false,
&mfmvs,
)
.unwrap();
assert_eq!(res.num_mv_found, 1);
// Only slot 0 has a candidate; DrlCtxStack[0] = 0 since there's
// no idx + 1.
assert_eq!(res.drl_ctx_stack[0], 0);
}
// ===================================================================
// §5.11.27 read_motion_mode + §7.10.3 has_overlappable_candidates +
// §7.10.4 find_warp_samples tests (round 175)
// ===================================================================
/// §5.11.27 default-table sanity: the row width is `MOTION_MODES +
/// 1 = 4` for `motion_mode` and `3` for the binary `use_obmc`.
#[test]
fn use_obmc_default_table_shape() {
assert_eq!(DEFAULT_USE_OBMC_CDF.len(), BLOCK_SIZES);
for row in DEFAULT_USE_OBMC_CDF.iter() {
assert_eq!(row.len(), 3);
// Last slot is the §8.3 adaptation counter (starts at 0).
assert_eq!(row[2], 0);
// Middle slot is the §8.3 cumulative-frequency cap (32768).
assert_eq!(row[1], 32768);
// First slot is in `1..32768` (a valid binary CDF
// threshold).
assert!(row[0] >= 1 && row[0] < 32768);
}
}
/// §8.3.2 `use_obmc` selector returns the §9.4 default row for
/// every `MiSize` and `None` for out-of-range.
#[test]
fn use_obmc_cdf_selector_returns_default_rows() {
let mut ctx = TileCdfContext::new_from_defaults();
for (i, expected) in DEFAULT_USE_OBMC_CDF.iter().enumerate() {
let row = ctx.use_obmc_cdf(i).unwrap();
assert_eq!(row[0], expected[0]);
assert_eq!(row[1], 32768);
assert_eq!(row[2], 0);
}
assert!(ctx.use_obmc_cdf(BLOCK_SIZES).is_none());
assert!(ctx.use_obmc_cdf(BLOCK_SIZES + 11).is_none());
}
/// §6.10.26 motion-mode ordinal sanity: the three §5.11.27 result
/// values match the spec's `SIMPLE = 0` / `OBMC = 1` /
/// `WARPED_CAUSAL = 2` enumeration.
#[test]
fn motion_mode_ordinals_match_spec() {
assert_eq!(MOTION_MODE_SIMPLE, 0);
assert_eq!(MOTION_MODE_OBMC, 1);
assert_eq!(MOTION_MODE_WARPED_CAUSAL, 2);
assert!((MOTION_MODE_WARPED_CAUSAL as usize) < MOTION_MODES);
}
/// §7.10.3: a fresh walker has no decoded neighbours → no
/// overlappable candidates → returns false.
#[test]
fn has_overlappable_candidates_fresh_walker_returns_false() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
// Block at (2, 2) sees no inter neighbours.
assert!(!walker.has_overlappable_candidates(2, 2, BLOCK_8X8));
// Block at (0, 0) has no above / left neighbours at all.
assert!(!walker.has_overlappable_candidates(0, 0, BLOCK_8X8));
}
/// §7.10.3: a stamped inter neighbour above the block flips the
/// result to true.
#[test]
fn has_overlappable_candidates_above_inter_neighbour() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
// Stamp an inter block at (0, 0..=1): RefFrames[0][1][0] =
// LAST_FRAME > INTRA_FRAME, so the §7.10.3 above-walk for the
// block at (2, 0) BLOCK_8X8 hits the (MiRow - 1 = 1, x4 | 1 =
// 1) probe.
walker.stamp_inter_neighbour(1, 0, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[0, 0], [0, 0]]);
assert!(walker.has_overlappable_candidates(2, 0, BLOCK_8X8));
}
/// §7.10.3: a stamped inter neighbour to the left flips the result
/// to true (left-walk arm).
#[test]
fn has_overlappable_candidates_left_inter_neighbour() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
// Stamp an inter block at (0, 0..=1) — the block at (0, 2) of
// BLOCK_8X8 sees (y4 | 1 = 1, MiCol - 1 = 1) as an inter
// neighbour.
walker.stamp_inter_neighbour(0, 0, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[0, 0], [0, 0]]);
assert!(walker.has_overlappable_candidates(0, 2, BLOCK_8X8));
}
/// §7.10.4: a fresh walker has no decoded inter neighbours so
/// `find_warp_samples` returns 0 — the §5.11.27 caller then takes
/// the `use_obmc` arm.
#[test]
fn find_warp_samples_fresh_walker_returns_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let ns = walker.find_warp_samples(2, 2, BLOCK_8X8, 1, [[0, 0], [0, 0]]);
assert_eq!(ns, 0);
}
/// §7.10.4: an inter neighbour with the SAME `RefFrame[0]` and a
/// matching MV (within `threshold`) counts as a valid sample.
#[test]
fn find_warp_samples_matching_neighbour_increments_num_samples() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
// Stamp a same-ref inter neighbour above the block at (2, 0).
// BLOCK_8X8 = 2x2 cells; the above-walk fires
// `add_sample(-1, 0)`. With MV = (1, 1) on neighbour and (1, 1)
// on the block, `mvDiffRow + mvDiffCol = 0 <= 16`, so the
// sample is valid.
walker.stamp_inter_neighbour(0, 0, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[1, 1], [0, 0]]);
let ns = walker.find_warp_samples(2, 0, BLOCK_8X8, 1, [[1, 1], [0, 0]]);
assert!(
ns >= 1,
"matching same-ref inter neighbour ⇒ NumSamples >= 1"
);
}
/// §7.10.4: a neighbour with a DIFFERENT `RefFrame[0]` is rejected
/// in the `add_sample` ref-frame check → `NumSamples` stays at 0.
#[test]
fn find_warp_samples_mismatched_ref_yields_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
// Neighbour ref_frame[0] = 2 (LAST2_FRAME); query with
// ref_frame_0 = 1 (LAST_FRAME) — `add_sample` short-circuits.
walker.stamp_inter_neighbour(0, 0, BLOCK_8X8, [2, -1], MODE_NEARESTMV, [[0, 0], [0, 0]]);
let ns = walker.find_warp_samples(2, 0, BLOCK_8X8, 1, [[0, 0], [0, 0]]);
assert_eq!(ns, 0);
}
/// §5.11.27 Arm 1: `skip_mode == 1` short-circuits to SIMPLE with
/// no S() read.
#[test]
fn read_motion_mode_skip_mode_arm_returns_simple_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
2,
2,
BLOCK_8X8,
/* skip_mode = */ 1,
/* is_compound = */ false,
/* ref_frame = */ [1, 7],
/* y_mode = */ MODE_NEAREST_NEARESTMV,
/* num_samples = */ 0,
/* is_motion_mode_switchable = */ true,
/* allow_warped_motion = */ true,
/* force_integer_mv = */ false,
[GM_TYPE_IDENTITY; 8],
[false; 7],
)
.unwrap();
assert_eq!(mm, MOTION_MODE_SIMPLE);
assert_eq!(dec.position(), pos_before, "skip_mode arm reads 0 bits");
}
/// §5.11.27 Arm 2: `!is_motion_mode_switchable` short-circuits to
/// SIMPLE with no S() read.
#[test]
fn read_motion_mode_not_switchable_returns_simple_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
2,
2,
BLOCK_16X16,
0,
false,
[1, -1],
MODE_NEARESTMV,
3,
/* is_motion_mode_switchable = */ false,
true,
false,
[GM_TYPE_IDENTITY; 8],
[false; 7],
)
.unwrap();
assert_eq!(mm, MOTION_MODE_SIMPLE);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.27 Arm 3: `Min(Block_Width, Block_Height) < 8` short-
/// circuits to SIMPLE with no S() read.
#[test]
fn read_motion_mode_small_block_returns_simple_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
// BLOCK_4X4: width 4 < 8 ⇒ arm fires.
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
0,
false,
[1, -1],
MODE_NEARESTMV,
0,
true,
true,
false,
[GM_TYPE_IDENTITY; 8],
[false; 7],
)
.unwrap();
assert_eq!(mm, MOTION_MODE_SIMPLE);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.27 Arm 4: GLOBALMV (or GLOBAL_GLOBALMV) + non-TRANSLATION
/// gm_type for `RefFrame[0]` ⇒ SIMPLE with no S() read (gated by
/// `!force_integer_mv`).
#[test]
fn read_motion_mode_globalmv_with_rotzoom_returns_simple() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mut gm_type = [GM_TYPE_IDENTITY; 8];
// RefFrame[0] = 1 (LAST_FRAME) is ROTZOOM (= 2 > TRANSLATION = 1).
gm_type[1] = GM_TYPE_ROTZOOM;
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
false,
[1, -1],
MODE_GLOBALMV,
3,
true,
true,
/* force_integer_mv = */ false,
gm_type,
[false; 7],
)
.unwrap();
assert_eq!(mm, MOTION_MODE_SIMPLE);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.27 Arm 5a: `isCompound == true` short-circuits to SIMPLE
/// with no S() read.
#[test]
fn read_motion_mode_compound_returns_simple() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
/* is_compound = */ true,
[1, 7],
MODE_NEAREST_NEARESTMV,
3,
true,
true,
false,
[GM_TYPE_IDENTITY; 8],
[false; 7],
)
.unwrap();
assert_eq!(mm, MOTION_MODE_SIMPLE);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.27 Arm 5c: `!has_overlappable_candidates()` short-circuits
/// to SIMPLE with no S() read. A fresh walker has no overlappable
/// candidates ⇒ this arm fires for a non-compound, switchable,
/// large-enough, non-GLOBALMV block.
#[test]
fn read_motion_mode_no_overlappable_candidates_returns_simple() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
4,
4,
BLOCK_16X16,
0,
false,
[1, -1],
MODE_NEARESTMV,
0,
/* is_motion_mode_switchable = */ true,
true,
false,
[GM_TYPE_IDENTITY; 8],
[false; 7],
)
.unwrap();
assert_eq!(mm, MOTION_MODE_SIMPLE);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.27 post-find_warp_samples Arm A (`use_obmc` S()): with
/// `force_integer_mv = true` and an overlappable above neighbour,
/// the `use_obmc` CDF is consulted and adapts.
///
/// Witness is CDF adaptation (with `disable_cdf_update = false`):
/// after a successful Arm-A read the `use_obmc` row for `BLOCK_8X8`
/// must differ from the §9.4 default value (the §8.3
/// `update_cdf` rescales every slot). The decoded `motion_mode`
/// is in `{SIMPLE, OBMC}` (no WARPED_CAUSAL on Arm A); the
/// `motion_mode` CDF row for `BLOCK_8X8` is left unchanged (Arm B
/// did not fire).
#[test]
fn read_motion_mode_use_obmc_arm_adapts_use_obmc_cdf() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(1, 0, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[0, 0], [0, 0]]);
let mut cdfs = TileCdfContext::new_from_defaults();
let use_obmc_before = cdfs.use_obmc[BLOCK_8X8];
let motion_mode_before = cdfs.motion_mode[BLOCK_8X8];
let bytes = [0u8; 16];
// disable_cdf_update = false ⇒ §8.3 update_cdf runs after the S()
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
2,
0,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
0,
true,
true,
/* force_integer_mv = */ true, // forces Arm A
[GM_TYPE_IDENTITY; 8],
[false; 7],
)
.unwrap();
// Arm A returns SIMPLE or OBMC, never WARPED_CAUSAL.
assert!(
mm == MOTION_MODE_SIMPLE || mm == MOTION_MODE_OBMC,
"Arm A must return SIMPLE or OBMC; got {mm}"
);
// The §8.3 update_cdf walked the use_obmc row (witness that S()
// fired against it).
assert_ne!(
cdfs.use_obmc[BLOCK_8X8], use_obmc_before,
"Arm A must read use_obmc S() ⇒ §8.3 update_cdf mutates the row"
);
// The motion_mode row is untouched (Arm B did NOT fire).
assert_eq!(
cdfs.motion_mode[BLOCK_8X8], motion_mode_before,
"Arm A must NOT read motion_mode S()"
);
}
/// §5.11.27 post-find_warp_samples Arm B (`motion_mode` S()): with
/// `force_integer_mv = false`, `num_samples > 0`,
/// `allow_warped_motion = true`, `is_scaled = false`, the
/// `motion_mode` CDF is consulted and adapts. The `use_obmc` row is
/// left unchanged (Arm A did not fire).
#[test]
fn read_motion_mode_motion_mode_arm_adapts_motion_mode_cdf() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(1, 0, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[0, 0], [0, 0]]);
let mut cdfs = TileCdfContext::new_from_defaults();
let motion_mode_before = cdfs.motion_mode[BLOCK_8X8];
let use_obmc_before = cdfs.use_obmc[BLOCK_8X8];
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
2,
0,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
/* num_samples = */ 2,
true,
/* allow_warped_motion = */ true,
/* force_integer_mv = */ false,
[GM_TYPE_IDENTITY; 8],
/* is_scaled_per_ref = */ [false; 7],
)
.unwrap();
assert!(
(mm as usize) < MOTION_MODES,
"Arm B return must be in 0..MOTION_MODES; got {mm}"
);
assert_ne!(
cdfs.motion_mode[BLOCK_8X8], motion_mode_before,
"Arm B must read motion_mode S() ⇒ §8.3 update_cdf mutates the row"
);
assert_eq!(
cdfs.use_obmc[BLOCK_8X8], use_obmc_before,
"Arm B must NOT read use_obmc S()"
);
}
/// §5.11.27 post-find_warp_samples Arm A forced by
/// `is_scaled(RefFrame[0]) = true`. With LAST_FRAME flagged as
/// scaled, even `force_integer_mv = false` + `num_samples > 0` +
/// `allow_warped_motion = true` must route through Arm A
/// (use_obmc). Witness via CDF adaptation.
#[test]
fn read_motion_mode_scaled_ref_routes_to_use_obmc_arm() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.stamp_inter_neighbour(1, 0, BLOCK_8X8, [1, -1], MODE_NEARESTMV, [[0, 0], [0, 0]]);
let mut cdfs = TileCdfContext::new_from_defaults();
let use_obmc_before = cdfs.use_obmc[BLOCK_8X8];
let motion_mode_before = cdfs.motion_mode[BLOCK_8X8];
let mut is_scaled = [false; 7];
is_scaled[0] = true; // LAST_FRAME (refFrame=1) → idx 0
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let _mm = walker
.read_motion_mode(
&mut dec,
&mut cdfs,
2,
0,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
/* num_samples = */ 4,
true,
/* allow_warped_motion = */ true,
/* force_integer_mv = */ false,
[GM_TYPE_IDENTITY; 8],
is_scaled,
)
.unwrap();
assert_ne!(
cdfs.use_obmc[BLOCK_8X8], use_obmc_before,
"is_scaled=true ⇒ Arm A (use_obmc) ⇒ §8.3 update_cdf mutates use_obmc row"
);
assert_eq!(
cdfs.motion_mode[BLOCK_8X8], motion_mode_before,
"is_scaled=true ⇒ Arm B (motion_mode) must NOT fire"
);
}
/// End-to-end through the §5.11.23 dispatcher: an inter block with
/// `skip_mode = 1` reaches `read_motion_mode` which short-circuits
/// to SIMPLE, and the dispatcher returns
/// `Ok(DecodedInterBlockModeInfo { motion_mode: SIMPLE, .. })`.
#[test]
fn cascade_read_motion_mode_skip_mode_surfaces_simple_through_aggregate() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
/* mi_row = */ 2,
/* mi_col = */ 2,
/* sub_size = */ BLOCK_8X8,
/* skip_mode = */ 1,
/* avail_u = */ true,
/* avail_l = */ true,
/* above_ref_frame = */ [0, -1],
/* left_ref_frame = */ [0, -1],
/* above_intra = */ true,
/* left_intra = */ true,
/* above_single = */ true,
/* left_single = */ true,
/* skip_mode_frame = */ [1, -1],
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_data = */ 0,
/* seg_skip_active = */ false,
/* seg_globalmv_active = */ false,
/* reference_select = */ false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ true,
/* allow_warped_motion = */ true,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
)
.expect("§5.11.27 SIMPLE short-circuit ⇒ dispatcher returns Ok");
assert_eq!(info.motion_mode, MOTION_MODE_SIMPLE);
assert_eq!(info.num_warp_samples, 0);
assert_eq!(info.mi_size, BLOCK_8X8);
}
// ===================================================================
// §5.11.28 read_interintra_mode tests (round 176)
// ===================================================================
/// §6.10.27 II_* ordinals — `II_DC_PRED = 0`, `II_V_PRED = 1`,
/// `II_H_PRED = 2`, `II_SMOOTH_PRED = 3`. They span
/// `0..INTERINTRA_MODES`.
#[test]
fn ii_mode_ordinals_match_spec() {
assert_eq!(II_DC_PRED, 0);
assert_eq!(II_V_PRED, 1);
assert_eq!(II_H_PRED, 2);
assert_eq!(II_SMOOTH_PRED, 3);
assert!((II_SMOOTH_PRED as usize) + 1 == INTERINTRA_MODES);
}
/// §5.11.28 outer gate closed via `skip_mode == 1`: no S() bit
/// read, `interintra == 0`, every per-symbol field `None`.
#[test]
fn read_interintra_mode_skip_mode_closes_gate_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interintra_mode(
&mut dec,
&mut cdfs,
BLOCK_16X16,
/* skip_mode = */ 1,
/* is_compound = */ false,
/* enable_interintra_compound = */ true,
)
.unwrap();
assert_eq!(r.interintra, 0);
assert_eq!(r.interintra_mode, None);
assert_eq!(r.wedge_interintra, None);
assert_eq!(r.wedge_index, None);
assert_eq!(dec.position(), pos_before, "skip_mode closes gate ⇒ 0 bits");
}
/// §5.11.28 outer gate closed via `!enable_interintra_compound`:
/// no S() bit read.
#[test]
fn read_interintra_mode_disabled_compound_closes_gate_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interintra_mode(
&mut dec,
&mut cdfs,
BLOCK_16X16,
0,
false,
/* enable_interintra_compound = */ false,
)
.unwrap();
assert_eq!(r.interintra, 0);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.28 outer gate closed via `isCompound == true`: no S() bit
/// read.
#[test]
fn read_interintra_mode_compound_closes_gate_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interintra_mode(
&mut dec,
&mut cdfs,
BLOCK_16X16,
0,
/* is_compound = */ true,
true,
)
.unwrap();
assert_eq!(r.interintra, 0);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.28 outer gate closed via `MiSize < BLOCK_8X8`: no S() bit
/// read. `BLOCK_4X4 = 0 < BLOCK_8X8 = 3` exercises the gate.
#[test]
fn read_interintra_mode_small_block_closes_gate_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interintra_mode(&mut dec, &mut cdfs, BLOCK_4X4, 0, false, true)
.unwrap();
assert_eq!(r.interintra, 0);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.28 outer gate closed via `MiSize > BLOCK_32X32`:
/// `BLOCK_64X64 = 12 > BLOCK_32X32 = 9` exercises the gate.
#[test]
fn read_interintra_mode_large_block_closes_gate_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interintra_mode(&mut dec, &mut cdfs, BLOCK_64X64, 0, false, true)
.unwrap();
assert_eq!(r.interintra, 0);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.28 outer gate open + `interintra` CDF forced to 0:
/// `interintra` S() fires (one bit consumed), inner arm skipped.
#[test]
fn read_interintra_mode_gate_open_interintra_zero_reads_one_symbol() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force interintra S() ⇒ 0 for the ctx = Size_Group[BLOCK_16X16]
// - 1 = 2 - 1 = 1 row.
let ctx = interintra_ctx(BLOCK_16X16).unwrap();
cdfs.inter_intra[ctx] = force_binary_cdf(0);
let inter_intra_before = cdfs.inter_intra[ctx];
let inter_intra_mode_before = cdfs.inter_intra_mode[ctx];
let bytes = [0u8; 16];
// disable_cdf_update = false ⇒ §8.3 update_cdf walks the row.
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let pos_before = dec.position();
let r = walker
.read_interintra_mode(&mut dec, &mut cdfs, BLOCK_16X16, 0, false, true)
.unwrap();
assert_eq!(r.interintra, 0);
assert_eq!(r.interintra_mode, None);
assert_eq!(r.wedge_interintra, None);
assert_eq!(r.wedge_index, None);
// The §8.3 update_cdf adapted the row (witness that S() fired).
assert_ne!(cdfs.inter_intra[ctx], inter_intra_before);
// The interintra_mode row stayed untouched (inner arm skipped).
assert_eq!(cdfs.inter_intra_mode[ctx], inter_intra_mode_before);
assert!(
dec.position() > pos_before,
"interintra S() must consume at least one bit"
);
}
/// §5.11.28 outer gate open + interintra forced to 1 + wedge
/// forced to 0: reads three S() symbols (interintra,
/// interintra_mode, wedge_interintra), skips wedge_index. All
/// three CDF rows adapt; wedge_index row is untouched.
#[test]
fn read_interintra_mode_inner_arm_no_wedge_reads_three_symbols() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let ctx = interintra_ctx(BLOCK_16X16).unwrap();
// Force interintra S() ⇒ 1 ⇒ enter inner arm.
cdfs.inter_intra[ctx] = force_binary_cdf(1);
// Force wedge_interintra S() ⇒ 0 ⇒ skip wedge_index.
cdfs.wedge_inter_intra[BLOCK_16X16] = force_binary_cdf(0);
let interintra_before = cdfs.inter_intra[ctx];
let mode_before = cdfs.inter_intra_mode[ctx];
let wedge_ii_before = cdfs.wedge_inter_intra[BLOCK_16X16];
let wedge_idx_before = cdfs.wedge_index[BLOCK_16X16];
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let r = walker
.read_interintra_mode(&mut dec, &mut cdfs, BLOCK_16X16, 0, false, true)
.unwrap();
assert_eq!(r.interintra, 1);
assert!(r.interintra_mode.is_some());
let ii_mode = r.interintra_mode.unwrap();
assert!(
(ii_mode as usize) < INTERINTRA_MODES,
"interintra_mode must be in 0..INTERINTRA_MODES; got {ii_mode}"
);
assert_eq!(r.wedge_interintra, Some(0));
assert_eq!(r.wedge_index, None);
// All three CDF rows adapted.
assert_ne!(cdfs.inter_intra[ctx], interintra_before);
assert_ne!(cdfs.inter_intra_mode[ctx], mode_before);
assert_ne!(cdfs.wedge_inter_intra[BLOCK_16X16], wedge_ii_before);
// wedge_index row stayed untouched (sub-branch skipped).
assert_eq!(cdfs.wedge_index[BLOCK_16X16], wedge_idx_before);
}
/// §5.11.28 outer gate open + interintra = 1 + wedge_interintra =
/// 1: drives the inner arm 100 times against fresh decoders and
/// asserts at least one trial produced `wedge_index = Some(_)`
/// alongside `wedge_interintra = Some(1)`, witnessing that the
/// four-symbol path is reachable. `disable_cdf_update = false` so
/// the CDFs adapt across trials and the observation distribution
/// migrates toward the forced bias.
///
/// Each successful inner-arm trial also implies the
/// `wedge_interintra` row was sampled, so its CDF adapts; we
/// witness the adaptation as a second property assertion.
#[test]
fn read_interintra_mode_inner_arm_wedge_path_reachable() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let ctx = interintra_ctx(BLOCK_16X16).unwrap();
// Bias both rows toward 1; CDF adaptation will keep them there.
cdfs.inter_intra[ctx] = force_binary_cdf(1);
cdfs.wedge_inter_intra[BLOCK_16X16] = force_binary_cdf(1);
let wedge_idx_before = cdfs.wedge_index[BLOCK_16X16];
let mut wedge_reached = 0usize;
let mut inner_arm_count = 0usize;
for trial in 0..100u32 {
// Vary the buffer slightly per trial so SymbolValue differs.
let mut bytes = [0u8; 32];
for (i, b) in bytes.iter_mut().enumerate() {
*b = ((trial.wrapping_mul(2654435761) ^ (i as u32 * 11)) & 0xFF) as u8;
}
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, false).unwrap();
let r = walker
.read_interintra_mode(&mut dec, &mut cdfs, BLOCK_16X16, 0, false, true)
.unwrap();
if r.interintra == 1 {
inner_arm_count += 1;
assert!(r.interintra_mode.is_some());
assert!(r.wedge_interintra.is_some());
if r.wedge_interintra == Some(1) {
assert!(
r.wedge_index.is_some(),
"wedge_interintra=1 ⇒ wedge_index Some"
);
let wi = r.wedge_index.unwrap();
assert!(
(wi as usize) < WEDGE_TYPES,
"wedge_index must be in 0..WEDGE_TYPES; got {wi}"
);
wedge_reached += 1;
} else {
assert_eq!(r.wedge_index, None);
}
} else {
assert_eq!(r.interintra_mode, None);
assert_eq!(r.wedge_interintra, None);
assert_eq!(r.wedge_index, None);
}
}
assert!(
inner_arm_count > 0,
"biased interintra CDF must produce at least one inner-arm trial in 100"
);
assert!(
wedge_reached > 0,
"biased wedge_interintra CDF must produce at least one wedge_index read in 100 trials"
);
// Witness: the wedge_index CDF row adapted (was sampled at
// least once by the four-symbol path).
assert_ne!(
cdfs.wedge_index[BLOCK_16X16], wedge_idx_before,
"the wedge_index row must have adapted after the four-symbol path fired"
);
}
/// §5.11.28 caller-bug — `mi_size >= BLOCK_SIZES` surfaces
/// `PartitionWalkOutOfRange` via the [`interintra_ctx`] /
/// [`wedge_inter_intra_cdf`] fallback. This only matters if the
/// caller bypasses the upstream guards; the §5.11.23 dispatcher
/// rejects `sub_size >= BLOCK_SIZES` up-front (`PartitionWalkOutOfRange`).
///
/// We exercise the path by passing a sub_size in the gated open
/// band (so the outer gate does NOT trigger early-return) but
/// out-of-range. The outer gate's `sub_size <= BLOCK_32X32` clause
/// closes for any value > BLOCK_32X32, so we'd take the no-bits
/// path — meaning the spec gate naturally precludes the
/// caller-bug path. This test pins the gate-close behaviour for
/// `mi_size == BLOCK_SIZES` (one past the last valid ordinal),
/// matching the §5.11.28 spec's MiSize <= BLOCK_32X32 clause.
#[test]
fn read_interintra_mode_mi_size_past_band_returns_zero() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interintra_mode(&mut dec, &mut cdfs, BLOCK_SIZES, 0, false, true)
.unwrap();
assert_eq!(r.interintra, 0);
assert_eq!(dec.position(), pos_before);
}
/// End-to-end through the §5.11.23 dispatcher: with
/// `enable_interintra_compound = false` the §5.11.28 outer gate is
/// closed and the returned `DecodedInterBlockModeInfo.interintra`
/// carries `interintra = 0` + every Option `None`.
#[test]
fn cascade_read_interintra_mode_disabled_compound_surfaces_zero_through_aggregate() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
/* mi_row = */ 0,
/* mi_col = */ 0,
/* sub_size = */ BLOCK_16X16,
/* skip_mode = */ 0,
/* avail_u = */ false,
/* avail_l = */ false,
/* above_ref_frame = */ [0, -1],
/* left_ref_frame = */ [0, -1],
/* above_intra = */ true,
/* left_intra = */ true,
/* above_single = */ true,
/* left_single = */ true,
/* skip_mode_frame = */ [0, -1],
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_data = */ 0,
/* seg_skip_active = */ false,
/* seg_globalmv_active = */ false,
/* reference_select = */ false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
)
.expect("§5.11.28 closed-gate ⇒ dispatcher returns Ok");
assert_eq!(info.interintra.interintra, 0);
assert_eq!(info.interintra.interintra_mode, None);
assert_eq!(info.interintra.wedge_interintra, None);
assert_eq!(info.interintra.wedge_index, None);
// Slot-1 grid stamp untouched on the closed-gate path
// (RefFrame[1] = NONE from §5.11.25 default arm with no
// reference_select).
let r1 = walker.ref_frames()[1];
assert_eq!(
r1, -1,
"closed gate ⇒ slot-1 grid stamp stays at the §5.11.25 outcome"
);
}
/// End-to-end through the §5.11.23 dispatcher: with
/// `enable_interintra_compound = true`, the §5.11.28 reader is
/// reachable from the dispatcher and the `interintra` CDF row
/// adapts as a witness that the S() call fired. The aggregate
/// `DecodedInterBlockModeInfo.interintra.interintra` flag is in
/// `{0, 1}` and the `Option` companion fields are consistent
/// (`None` when `interintra == 0`, `Some(_)` for
/// `interintra_mode` and `wedge_interintra` when `interintra == 1`).
///
/// Whenever the inner arm fires, the dispatcher stamps the
/// walker's slot-1 grid over the BLOCK_16X16 footprint to
/// `INTRA_FRAME = 0`. We assert this conditional grid effect
/// rather than insisting the inner arm fire on any single trial.
#[test]
fn cascade_read_interintra_mode_enabled_compound_witnesses_adaptation() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let ctx = interintra_ctx(BLOCK_16X16).unwrap();
let inter_intra_before = cdfs.inter_intra[ctx];
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, false).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
/* mi_row = */ 0,
/* mi_col = */ 0,
/* sub_size = */ BLOCK_16X16,
/* skip_mode = */ 0,
/* avail_u = */ false,
/* avail_l = */ false,
/* above_ref_frame = */ [0, -1],
/* left_ref_frame = */ [0, -1],
/* above_intra = */ true,
/* left_intra = */ true,
/* above_single = */ true,
/* left_single = */ true,
/* skip_mode_frame = */ [0, -1],
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_data = */ 0,
/* seg_skip_active = */ false,
/* seg_globalmv_active = */ false,
/* reference_select = */ false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ true,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
)
.expect("§5.11.28 enabled ⇒ dispatcher returns Ok");
// The reader fired (witness via inter_intra CDF adaptation).
assert_ne!(
cdfs.inter_intra[ctx], inter_intra_before,
"the §5.11.28 reader's `interintra` S() must have adapted the inter_intra CDF row"
);
// Aggregate consistency: per-symbol fields agree with the
// gate-result flag.
assert!(matches!(info.interintra.interintra, 0 | 1));
if info.interintra.interintra == 0 {
assert_eq!(info.interintra.interintra_mode, None);
assert_eq!(info.interintra.wedge_interintra, None);
assert_eq!(info.interintra.wedge_index, None);
} else {
assert!(info.interintra.interintra_mode.is_some());
assert!(info.interintra.wedge_interintra.is_some());
}
// Conditional grid stamp: if the inner arm fired, every cell
// of the BLOCK_16X16 (bh4=bw4=4) footprint at (0, 0) carries
// slot-1 = INTRA_FRAME = 0 per the §5.11.28 spec override.
if info.interintra.interintra == 1 {
let mi_cols = walker.mi_cols() as usize;
for r in 0..4 {
for c in 0..4 {
let cell = (r * mi_cols + c) * 2;
assert_eq!(
walker.ref_frames()[cell + 1],
0,
"§5.11.28 inner-arm overrides slot-1 to INTRA_FRAME at ({r}, {c})"
);
}
}
}
// The aggregate `ref_frame[1]` retains the §5.11.25 outcome —
// the §5.11.28 override is recorded only in the walker grid,
// not in the per-block aggregate snapshot.
let _ = info.ref_frame;
}
// ===================================================================
// §5.11.29 read_compound_type tests (round 177)
// ===================================================================
/// §3 `Wedge_Bits[ BLOCK_SIZES ]` is the per-`MiSize` count of
/// bits the §5.11.29 inner branch uses to decide whether
/// `compound_type` is read via the S() (`n > 0` ⇒ S()) or forced
/// to `COMPOUND_DIFFWTD` (`n == 0`). The transcription should
/// match the spec table verbatim and be non-zero exactly on the
/// rows where the §9.4 `Default_Wedge_Index_Cdf` carries a
/// non-placeholder distribution (`BLOCK_8X8..=BLOCK_32X32` and
/// `BLOCK_8X16` / `BLOCK_16X8`).
#[test]
fn wedge_bits_matches_spec_table() {
let expected: [u8; BLOCK_SIZES] = [
0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0,
];
assert_eq!(WEDGE_BITS, expected, "WEDGE_BITS must match §3 table");
// wedge_bits() helper agrees with the table over the valid
// range and returns 0 for the caller-bug out-of-range slot.
for (i, &e) in expected.iter().enumerate() {
assert_eq!(wedge_bits(i), e, "wedge_bits({i})");
}
assert_eq!(wedge_bits(BLOCK_SIZES), 0);
assert_eq!(wedge_bits(BLOCK_SIZES + 7), 0);
}
/// §6.10.24 compound-type ordinals + §6.10.24 mask-type ordinals
/// align with the spec enumeration.
#[test]
fn compound_type_and_mask_type_ordinals_match_spec() {
assert_eq!(COMPOUND_WEDGE, 0);
assert_eq!(COMPOUND_DIFFWTD, 1);
assert_eq!(COMPOUND_AVERAGE, 2);
assert_eq!(COMPOUND_INTRA, 3);
assert_eq!(COMPOUND_DISTANCE, 4);
assert_eq!(UNIFORM_45, 0);
assert_eq!(UNIFORM_45_INV, 1);
// The §8.3.2 `compound_type` S() codes only the first two —
// `COMPOUND_TYPES = 2`.
assert_eq!(COMPOUND_TYPES, 2);
}
/// §8.3.2 `comp_group_idx_ctx` exhaustive small-input audit. The
/// spec body adds `0..=2` from each of {above, left} and clamps
/// the sum at 5; verify every reachable input triple lands in
/// `0..COMP_GROUP_IDX_CONTEXTS` and matches the literal formula.
#[test]
fn comp_group_idx_ctx_matches_spec_formula() {
// Closed-both arm: 0.
assert_eq!(
comp_group_idx_ctx(false, true, 0, false, false, true, 0, false),
0
);
// Above-only `!AboveSingle` arm: ctx += CompGroupIdxs (0 or 1).
assert_eq!(
comp_group_idx_ctx(true, false, 0, false, false, true, 0, false),
0
);
assert_eq!(
comp_group_idx_ctx(true, false, 1, false, false, true, 0, false),
1
);
// Above-only `AboveSingle && ALTREF` arm: ctx += 3.
assert_eq!(
comp_group_idx_ctx(true, true, 0, true, false, true, 0, false),
3
);
// Above-only `AboveSingle && !ALTREF` arm: ctx unchanged.
assert_eq!(
comp_group_idx_ctx(true, true, 0, false, false, true, 0, false),
0
);
// Both ALTREF singles: ctx = 3 + 3 = 6, clamped to 5.
assert_eq!(
comp_group_idx_ctx(true, true, 0, true, true, true, 0, true),
5
);
// Above ALTREF + left compound w/ CompGroupIdx=1: ctx = 3 + 1
// = 4 (below the clamp).
assert_eq!(
comp_group_idx_ctx(true, true, 0, true, true, false, 1, false),
4
);
// Both compound + both CompGroupIdx=1: ctx = 1 + 1 = 2.
assert_eq!(
comp_group_idx_ctx(true, false, 1, false, true, false, 1, false),
2
);
}
/// §8.3.2 `compound_idx_ctx` exhaustive small-input audit.
/// `dist_equal ? 3 : 0` seed; +CompoundIdxs on `!Single`; +1 on
/// `Single && ALTREF`. Maximum reachable is 3 + 1 + 1 = 5; the
/// spec body does not clamp (the cap is structural). All outputs
/// must be in `0..COMPOUND_IDX_CONTEXTS`.
#[test]
fn compound_idx_ctx_matches_spec_formula() {
// Closed-both arm, dist_equal=false ⇒ 0.
assert_eq!(
compound_idx_ctx(false, false, true, 0, false, false, true, 0, false),
0
);
// Closed-both, dist_equal=true ⇒ 3.
assert_eq!(
compound_idx_ctx(true, false, true, 0, false, false, true, 0, false),
3
);
// Above-only `!AboveSingle` arm: ctx = 0 + CompoundIdxs.
assert_eq!(
compound_idx_ctx(false, true, false, 1, false, false, true, 0, false),
1
);
assert_eq!(
compound_idx_ctx(false, true, false, 0, false, false, true, 0, false),
0
);
// Above-only `AboveSingle && ALTREF` arm: ctx += 1.
assert_eq!(
compound_idx_ctx(false, true, true, 0, true, false, true, 0, false),
1
);
// Both compound w/ CompoundIdxs=1 + dist_equal: ctx = 3 + 1 +
// 1 = 5.
assert_eq!(
compound_idx_ctx(true, true, false, 1, false, true, false, 1, false),
5
);
// Every output stays in 0..COMPOUND_IDX_CONTEXTS.
for dist_equal in [false, true] {
for above_single in [false, true] {
for above_ci in [0u8, 1] {
for above_altref in [false, true] {
for left_single in [false, true] {
for left_ci in [0u8, 1] {
for left_altref in [false, true] {
let ctx = compound_idx_ctx(
dist_equal,
true,
above_single,
above_ci,
above_altref,
true,
left_single,
left_ci,
left_altref,
);
assert!(
ctx < COMPOUND_IDX_CONTEXTS,
"compound_idx_ctx out of range: {ctx}"
);
}
}
}
}
}
}
}
}
/// §5.11.29 `skip_mode` short-circuit: returns
/// `compound_type = COMPOUND_AVERAGE`, defaults intact, no bits
/// read.
#[test]
fn read_compound_type_skip_mode_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 1,
/* is_compound = */ true,
0,
0,
/* enable_masked_compound = */ true,
/* enable_jnt_comp = */ true,
/* dist_equal = */ false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.comp_group_idx, 0);
assert_eq!(r.compound_idx, 1);
assert_eq!(r.compound_type, COMPOUND_AVERAGE);
assert_eq!(r.wedge_index, None);
assert_eq!(r.wedge_sign, None);
assert_eq!(r.mask_type, None);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.29 `!isCompound && interintra == 0` else-arm:
/// `compound_type = COMPOUND_AVERAGE`, no bits read.
#[test]
fn read_compound_type_single_pred_no_interintra_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* is_compound = */ false,
/* interintra = */ 0,
/* wedge_interintra = */ 0,
true,
true,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.compound_type, COMPOUND_AVERAGE);
assert_eq!(r.comp_group_idx, 0);
assert_eq!(r.compound_idx, 1);
assert!(r.wedge_index.is_none() && r.wedge_sign.is_none() && r.mask_type.is_none());
assert_eq!(dec.position(), pos_before);
}
/// §5.11.29 `!isCompound && interintra == 1 && wedge_interintra == 0`
/// else-arm: `compound_type = COMPOUND_INTRA`, no bits read.
#[test]
fn read_compound_type_single_pred_interintra_nowedge_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
false,
/* interintra = */ 1,
/* wedge_interintra = */ 0,
true,
true,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.compound_type, COMPOUND_INTRA);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.29 `!isCompound && interintra == 1 && wedge_interintra == 1`
/// else-arm: `compound_type = COMPOUND_WEDGE`, no bits read here
/// (the wedge sub-branch S()/L(1) is the §5.11.28 wedge_index, not
/// this §5.11.29 sub-branch).
#[test]
fn read_compound_type_single_pred_interintra_wedge_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
false,
/* interintra = */ 1,
/* wedge_interintra = */ 1,
true,
true,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.compound_type, COMPOUND_WEDGE);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.29 `isCompound && !enable_masked_compound &&
/// !enable_jnt_comp` arm: `comp_group_idx` S() skipped (kept at
/// pre-set 0), `compound_idx` S() skipped (kept at pre-set 1),
/// `compound_type = COMPOUND_AVERAGE`, no bits read.
#[test]
fn read_compound_type_compound_no_masked_no_jnt_no_bits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
/* is_compound = */ true,
0,
0,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.compound_type, COMPOUND_AVERAGE);
assert_eq!(r.comp_group_idx, 0);
assert_eq!(r.compound_idx, 1);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.29 `isCompound && enable_masked_compound &&
/// comp_group_idx S() ⇒ 0 && !enable_jnt_comp` path: one S()
/// fires, `compound_type = COMPOUND_AVERAGE`, the
/// `TileCompGroupIdxCdf` row adapts.
#[test]
fn read_compound_type_compound_masked_group0_no_jnt_one_symbol() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// §8.3.2 `comp_group_idx_ctx` with all-zero neighbours = 0.
let ctx = 0usize;
// Force comp_group_idx S() ⇒ 0.
cdfs.comp_group_idx[ctx] = force_binary_cdf(0);
let before = cdfs.comp_group_idx[ctx];
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let pos_before = dec.position();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
0,
0,
/* enable_masked_compound = */ true,
/* enable_jnt_comp = */ false,
false,
/* avail_u = */ false,
/* avail_l = */ false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.comp_group_idx, 0);
assert_eq!(r.compound_idx, 1);
assert_eq!(r.compound_type, COMPOUND_AVERAGE);
assert_ne!(
cdfs.comp_group_idx[ctx], before,
"TileCompGroupIdxCdf row must have adapted via S()"
);
assert!(
dec.position() > pos_before,
"comp_group_idx S() must consume at least one bit"
);
}
/// §5.11.29 `isCompound && enable_masked_compound &&
/// comp_group_idx S() ⇒ 1 && n > 0` path: forces the
/// `compound_type` S() to fire and exercises the
/// `COMPOUND_WEDGE` sub-branch (S() for `wedge_index` + L(1) for
/// `wedge_sign`). `BLOCK_16X16` has `n = Wedge_Bits[6] = 4`.
#[test]
fn read_compound_type_compound_masked_group1_wedge_path() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let ctx = 0usize;
cdfs.comp_group_idx[ctx] = force_binary_cdf(1);
cdfs.compound_type[BLOCK_16X16] = force_binary_cdf(COMPOUND_WEDGE);
let cg_before = cdfs.comp_group_idx[ctx];
let ct_before = cdfs.compound_type[BLOCK_16X16];
let wi_before = cdfs.wedge_index[BLOCK_16X16];
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, false).unwrap();
let pos_before = dec.position();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
0,
0,
true,
false,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.comp_group_idx, 1);
assert_eq!(r.compound_type, COMPOUND_WEDGE);
assert!(
r.wedge_index.is_some(),
"wedge_index Some after COMPOUND_WEDGE"
);
assert!(
r.wedge_sign.is_some(),
"wedge_sign Some after COMPOUND_WEDGE"
);
assert_eq!(r.mask_type, None);
let wi = r.wedge_index.unwrap();
assert!((wi as usize) < WEDGE_TYPES);
// CDFs adapted (S() reads sampled them).
assert_ne!(cdfs.comp_group_idx[ctx], cg_before);
assert_ne!(cdfs.compound_type[BLOCK_16X16], ct_before);
assert_ne!(cdfs.wedge_index[BLOCK_16X16], wi_before);
// At least three bits read (two S() + one L(1) + the wedge_index S()).
assert!(dec.position() > pos_before + 1);
}
/// §5.11.29 `isCompound && enable_masked_compound &&
/// comp_group_idx S() ⇒ 1 && n > 0 && compound_type S() ⇒
/// COMPOUND_DIFFWTD` path: exercises the diffwtd sub-branch
/// (L(1) `mask_type`). The wedge sub-branch must stay silent.
#[test]
fn read_compound_type_compound_masked_group1_diffwtd_path() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let ctx = 0usize;
cdfs.comp_group_idx[ctx] = force_binary_cdf(1);
cdfs.compound_type[BLOCK_16X16] = force_binary_cdf(COMPOUND_DIFFWTD);
let wi_before = cdfs.wedge_index[BLOCK_16X16];
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, false).unwrap();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
0,
0,
true,
false,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.comp_group_idx, 1);
assert_eq!(r.compound_type, COMPOUND_DIFFWTD);
assert!(
r.mask_type.is_some(),
"mask_type Some after COMPOUND_DIFFWTD"
);
assert_eq!(r.wedge_index, None);
assert_eq!(r.wedge_sign, None);
let mt = r.mask_type.unwrap();
assert!(mt == UNIFORM_45 || mt == UNIFORM_45_INV);
// The wedge_index CDF stayed untouched (sub-branch skipped).
assert_eq!(cdfs.wedge_index[BLOCK_16X16], wi_before);
}
/// §5.11.29 `isCompound && enable_masked_compound &&
/// comp_group_idx S() ⇒ 1 && n == 0` path: `BLOCK_4X4`
/// (`Wedge_Bits[0] = 0`) forces `compound_type = COMPOUND_DIFFWTD`
/// without a `compound_type` S() read. The diffwtd L(1) still
/// fires.
#[test]
fn read_compound_type_compound_masked_group1_n_zero_forces_diffwtd() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let ctx = 0usize;
cdfs.comp_group_idx[ctx] = force_binary_cdf(1);
let ct_before = cdfs.compound_type[BLOCK_4X4];
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, false).unwrap();
let r = walker
.read_compound_type(
&mut dec, &mut cdfs, 0, 0, /* sub_size = */ BLOCK_4X4, 0, true, 0, 0, true,
false, false, false, false, true, true, false, false,
)
.unwrap();
assert_eq!(r.compound_type, COMPOUND_DIFFWTD);
assert!(r.mask_type.is_some());
// The compound_type CDF row stayed untouched (S() skipped on
// the `n == 0` branch).
assert_eq!(
cdfs.compound_type[BLOCK_4X4], ct_before,
"n == 0 ⇒ compound_type S() skipped, default CDF unchanged"
);
}
/// §5.11.29 `isCompound && comp_group_idx == 0 && enable_jnt_comp`
/// path: `compound_idx` S() fires and adapts the CDF row;
/// `compound_type = compound_idx ? COMPOUND_AVERAGE :
/// COMPOUND_DISTANCE`. With a forced `compound_idx S() ⇒ 1` the
/// type lands on `COMPOUND_AVERAGE`.
#[test]
fn read_compound_type_compound_group0_jnt_compound_idx_one() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// §8.3.2 `compound_idx_ctx` with !dist_equal + closed neighbours = 0.
let ctx = 0usize;
cdfs.compound_idx[ctx] = force_binary_cdf(1);
let ci_before = cdfs.compound_idx[ctx];
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
0,
0,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ true,
/* dist_equal = */ false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.comp_group_idx, 0);
assert_eq!(r.compound_idx, 1);
assert_eq!(r.compound_type, COMPOUND_AVERAGE);
assert_ne!(cdfs.compound_idx[ctx], ci_before);
}
/// §5.11.29 `compound_idx S() ⇒ 0` outcome: `compound_type =
/// COMPOUND_DISTANCE`.
#[test]
fn read_compound_type_compound_group0_jnt_compound_idx_zero_yields_distance() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let ctx = 0usize;
cdfs.compound_idx[ctx] = force_binary_cdf(0);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
0,
0,
false,
true,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.compound_idx, 0);
assert_eq!(r.compound_type, COMPOUND_DISTANCE);
}
/// §5.11.29 dist_equal seed: when `dist_equal == true`, the
/// `compound_idx_ctx` lands in `3..=5`. Driving the
/// `compound_idx` S() against a forced ctx-3 distribution shows
/// it adapts.
#[test]
fn read_compound_type_dist_equal_uses_ctx3() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// dist_equal seeds ctx = 3 (no neighbour contributions on
// closed-both arm).
let ctx = 3usize;
cdfs.compound_idx[ctx] = force_binary_cdf(1);
let before = cdfs.compound_idx[ctx];
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
0,
true,
0,
0,
false,
true,
/* dist_equal = */ true,
false,
false,
true,
true,
false,
false,
)
.unwrap();
assert_eq!(r.compound_type, COMPOUND_AVERAGE);
assert_ne!(
cdfs.compound_idx[ctx], before,
"ctx 3 row must have adapted"
);
}
/// §5.11.29 caller-bug: `mi_size >= BLOCK_SIZES` on the wedge
/// sub-branch surfaces `PartitionWalkOutOfRange` via
/// `wedge_index_cdf` / `compound_type_cdf` fallback. The
/// `comp_group_idx == 1 && n == 0 ⇒ COMPOUND_DIFFWTD` shortcut
/// bypasses `compound_type_cdf`, but the spec-correct caller
/// never exercises `mi_size >= BLOCK_SIZES`; we pin the error
/// surface for the defensive caller-bug fallback.
#[test]
fn read_compound_type_mi_size_out_of_range_surfaces_error() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force the comp_group_idx S() → 1 + the `compound_type` S()
// path: with `mi_size = BLOCK_SIZES` (out of range),
// `wedge_bits` returns 0 ⇒ short-circuit COMPOUND_DIFFWTD ⇒
// L(1) mask_type. So out-of-range is actually safe under the
// r177 helper. Force `compound_type S()` by passing a
// `Wedge_Bits == 0` row but with comp_group_idx = 1 forced;
// that path doesn't hit `compound_type_cdf` either. Pin the
// observation: the call returns Ok with COMPOUND_DIFFWTD.
cdfs.comp_group_idx[0] = force_binary_cdf(1);
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, false).unwrap();
let r = walker
.read_compound_type(
&mut dec,
&mut cdfs,
0,
0,
/* sub_size = */ BLOCK_SIZES,
0,
true,
0,
0,
true,
false,
false,
false,
false,
true,
true,
false,
false,
)
.unwrap();
// `wedge_bits(BLOCK_SIZES)` returns 0 ⇒ COMPOUND_DIFFWTD
// shortcut; mask_type fires; the wedge sub-branch is skipped
// (which would otherwise hit `wedge_index_cdf` and return
// None ⇒ PartitionWalkOutOfRange).
assert_eq!(r.compound_type, COMPOUND_DIFFWTD);
}
/// End-to-end through the §5.11.23 dispatcher: with
/// `is_compound = false` (single-pred path forced via
/// `seg_skip_active = true ⇒ GLOBALMV`), the §5.11.29 readout
/// surfaces on the aggregate. The default `interintra = 0`
/// (closed §5.11.28 gate) yields `compound_type = COMPOUND_AVERAGE`.
#[test]
fn cascade_read_compound_type_single_pred_surfaces_average() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
[0, -1],
false,
0,
false,
false,
false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
false,
false,
[false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ 0,
/* enable_dual_filter = */ false,
&mfmvs,
)
.expect("dispatcher Ok");
assert_eq!(info.compound_type.compound_type, COMPOUND_AVERAGE);
assert_eq!(info.compound_type.comp_group_idx, 0);
assert_eq!(info.compound_type.compound_idx, 1);
assert!(info.compound_type.wedge_index.is_none());
assert!(info.compound_type.wedge_sign.is_none());
assert!(info.compound_type.mask_type.is_none());
// Walker grid was stamped with the defaults over the bh4 * bw4
// footprint (BLOCK_16X16: 4x4 cells).
let mi_cols = walker.mi_cols() as usize;
for r in 0..4 {
for c in 0..4 {
let cell = r * mi_cols + c;
assert_eq!(walker.comp_group_idxs()[cell], 0);
assert_eq!(walker.compound_idxs()[cell], 1);
}
}
}
// ===================================================================
// §5.11.x read_interpolation_filter tests (round 178)
// ===================================================================
/// §6.8.9 named-ordinal alignment: the four block-level filter
/// ordinals and the frame-only `SWITCHABLE` sentinel pin their
/// av1-spec p.161 values. The §5.11.x reader and the §8.3.2 ctx
/// walk both encode these as `u8`; the `SWITCHABLE` sentinel can
/// never appear as an `interp_filter[ dir ]` value (only as the
/// frame-header `interpolation_filter`).
#[test]
fn interpolation_filter_ordinals_match_spec() {
assert_eq!(EIGHTTAP, 0);
assert_eq!(EIGHTTAP_SMOOTH, 1);
assert_eq!(EIGHTTAP_SHARP, 2);
assert_eq!(BILINEAR, 3);
assert_eq!(SWITCHABLE, 4);
// The §3 `INTERP_FILTERS = 3` constant — the block-level
// symbol space — covers `EIGHTTAP..=EIGHTTAP_SHARP` only.
assert!((EIGHTTAP_SHARP as usize) < INTERP_FILTERS);
assert_eq!(BILINEAR as usize, INTERP_FILTERS);
}
/// §5.11.x non-switchable arm: when `interpolation_filter !=
/// SWITCHABLE` the reader forces both per-direction slots to the
/// frame-header value and reads no bits. The walker grid is not
/// stamped here (the §5.11.23 dispatcher does that after the call),
/// so this test only inspects the returned aggregate and verifies
/// the bitstream position is unchanged.
#[test]
fn read_interpolation_filter_non_switchable_forces_both_slots() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* skip_mode = */ 0,
/* is_compound = */ false,
/* ref_frame = */ [1, -1],
/* y_mode = */ MODE_NEARESTMV,
/* motion_mode = */ MOTION_MODE_SIMPLE,
/* interpolation_filter = */ EIGHTTAP_SHARP,
/* enable_dual_filter = */ true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
)
.unwrap();
assert_eq!(r.interp_filter, [EIGHTTAP_SHARP, EIGHTTAP_SHARP]);
assert_eq!(r.read_from_bitstream, [false, false]);
assert_eq!(
dec.position(),
pos_before,
"non-SWITCHABLE arm reads zero bits"
);
}
/// §5.11.x `needs_interp_filter == 0` arm (skip_mode forces it):
/// even though `interpolation_filter == SWITCHABLE`, the
/// per-direction fallback fires and forces `EIGHTTAP` with no bits
/// read. Both slots end up at EIGHTTAP via the `!enable_dual_filter`
/// mirror.
#[test]
fn read_interpolation_filter_switchable_skip_mode_falls_back_to_eighttap() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* skip_mode = */ 1,
/* is_compound = */ false,
/* ref_frame = */ [1, -1],
/* y_mode = */ MODE_NEARESTMV,
/* motion_mode = */ MOTION_MODE_SIMPLE,
/* interpolation_filter = */ SWITCHABLE,
/* enable_dual_filter = */ false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
)
.unwrap();
assert_eq!(r.interp_filter, [EIGHTTAP, EIGHTTAP]);
assert_eq!(r.read_from_bitstream, [false, false]);
assert_eq!(
dec.position(),
pos_before,
"skip_mode short-circuits needs_interp_filter to 0 — no S() bits"
);
}
/// §5.11.x `needs_interp_filter == 0` arm via the LOCALWARP path:
/// `motion_mode == WARPED_CAUSAL` also closes the gate per the
/// av1-spec p.75 first clause. EIGHTTAP fallback with zero bits
/// read.
#[test]
fn read_interpolation_filter_switchable_localwarp_falls_back_to_eighttap() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let pos_before = dec.position();
let r = walker
.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* is_compound = */ false,
/* ref_frame = */ [1, -1],
/* y_mode = */ MODE_NEARESTMV,
/* motion_mode = */ MOTION_MODE_WARPED_CAUSAL,
/* interpolation_filter = */ SWITCHABLE,
/* enable_dual_filter = */ true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
)
.unwrap();
assert_eq!(r.interp_filter, [EIGHTTAP, EIGHTTAP]);
assert_eq!(r.read_from_bitstream, [false, false]);
assert_eq!(dec.position(), pos_before);
}
/// §5.11.x `needs_interp_filter == 0` arm via large GLOBALMV with
/// non-TRANSLATION gm_type: the av1-spec p.75 third clause closes
/// the gate (because the GLOBALMV path doesn't use translational
/// motion, the filter is implied). Fallback to EIGHTTAP.
#[test]
fn read_interpolation_filter_switchable_globalmv_non_translation_falls_back() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0xFFu8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let mut gm_type = [GM_TYPE_IDENTITY; 8];
gm_type[1] = GM_TYPE_TRANSLATION + 1; // ROTZOOM or AFFINE
let pos_before = dec.position();
let r = walker
.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* is_compound = */ false,
/* ref_frame = */ [1, -1],
/* y_mode = */ MODE_GLOBALMV,
/* motion_mode = */ MOTION_MODE_SIMPLE,
/* interpolation_filter = */ SWITCHABLE,
/* enable_dual_filter = */ true,
/* gm_type = */ gm_type,
)
.unwrap();
// GLOBALMV + large block + non-TRANSLATION gm_type ⇒
// needs_interp_filter returns false ⇒ EIGHTTAP fallback.
assert_eq!(r.interp_filter, [EIGHTTAP, EIGHTTAP]);
assert_eq!(r.read_from_bitstream, [false, false]);
assert_eq!(dec.position(), pos_before);
}
/// Build a `TileInterpFilterCdf` row that forces the §8.2.6 symbol
/// decode to yield `target_sym ∈ 0..INTERP_FILTERS`. Convention:
/// * `cdf[i] = 0` for `i < target_sym` (max probability ⇒
/// `cur` stays near `range`, so `value < cur` and the loop
/// continues);
/// * `cdf[target_sym..INTERP_FILTERS] = 1 << 15` (zero
/// probability for these symbols ⇒ `cur` collapses to ~0 and
/// the loop breaks at `i = target_sym`);
/// * `cdf[INTERP_FILTERS] = 0` (counter slot — §8.3 ignores).
///
/// Works with the symbol-decoder's initial `value = 0x7FFF` (the
/// `init_symbol` with all-`0x00` input bytes).
fn force_interp_filter_row(target_sym: u8) -> [u16; INTERP_FILTERS + 1] {
let mut out = [0u16; INTERP_FILTERS + 1];
for (i, slot) in out.iter_mut().enumerate().take(INTERP_FILTERS) {
*slot = if (i as u8) < target_sym { 0 } else { 1 << 15 };
}
out
}
/// §5.11.x `needs_interp_filter == 1` arm with `enable_dual_filter
/// == false`: ONE S() fires and slot 1 mirrors slot 0 (no second
/// read). With a rigged CDF forcing symbol `EIGHTTAP_SMOOTH`, both
/// slots end up at `EIGHTTAP_SMOOTH` with
/// `read_from_bitstream == [true, false]`.
#[test]
fn read_interpolation_filter_switchable_single_dir_reads_one_symbol() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Rig every interp_filter CDF row to force symbol 1
// (EIGHTTAP_SMOOTH).
for row in cdfs.interp_filter.iter_mut() {
*row = force_interp_filter_row(EIGHTTAP_SMOOTH);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let range_before = dec.symbol_range();
let r = walker
.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* is_compound = */ false,
/* ref_frame = */ [1, -1],
/* y_mode = */ MODE_NEARESTMV,
/* motion_mode = */ MOTION_MODE_SIMPLE,
/* interpolation_filter = */ SWITCHABLE,
/* enable_dual_filter = */ false,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
)
.unwrap();
assert_eq!(r.interp_filter, [EIGHTTAP_SMOOTH, EIGHTTAP_SMOOTH]);
assert_eq!(
r.read_from_bitstream,
[true, false],
"single-dir path reads slot 0 only; slot 1 mirrors"
);
// The symbol decoder's `range` is invariant after a no-bit
// renormalisation, but the symbol read still mutated decoder
// state through `value`; we observe progress via the `read`
// flags above. (Reads of high-probability tails consume zero
// bits at byte-position granularity but still advance §8.2.6
// state — that's why the spec separates `value` / `range` from
// the §8.2.2 byte-stream position.)
let _ = range_before;
}
/// §5.11.x `needs_interp_filter == 1` arm with `enable_dual_filter
/// == true`: TWO S()s fire (one per dir). With the CDF rigged to
/// force symbol `EIGHTTAP_SHARP` both reads land on the same
/// symbol; `read_from_bitstream == [true, true]`.
#[test]
fn read_interpolation_filter_switchable_dual_dir_reads_two_symbols() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
for row in cdfs.interp_filter.iter_mut() {
*row = force_interp_filter_row(EIGHTTAP_SHARP);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r = walker
.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
/* is_compound = */ false,
/* ref_frame = */ [1, -1],
/* y_mode = */ MODE_NEARESTMV,
/* motion_mode = */ MOTION_MODE_SIMPLE,
/* interpolation_filter = */ SWITCHABLE,
/* enable_dual_filter = */ true,
/* gm_type = */ [GM_TYPE_IDENTITY; 8],
)
.unwrap();
assert_eq!(r.interp_filter, [EIGHTTAP_SHARP, EIGHTTAP_SHARP]);
// Both per-direction reads fired against the rigged CDF.
assert_eq!(r.read_from_bitstream, [true, true]);
}
/// §5.11.x caller-bug guards: out-of-range `sub_size`,
/// `interpolation_filter`, or `ref_frame` surface
/// `PartitionWalkOutOfRange` with zero bits consumed.
#[test]
fn read_interpolation_filter_rejects_caller_bugs() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// Out-of-range sub_size.
let r = walker.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
0,
false,
[1, -1],
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
EIGHTTAP,
false,
[GM_TYPE_IDENTITY; 8],
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
// Out-of-range interpolation_filter (5 > SWITCHABLE).
let r = walker.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
0,
false,
[1, -1],
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
/* interpolation_filter = */ 5,
false,
[GM_TYPE_IDENTITY; 8],
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
// Out-of-range ref_frame[0] (= 8, ALTREF_FRAME + 1).
let r = walker.read_interpolation_filter(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
0,
false,
[8, -1],
MODE_NEARESTMV,
MOTION_MODE_SIMPLE,
EIGHTTAP,
false,
[GM_TYPE_IDENTITY; 8],
);
assert_eq!(r, Err(crate::Error::PartitionWalkOutOfRange));
}
/// End-to-end §5.11.23 cascade: with `interpolation_filter !=
/// SWITCHABLE`, the §5.11.x leaf forces both slots to the
/// frame-header value over the full block footprint. The walker
/// grid is stamped accordingly; the `DecodedInterBlockModeInfo`
/// surfaces the readout on its new `interp_filter` field.
#[test]
fn cascade_read_interpolation_filter_non_switchable_stamps_grid() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 0,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
/* skip_mode_frame = */ [0, -1],
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_data = */ 0,
/* seg_skip_active = */ false,
/* seg_globalmv_active = */ false,
/* reference_select = */ false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ EIGHTTAP_SMOOTH,
/* enable_dual_filter = */ true,
&mfmvs,
)
.expect("cascade Ok");
assert_eq!(
info.interp_filter.interp_filter,
[EIGHTTAP_SMOOTH, EIGHTTAP_SMOOTH]
);
assert_eq!(info.interp_filter.read_from_bitstream, [false, false]);
// Walker grid stamped over the BLOCK_16X16 footprint (4x4 cells).
let mi_cols = walker.mi_cols() as usize;
for r in 0..4 {
for c in 0..4 {
let cell = r * mi_cols + c;
assert_eq!(walker.interp_filters()[cell * 2], EIGHTTAP_SMOOTH);
assert_eq!(walker.interp_filters()[cell * 2 + 1], EIGHTTAP_SMOOTH);
}
}
}
/// End-to-end §5.11.23 cascade: with `interpolation_filter ==
/// SWITCHABLE` and skip_mode forced ⇒ `needs_interp_filter` returns
/// 0 ⇒ EIGHTTAP fallback. The cascade reaches the §5.11.x leaf
/// cleanly, the readout reports `EIGHTTAP` for both slots with
/// `read_from_bitstream == [false, false]`, and the walker's
/// interp_filters grid is stamped EIGHTTAP over the block footprint.
#[test]
fn cascade_read_interpolation_filter_switchable_skip_mode_eighttap_stamp() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 32];
let mut dec = SymbolDecoder::init_symbol(&bytes, 32, true).unwrap();
let mfmvs = MotionFieldMvs::new_invalid(walker.mi_rows(), walker.mi_cols());
let info = walker
.decode_inter_block_mode_info(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_16X16,
/* skip_mode = */ 1,
false,
false,
[0, -1],
[0, -1],
true,
true,
true,
true,
/* skip_mode_frame = */ [1, 7],
/* seg_ref_frame_active = */ false,
/* seg_ref_frame_data = */ 0,
/* seg_skip_active = */ false,
/* seg_globalmv_active = */ false,
/* reference_select = */ false,
[GM_TYPE_IDENTITY; 8],
identity_gm_params(),
[0; 8],
false,
false,
false,
/* is_motion_mode_switchable = */ false,
/* allow_warped_motion = */ false,
/* is_scaled_per_ref = */ [false; 7],
/* enable_interintra_compound = */ false,
/* enable_masked_compound = */ false,
/* enable_jnt_comp = */ false,
/* order_hints = */ FrameInterOrderHints::IDENTITY,
/* interpolation_filter = */ SWITCHABLE,
/* enable_dual_filter = */ true,
&mfmvs,
)
.expect("cascade Ok");
// skip_mode == 1 ⇒ needs_interp_filter == 0 ⇒ EIGHTTAP for both
// dirs, no bits read.
assert_eq!(info.interp_filter.interp_filter, [EIGHTTAP, EIGHTTAP]);
assert_eq!(info.interp_filter.read_from_bitstream, [false, false]);
// Walker grid stamped over the BLOCK_16X16 footprint.
let mi_cols = walker.mi_cols() as usize;
for r in 0..4 {
for c in 0..4 {
let cell = r * mi_cols + c;
assert_eq!(walker.interp_filters()[cell * 2], EIGHTTAP);
assert_eq!(walker.interp_filters()[cell * 2 + 1], EIGHTTAP);
}
}
}
// ===================== §5.11.39 `coeffs( )` =====================
/// Test helper: build an `N`-entry CDF row (`N - 1` symbols +
/// counter) that forces a *fresh-state* `read_symbol` to return
/// `target_sym`. Convention: `cdf[i] = 0` for `i < target_sym` (a
/// 0-CDF entry gives `f = 1 << 15`; the §8.2.6 `cur` for that
/// symbol exceeds the decoder's fresh `value = (1 << 15) - 1 -
/// paddedBuf`, keeping `value < cur` → the loop continues past
/// `i`), then `cdf[i] = 1 << 15` for `i >= target_sym` (a `1 <<
/// 15` entry gives `f = 0`, `cur = EC_MIN_PROB * (N - 2 - i)`,
/// which is `<= value` → the loop breaks at the first such `i =
/// target_sym`). The final counter entry stays at `0`.
///
/// **Note**: this rig is only guaranteed on the *first* read in
/// fresh `[0u8; _]`-init decoder state (where `value = (1 << 15) -
/// 1 = 32767`). After one or more reads the decoder's
/// renormalisation may push `range` above `1 << 15`, and the
/// `cur = ((range >> 8) * (f >> EC_PROB_SHIFT)) >> (7 -
/// EC_PROB_SHIFT)` truncation can drop below `value` even for
/// `f = 1 << 15`. The tests using `rig_cdf` for a non-zero
/// `target_sym` therefore arrange the relevant CDF to be the
/// *first* read at that target — and tests that need a non-zero
/// target after several reads use the gate-closed (`all_zero =
/// 1`) short-circuit instead.
fn rig_cdf<const N: usize>(target_sym: usize) -> [u16; N] {
let mut out = [0u16; N];
let last = N - 1;
for (i, slot) in out.iter_mut().enumerate().take(last) {
*slot = if i < target_sym { 0 } else { 1 << 15 };
}
// counter (slot N-1) stays at 0
out
}
/// §5.11.39 line-3 short-circuit: when the `all_zero` S() read returns
/// `1`, the reader returns `CoefficientsReadout {
/// all_zero: true, eob: 0, cul_level: 0, dc_category: 0 }` with no
/// further S() / L(1) reads. Driven by forcing the `txb_skip` CDF to
/// always select symbol 1.
#[test]
fn coefficients_all_zero_short_circuits() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force every txb_skip ctx row to select symbol 1 (all_zero = 1).
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(1);
}
}
let bytes = [0u8; 4];
let mut dec = SymbolDecoder::init_symbol(&bytes, 4, true).unwrap();
// TX_4X4: 16 coefficients, scan is the identity 0..16 for tests.
let scan: Vec<u16> = (0..16u16).collect();
let mut quant = vec![999i32; 16]; // pre-fill to verify zero-fill
let r = walker
.coefficients(
&mut dec,
&mut cdfs,
/* plane = */ 0,
/* is_inter = */ 0,
TX_4X4,
TX_CLASS_2D,
/* txb_skip_ctx = */ 0,
/* dc_sign_ctx = */ 0,
&scan,
&mut quant,
)
.unwrap();
assert!(r.all_zero);
assert_eq!(r.eob, 0);
assert_eq!(r.cul_level, 0);
assert_eq!(r.dc_category, 0);
// The §5.11.39 line 8-9 pre-loop zero-fills Quant[ 0..segEob ];
// segEob = 16 here, so every cell is zeroed.
assert!(quant.iter().all(|&q| q == 0));
}
/// §5.11.39 gate-open smoke test: `all_zero` forced to 0 (cascade
/// fires); the reader runs to completion, producing a non-`all_zero`
/// readout with `eob` in `1..=segEob = 1..=16`. With `[0u8;
/// 8]`-init the §8.2.6 first-symbol selection is sym-0 for every
/// rigged CDF whose `cdf[0] = 1 << 15`. We confirm:
/// * `all_zero == false` (the gate-closed test confirms the
/// positive case).
/// * `eob >= 1` (every gate-open path produces at least one
/// coefficient).
/// * `cul_level <= 63` (the §5.11.39 line-102 clamp).
/// * `dc_category ∈ {0, 1, 2}` (the §5.11.39 line-94
/// three-arm dispatch).
#[test]
fn coefficients_gate_open_smoke_default_cdfs() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force txb_skip to symbol 0 (first read; sym-0 wins from value=32767).
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(0);
}
}
// The remaining CDFs stay at the §9.4 defaults; the §8.2.6
// decoder selects whatever the default skew lands on. The test
// only asserts structural invariants of the readout.
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let scan: Vec<u16> = (0..16u16).collect();
let mut quant = vec![0i32; 16];
let r = walker
.coefficients(
&mut dec,
&mut cdfs,
/* plane = */ 0,
/* is_inter = */ 0,
TX_4X4,
TX_CLASS_2D,
/* txb_skip_ctx = */ 0,
/* dc_sign_ctx = */ 0,
&scan,
&mut quant,
)
.unwrap();
assert!(!r.all_zero, "txb_skip rigged to 0 ⇒ cascade fires");
assert!(
r.eob >= 1 && r.eob <= 16,
"eob ∈ 1..=segEob = 1..=16 for TX_4X4; got {}",
r.eob
);
assert!(
r.cul_level <= 63,
"§5.11.39 line-102 clamp ensures cul_level ≤ 63; got {}",
r.cul_level
);
assert!(
r.dc_category <= 2,
"§5.11.39 line-94 three-arm dispatch yields {{0, 1, 2}}; got {}",
r.dc_category
);
// Decoder advanced past at least the txb_skip + eob_pt_16 reads.
assert!(
dec.position() > 0,
"gate-open cascade consumed at least one symbol bit"
);
}
/// §5.11.39 cdf adaptation smoke test: with `all_zero` forced to 0
/// (a §8.3 adapting CDF), the `txb_skip` row at the consulted
/// `(txSzCtx, ctx)` cell must have its counter slot incremented
/// (the §8.3 update increments `cdf[N]` after every successful
/// decode). This proves the reader is actually invoking
/// `read_symbol` against the right CDF row.
#[test]
fn coefficients_gate_open_adapts_txb_skip_cdf() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force txb_skip to symbol 0 across all rows.
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(0);
}
}
// Snapshot the row that will be consulted (TX_4X4 has txSzCtx=0,
// and the test passes txb_skip_ctx=0).
let counter_before = cdfs.txb_skip[0][0][2];
let bytes = [0u8; 8];
// Critical: use disable_cdf_update = FALSE so the adaptation runs.
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, false).unwrap();
let scan: Vec<u16> = (0..16u16).collect();
let mut quant = vec![0i32; 16];
let _ = walker
.coefficients(
&mut dec,
&mut cdfs,
0,
0,
TX_4X4,
TX_CLASS_2D,
0,
0,
&scan,
&mut quant,
)
.unwrap();
let counter_after = cdfs.txb_skip[0][0][2];
assert!(
counter_after > counter_before,
"§8.3 CDF update increments the txb_skip counter slot after read"
);
}
/// §5.11.39 caller-bug guards: each out-of-range argument returns
/// [`crate::Error::PartitionWalkOutOfRange`] without touching the
/// decoder state.
#[test]
fn coefficients_rejects_out_of_range_arguments() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let scan: Vec<u16> = (0..16u16).collect();
let mut quant = vec![0i32; 16];
// tx_size out of range.
let r = walker.coefficients(
&mut dec,
&mut cdfs,
0,
0,
TX_SIZES_ALL,
TX_CLASS_2D,
0,
0,
&scan,
&mut quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
// tx_class out of range (TX_CLASS_VERT = 2; 3 is illegal).
let r = walker.coefficients(
&mut dec, &mut cdfs, 0, 0, TX_4X4, 3, 0, 0, &scan, &mut quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
// plane out of range (> 2).
let r = walker.coefficients(
&mut dec,
&mut cdfs,
3,
0,
TX_4X4,
TX_CLASS_2D,
0,
0,
&scan,
&mut quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
// is_inter out of range (> 1).
let r = walker.coefficients(
&mut dec,
&mut cdfs,
0,
2,
TX_4X4,
TX_CLASS_2D,
0,
0,
&scan,
&mut quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
// txb_skip_ctx out of range.
let r = walker.coefficients(
&mut dec,
&mut cdfs,
0,
0,
TX_4X4,
TX_CLASS_2D,
TXB_SKIP_CONTEXTS,
0,
&scan,
&mut quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
// dc_sign_ctx out of range.
let r = walker.coefficients(
&mut dec,
&mut cdfs,
0,
0,
TX_4X4,
TX_CLASS_2D,
0,
DC_SIGN_CONTEXTS,
&scan,
&mut quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
// scan too short.
let short_scan: Vec<u16> = (0..4u16).collect();
let r = walker.coefficients(
&mut dec,
&mut cdfs,
0,
0,
TX_4X4,
TX_CLASS_2D,
0,
0,
&short_scan,
&mut quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
// quant too small.
let mut tiny_quant = vec![0i32; 4];
let r = walker.coefficients(
&mut dec,
&mut cdfs,
0,
0,
TX_4X4,
TX_CLASS_2D,
0,
0,
&scan,
&mut tiny_quant,
);
assert!(matches!(r, Err(crate::Error::PartitionWalkOutOfRange)));
}
/// §5.11.39 line-4 `txSzCtx` derivation invariants: for every
/// square `TX_NxN` (TX_4X4..TX_64X64) both `Tx_Size_Sqr` and
/// `Tx_Size_Sqr_Up` return the same ordinal `n`, so the shift
/// `(n + n + 1) >> 1` collapses to `n`. This test runs the reader
/// across all five square ordinals with the gate-closed (`all_zero
/// = 1`) short-circuit to confirm none trip a bounds check —
/// proving the reader's internal `txSzCtx` formula stays in
/// `0..TX_SIZES` across every square tx size.
#[test]
fn coefficients_txszctx_square_tx_sizes_do_not_panic() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 64,
mi_col_start: 0,
mi_col_end: 64,
};
let mut walker = PartitionWalker::new(64, 64, geom).unwrap();
for tx in &[TX_4X4, TX_8X8, TX_16X16, TX_32X32, TX_64X64] {
let mut cdfs = TileCdfContext::new_from_defaults();
// Force txb_skip to symbol 1 (gate closed — quickest path).
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(1);
}
}
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// Scan + quant sized for the largest case (TX_64X64 = 4096).
let scan: Vec<u16> = (0..4096u16).collect();
let mut quant = vec![0i32; 4096];
let r = walker
.coefficients(
&mut dec,
&mut cdfs,
0,
0,
*tx,
TX_CLASS_2D,
0,
0,
&scan,
&mut quant,
)
.unwrap();
assert!(
r.all_zero,
"rigged all_zero=1 ⇒ short-circuit for tx={}",
tx
);
assert_eq!(r.eob, 0);
}
}
/// §5.11.39 line-6 `segEob` derivation cross-check: for `TX_16X64`
/// the spec hard-codes `segEob = 512`, not the
/// `min(1024, tx_w * tx_h) = min(1024, 1024) = 1024` it would
/// otherwise be. The reader's `scan.len() >= segEob` guard fires
/// when the caller passes a scan shorter than 512 — the test
/// passes 511 cells and confirms the bug guard fires.
#[test]
fn coefficients_tx_16x64_uses_512_seg_eob() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
// scan length 511 — just below the 512-cap.
let scan: Vec<u16> = (0..511u16).collect();
let mut quant = vec![0i32; 16 * 64]; // 1024 cells, fits indexing.
let r = walker.coefficients(
&mut dec,
&mut cdfs,
0,
0,
TX_16X64,
TX_CLASS_2D,
0,
0,
&scan,
&mut quant,
);
assert!(
matches!(r, Err(crate::Error::PartitionWalkOutOfRange)),
"segEob = 512 for TX_16X64; scan.len() = 511 ⇒ guard fires"
);
// A 512-cell scan passes the guard.
let scan_ok: Vec<u16> = (0..512u16).collect();
let mut quant_ok = vec![0i32; 16 * 64];
// Force all_zero to short-circuit so we don't depend on EOB tables.
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(1);
}
}
let bytes2 = [0u8; 8];
let mut dec2 = SymbolDecoder::init_symbol(&bytes2, 8, true).unwrap();
let r2 = walker
.coefficients(
&mut dec2,
&mut cdfs,
0,
0,
TX_16X64,
TX_CLASS_2D,
0,
0,
&scan_ok,
&mut quant_ok,
)
.unwrap();
assert!(r2.all_zero);
assert_eq!(r2.eob, 0);
}
// ============================================================
// r181 — §5.11.34 `residual()` outer-dispatch tests.
// ============================================================
/// `get_tx_size( plane=0, ... )` is a pass-through — the §5.11.37
/// spec body line 2 short-circuits luma to the input `txSz`.
#[test]
fn get_tx_size_plane0_is_pass_through() {
for tx in 0..TX_SIZES_ALL {
assert_eq!(
get_tx_size(0, tx, BLOCK_16X16, 0, 0),
Some(tx),
"plane 0 pass-through (tx_size = {tx})",
);
}
}
/// `get_tx_size` chroma path with no subsampling (4:4:4) — chroma
/// residual equals luma residual, then §5.11.37 lookup via
/// `Max_Tx_Size_Rect`.
#[test]
fn get_tx_size_chroma_no_subsampling_matches_max_tx_size_rect() {
// BLOCK_16X16, 4:4:4 — chroma plane_sz = BLOCK_16X16; uvTx =
// MAX_TX_SIZE_RECT[BLOCK_16X16] = TX_16X16. Neither width nor
// height is 64, so returns uvTx = TX_16X16.
assert_eq!(get_tx_size(1, TX_16X16, BLOCK_16X16, 0, 0), Some(TX_16X16));
assert_eq!(get_tx_size(2, TX_16X16, BLOCK_16X16, 0, 0), Some(TX_16X16));
// BLOCK_32X32, 4:4:4 — uvTx = MAX_TX_SIZE_RECT[BLOCK_32X32] = TX_32X32.
assert_eq!(get_tx_size(1, TX_8X8, BLOCK_32X32, 0, 0), Some(TX_32X32));
}
/// `get_tx_size` 4:2:0 chroma path — BLOCK_64X64 → 32×32 chroma
/// residual → uvTx = TX_32X32 (no 64-side clamp fires).
#[test]
fn get_tx_size_chroma_420_block_64x64_is_tx_32x32() {
assert_eq!(get_tx_size(1, TX_64X64, BLOCK_64X64, 1, 1), Some(TX_32X32));
assert_eq!(get_tx_size(2, TX_64X64, BLOCK_64X64, 1, 1), Some(TX_32X32));
}
/// `get_tx_size` 4:2:0 chroma path on BLOCK_128X128 — chroma
/// plane_sz = BLOCK_64X64, uvTx = MAX_TX_SIZE_RECT[BLOCK_64X64] =
/// TX_64X64 → triggers the §5.11.37 64-side clamp arm → TX_32X32.
#[test]
fn get_tx_size_chroma_128x128_420_clamps_to_32x32() {
// 4:2:0 BLOCK_128X128 chroma plane_sz = BLOCK_64X64.
// uvTx = MAX_TX_SIZE_RECT[BLOCK_64X64] = TX_64X64 (w = h = 64).
// 64-clamp arm: w == 64 && h == 64, neither is 16 ⇒ TX_32X32.
let mi_size = BLOCK_64X64 + 3; // BLOCK_128X128
assert_eq!(get_tx_size(1, TX_64X64, mi_size, 1, 1), Some(TX_32X32));
}
/// `get_tx_size` out-of-range guards.
#[test]
fn get_tx_size_caller_bug_guards() {
assert_eq!(get_tx_size(3, TX_4X4, BLOCK_4X4, 0, 0), None);
assert_eq!(get_tx_size(0, TX_SIZES_ALL, BLOCK_4X4, 0, 0), None);
assert_eq!(get_tx_size(0, TX_4X4, BLOCK_SIZES, 0, 0), None);
assert_eq!(get_tx_size(0, TX_4X4, BLOCK_4X4, 2, 0), None);
assert_eq!(get_tx_size(0, TX_4X4, BLOCK_4X4, 0, 2), None);
}
/// `residual()` gate-closed skip arm: `skip == true` short-
/// circuits the per-TU `coefficients()` invocation. The
/// dispatcher returns a [`ResidualReadout`] with the per-TU task
/// enumeration filled in but the `coeffs` vec empty.
#[test]
fn residual_skip_arm_emits_tasks_only_no_coefficient_reads() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let pos_before = dec.position();
let r = walker
.residual(
&mut dec,
&mut cdfs,
/* mi_row = */ 0,
/* mi_col = */ 0,
/* mi_size = */ BLOCK_8X8,
/* has_chroma = */ true,
/* subsampling_x = */ 0,
/* subsampling_y = */ 0,
/* is_inter = */ false,
/* lossless = */ false,
/* skip = */ true,
/* tx_size = */ TX_8X8,
/* use_128x128_superblock = */ false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
let pos_after = dec.position();
assert_eq!(r.width_chunks, 1, "BLOCK_8X8 ⇒ widthChunks = 1");
assert_eq!(r.height_chunks, 1, "BLOCK_8X8 ⇒ heightChunks = 1");
assert_eq!(r.mi_size_chunk, BLOCK_8X8 as u8);
assert_eq!(r.num_planes_visited, 3, "has_chroma ⇒ 3 planes");
// Each plane emits exactly one TU at TX_8X8 (stepX = stepY = 2
// and num4x4W = num4x4H = 2 ⇒ one (y, x) step).
assert_eq!(
r.tasks.len(),
3,
"one TU per plane on the BLOCK_8X8 / TX_8X8 path"
);
assert!(r.coeffs.is_empty(), "skip == true ⇒ no coefficient reads");
assert!(r.skip);
assert_eq!(pos_after, pos_before, "skip arm reads no bits");
// Per-plane task ordering: plane 0, 1, 2.
assert_eq!(r.tasks[0].plane, 0);
assert_eq!(r.tasks[1].plane, 1);
assert_eq!(r.tasks[2].plane, 2);
// Every task on the non-inter / chroma path has
// `from_transform_tree == false`.
for t in &r.tasks {
assert!(!t.from_transform_tree);
}
}
/// §5.11.35 `BlockDecoded[]` per-TU stamp through `residual()`: a
/// BLOCK_8X8 / TX_8X8 luma TU at the SB origin stamps the 2×2
/// (`stepX = stepY = 2`) footprint as decoded. The tail stamp runs
/// on both the skip and non-skip arms (it is after the §5.11.39
/// gate), so the gate-closed skip path still marks the footprint.
#[test]
fn residual_stamps_block_decoded_over_tu_footprint() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
// §5.11.3 reset first (the tile walk runs this before the SB).
walker.clear_block_decoded_flags(0, 0, 16, 3, 0, 0);
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.residual(
&mut dec,
&mut cdfs,
/* mi_row = */ 0,
/* mi_col = */ 0,
/* mi_size = */ BLOCK_8X8,
/* has_chroma = */ false,
/* subsampling_x = */ 0,
/* subsampling_y = */ 0,
/* is_inter = */ false,
/* lossless = */ false,
/* skip = */ true,
/* tx_size = */ TX_8X8,
/* use_128x128_superblock = */ false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
// §5.11.35: TX_8X8 ⇒ stepX = stepY = 2; SB-local anchor (0, 0).
// The 2×2 footprint [0..2][0..2] is now decoded.
for y in 0..2i32 {
for x in 0..2i32 {
assert!(
walker.block_decoded(0, y, x),
"TU footprint [{y}][{x}] must be marked decoded"
);
}
}
// Cells outside the footprint stay 0 (interior reset value).
assert!(
!walker.block_decoded(0, 2, 0),
"cell below the TU footprint stays 0"
);
assert!(
!walker.block_decoded(0, 0, 2),
"cell right of the TU footprint stays 0"
);
// The left/top borders set by §5.11.3 are untouched by the stamp.
assert!(walker.block_decoded(0, -1, 0), "top border preserved");
assert!(walker.block_decoded(0, 0, -1), "left border preserved");
}
/// §5.11.35 stamp at a non-origin superblock-local position: a TU
/// at `(mi_row, mi_col) = (4, 4)` inside a 64×64 SB stamps SB-local
/// `(4, 4)` (the `subBlockMiRow / subBlockMiCol = row & 15` mask
/// keeps the position SB-local).
#[test]
fn residual_stamps_block_decoded_at_sb_local_offset() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
walker.clear_block_decoded_flags(0, 0, 16, 3, 0, 0);
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
walker
.residual(
&mut dec,
&mut cdfs,
/* mi_row = */ 4,
/* mi_col = */ 4,
/* mi_size = */ BLOCK_4X4,
/* has_chroma = */ false,
0,
0,
/* is_inter = */ false,
/* lossless = */ false,
/* skip = */ true,
/* tx_size = */ TX_4X4,
/* use_128x128_superblock = */ false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
// TX_4X4 ⇒ stepX = stepY = 1; SB-local anchor (4, 4).
assert!(
walker.block_decoded(0, 4, 4),
"SB-local [4][4] must be decoded"
);
assert!(
!walker.block_decoded(0, 4, 5),
"adjacent SB-local [4][5] stays 0 (1×1 footprint)"
);
}
/// `residual()` gate-open `!skip` arm with `all_zero == 1` forced
/// on every TU: the dispatcher invokes `coefficients()` per TU
/// and the §5.11.39 reader short-circuits at the `all_zero` S().
/// `coeffs.len() == tasks.len()`; every entry has `all_zero ==
/// true` and `eob == 0`. The dispatcher completes cleanly; on
/// every `eob == 0` TU the r182 residuals vec carries `None`
/// (no §7.13 inverse-transform call).
#[test]
fn residual_no_skip_all_zero_completes_without_reconstruct_stub() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force §5.11.39 `all_zero` S() to return 1 for every TU.
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(1);
}
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r = walker
.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* has_chroma = */ true,
0,
0,
/* is_inter = */ false,
/* lossless = */ false,
/* skip = */ false,
TX_8X8,
false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
assert_eq!(r.tasks.len(), 3);
assert_eq!(r.coeffs.len(), 3, "!skip ⇒ one coefficient readout per TU");
for c in &r.coeffs {
assert!(c.all_zero, "rigged CDFs force all_zero = 1");
assert_eq!(c.eob, 0);
assert_eq!(c.cul_level, 0);
assert_eq!(c.dc_category, 0);
}
// r182: every all_zero TU records `None` on the residuals vec —
// §7.13.3 inverse_transform_2d is gated on `eob > 0`.
assert_eq!(
r.residuals.len(),
3,
"!skip ⇒ residuals length matches tasks length"
);
for residual in &r.residuals {
assert!(residual.is_none(), "all_zero TU ⇒ no Residual computed");
}
}
/// `residual()` lossless path forces `txSz = TX_4X4` (the §5.11.34
/// line 12 short-circuit). Verify per-plane task `tx_size = TX_4X4`.
#[test]
fn residual_lossless_forces_tx_4x4_per_plane() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r = walker
.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* has_chroma = */ true,
0,
0,
false,
/* lossless = */ true,
/* skip = */ true,
TX_8X8,
false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
for t in &r.tasks {
assert_eq!(
t.tx_size as usize, TX_4X4,
"Lossless ⇒ TX_4X4 (got {} for plane {})",
t.tx_size, t.plane
);
}
// BLOCK_8X8 lossless ⇒ TX_4X4 ⇒ num4x4W = num4x4H = 2, stepX
// = stepY = 1 ⇒ 4 TUs per plane × 3 planes = 12 total.
assert_eq!(r.tasks.len(), 12);
}
/// `residual()` monochrome (`has_chroma == false`) emits only
/// plane 0 tasks.
#[test]
fn residual_monochrome_emits_only_luma_tasks() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r = walker
.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* has_chroma = */ false,
0,
0,
false,
false,
/* skip = */ true,
TX_8X8,
false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
assert_eq!(r.num_planes_visited, 1, "monochrome ⇒ 1 plane");
for t in &r.tasks {
assert_eq!(t.plane, 0, "monochrome ⇒ only luma");
}
}
/// `residual()` BLOCK_128X128 chunk-grid: `widthChunks = 2`,
/// `heightChunks = 2`, `miSizeChunk = BLOCK_64X64`. Four chunks
/// emit per plane.
#[test]
fn residual_block_128x128_chunks_to_4_chunks() {
// 128x128 fits in a 32×32 mi grid (32 * 4 = 128 luma).
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let mi_size_128x128 = BLOCK_64X64 + 3;
let r = walker
.residual(
&mut dec,
&mut cdfs,
0,
0,
mi_size_128x128,
/* has_chroma = */ true,
0,
0,
false,
/* lossless = */ true,
/* skip = */ true,
TX_4X4,
/* use_128x128_superblock = */ true,
&ResidualContext::neutral(0, 8),
)
.unwrap();
assert_eq!(r.width_chunks, 2, "BLOCK_128X128 ⇒ widthChunks = 2");
assert_eq!(r.height_chunks, 2, "BLOCK_128X128 ⇒ heightChunks = 2");
assert_eq!(
r.mi_size_chunk, BLOCK_64X64 as u8,
"(widthChunks > 1 || heightChunks > 1) ⇒ miSizeChunk = BLOCK_64X64"
);
// chunkY axis emits before chunkX — verify (chunk_y, chunk_x)
// ordering of the first few tasks.
let first = r.tasks[0];
assert_eq!((first.chunk_x, first.chunk_y), (0, 0));
// Per-plane visits inside chunk (0,0) precede chunk (1,0).
let chunk_pairs: Vec<(u8, u8)> = r.tasks.iter().map(|t| (t.chunk_x, t.chunk_y)).collect();
// First emission should be chunk (0,0); somewhere later, chunk
// (1,0) and (0,1) and (1,1) appear.
assert!(chunk_pairs.contains(&(1, 0)));
assert!(chunk_pairs.contains(&(0, 1)));
assert!(chunk_pairs.contains(&(1, 1)));
}
/// `residual()` caller-bug guards.
#[test]
fn residual_caller_bug_guards() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
// Out-of-range mi_size.
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
assert_eq!(
walker.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_SIZES,
false,
0,
0,
false,
false,
true,
TX_4X4,
false,
&ResidualContext::neutral(0, 8)
),
Err(crate::Error::PartitionWalkOutOfRange)
);
// Out-of-range mi_row.
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
assert_eq!(
walker.residual(
&mut dec,
&mut cdfs,
9,
0,
BLOCK_4X4,
false,
0,
0,
false,
false,
true,
TX_4X4,
false,
&ResidualContext::neutral(0, 8)
),
Err(crate::Error::PartitionWalkOutOfRange)
);
// Out-of-range mi_col.
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
assert_eq!(
walker.residual(
&mut dec,
&mut cdfs,
0,
9,
BLOCK_4X4,
false,
0,
0,
false,
false,
true,
TX_4X4,
false,
&ResidualContext::neutral(0, 8)
),
Err(crate::Error::PartitionWalkOutOfRange)
);
// Out-of-range subsampling_x.
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
assert_eq!(
walker.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
false,
2,
0,
false,
false,
true,
TX_4X4,
false,
&ResidualContext::neutral(0, 8)
),
Err(crate::Error::PartitionWalkOutOfRange)
);
// Out-of-range subsampling_y.
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
assert_eq!(
walker.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
false,
0,
2,
false,
false,
true,
TX_4X4,
false,
&ResidualContext::neutral(0, 8)
),
Err(crate::Error::PartitionWalkOutOfRange)
);
// Out-of-range tx_size.
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
assert_eq!(
walker.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_4X4,
false,
0,
0,
false,
false,
true,
TX_SIZES_ALL,
false,
&ResidualContext::neutral(0, 8)
),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
/// `residual()` inter-luma `transform_tree` recursion: BLOCK_8X8
/// inter + non-lossless, plane 0 routes through the §5.11.36
/// recursion. With InterTxSizes default (TX_4X4) the recursion
/// splits the 8×8 region into 4× TX_4X4 leaves. `from_transform_
/// tree == true` on every plane-0 task.
#[test]
fn residual_inter_luma_transform_tree_emits_per_tx_leaf() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
// The walker's InterTxSizes grid defaults to TX_4X4. The
// §5.11.36 recursion at (start=0,0, w=8, h=8) sees lumaW =
// lumaH = 4 < 8, so splits 4-ways into four 4×4 leaves.
let r = walker
.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* has_chroma = */ false, // skip chroma to simplify
0,
0,
/* is_inter = */ true,
/* lossless = */ false,
/* skip = */ true,
TX_8X8,
false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
// 4 luma TUs via the transform_tree split + 0 chroma.
assert_eq!(r.tasks.len(), 4);
for t in &r.tasks {
assert_eq!(t.plane, 0);
assert!(
t.from_transform_tree,
"inter-luma arm routes through transform_tree"
);
assert_eq!(
t.tx_size as usize, TX_4X4,
"InterTxSizes default ⇒ find_tx_size(4, 4) = TX_4X4"
);
}
}
/// r182: `residual()` gate-open `!skip` arm with `txb_skip` rigged
/// to `0` (the §5.11.39 `all_zero` short-circuit closed); the
/// reader produces a non-zero `eob` per TU, and the §5.11.34
/// dispatcher invokes [`crate::transform::inverse_transform_2d`]
/// on every such TU. The per-TU `Residual[][]` is recorded as
/// `Some(...)` of length `Tx_Width[txSz] * Tx_Height[txSz]`.
#[test]
fn residual_no_skip_eob_positive_invokes_inverse_transform() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force §5.11.39 `all_zero` S() to return 0 for every TU
// (cascade fires; eob >= 1).
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(0);
}
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r = walker
.residual(
&mut dec,
&mut cdfs,
/* mi_row = */ 0,
/* mi_col = */ 0,
/* mi_size = */ BLOCK_8X8,
/* has_chroma = */ true,
/* subsampling_x = */ 0,
/* subsampling_y = */ 0,
/* is_inter = */ false,
/* lossless = */ false,
/* skip = */ false,
/* tx_size = */ TX_8X8,
/* use_128x128_superblock = */ false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
assert_eq!(
r.tasks.len(),
3,
"3 planes ⇒ 3 TUs on the BLOCK_8X8 / TX_8X8 path"
);
assert_eq!(r.coeffs.len(), 3);
assert_eq!(r.residuals.len(), 3);
// Every TU went through §5.11.39 cascade ⇒ eob >= 1 ⇒
// §7.13.3 inverse_transform_2d fired ⇒ Some(Vec).
for (i, residual) in r.residuals.iter().enumerate() {
let res = residual
.as_ref()
.unwrap_or_else(|| panic!("TU {i}: §7.13.3 must fire when eob > 0"));
let tx_sz = r.tasks[i].tx_size as usize;
let expected_len = TX_WIDTH[tx_sz] * TX_HEIGHT[tx_sz];
assert_eq!(
res.len(),
expected_len,
"TU {i}: Residual length must match Tx_Width * Tx_Height"
);
}
}
// -----------------------------------------------------------------
// Round 183 — §7.12.2 dequantization-function tables + §7.12.3
// step-1 dequant loop + §5.11.47 transform_type S() reader.
// -----------------------------------------------------------------
/// `Dc_Qlookup` / `Ac_Qlookup` row dimensions and per-bit-depth
/// row[0] / row[255] spec values per §7.12.2 (av1-spec p.289-292).
#[test]
fn dc_ac_qlookup_table_spot_checks() {
// Every row: length 256, first entry 4, last entry monotonic.
for row in 0..3 {
assert_eq!(DC_QLOOKUP[row].len(), 256);
assert_eq!(AC_QLOOKUP[row].len(), 256);
assert_eq!(DC_QLOOKUP[row][0], 4);
assert_eq!(AC_QLOOKUP[row][0], 4);
}
// 8-bit row last entries from spec p.289 / p.291.
assert_eq!(DC_QLOOKUP[0][255], 1336);
assert_eq!(AC_QLOOKUP[0][255], 1828);
// 10-bit row last entries from spec p.289 / p.291.
assert_eq!(DC_QLOOKUP[1][255], 5347);
assert_eq!(AC_QLOOKUP[1][255], 7312);
// 12-bit row last entries from spec p.290 / p.292.
assert_eq!(DC_QLOOKUP[2][255], 21387);
assert_eq!(AC_QLOOKUP[2][255], 29247);
}
/// `dc_q` / `ac_q` row selection + `Clip3( 0, 255, b )` clamp per
/// §7.12.2.
#[test]
fn dc_q_ac_q_clip_and_row_select() {
// 8-bit row.
assert_eq!(dc_q(8, 0), DC_QLOOKUP[0][0]);
assert_eq!(dc_q(8, 255), DC_QLOOKUP[0][255]);
// Out-of-range b clamps to [0, 255].
assert_eq!(dc_q(8, -5), DC_QLOOKUP[0][0]);
assert_eq!(dc_q(8, 1024), DC_QLOOKUP[0][255]);
// 10-bit row.
assert_eq!(ac_q(10, 100), AC_QLOOKUP[1][100]);
// 12-bit row.
assert_eq!(ac_q(12, 200), AC_QLOOKUP[2][200]);
}
/// `get_qindex` per §7.12.2 — `base_q_idx` fallback when
/// segmentation off and delta_q off.
#[test]
fn get_qindex_no_segmentation_returns_base() {
let q = QuantizerParams::neutral(57, 8);
assert_eq!(get_qindex(&q, true, 0), 57);
assert_eq!(get_qindex(&q, false, 5), 57);
}
/// `get_qindex` per §7.12.2 — `delta_q_present` arm.
#[test]
fn get_qindex_delta_q_present_path() {
let mut q = QuantizerParams::neutral(50, 8);
q.delta_q_present = true;
q.current_q_index = 120;
// ignore_delta_q == true ⇒ base_q_idx fallback.
assert_eq!(get_qindex(&q, true, 0), 50);
// ignore_delta_q == false ⇒ current_q_index.
assert_eq!(get_qindex(&q, false, 0), 120);
}
/// `get_qindex` per §7.12.2 — segmentation-active path with
/// `SEG_LVL_ALT_Q` feature.
#[test]
fn get_qindex_segmentation_alt_q_path() {
let mut q = QuantizerParams::neutral(100, 8);
q.segmentation_enabled = true;
q.seg_alt_q_active[3] = true;
q.seg_alt_q_data[3] = -40;
assert_eq!(get_qindex(&q, false, 3), 60);
// Other segments fall back to base_q_idx.
assert_eq!(get_qindex(&q, false, 0), 100);
// Clip3 lower bound.
q.seg_alt_q_data[3] = -255;
assert_eq!(get_qindex(&q, false, 3), 0);
}
/// `get_dc_quant` / `get_ac_quant` per-plane delta routing per
/// §7.12.2.
#[test]
fn get_dc_ac_quant_per_plane_delta_routing() {
let mut q = QuantizerParams::neutral(80, 8);
q.delta_q_y_dc = 5;
q.delta_q_u_dc = -3;
q.delta_q_u_ac = 7;
q.delta_q_v_dc = 10;
q.delta_q_v_ac = -8;
// Y DC = dc_q(8, 80 + 5).
assert_eq!(get_dc_quant(&q, 0, 0), dc_q(8, 85));
// U DC = dc_q(8, 80 + (-3)).
assert_eq!(get_dc_quant(&q, 1, 0), dc_q(8, 77));
// V DC.
assert_eq!(get_dc_quant(&q, 2, 0), dc_q(8, 90));
// Y AC = ac_q(8, base_q_idx) — no DeltaQYAc field in the spec.
assert_eq!(get_ac_quant(&q, 0, 0), ac_q(8, 80));
assert_eq!(get_ac_quant(&q, 1, 0), ac_q(8, 87));
assert_eq!(get_ac_quant(&q, 2, 0), ac_q(8, 72));
}
/// `dequant_denom` per §7.12.3 — 4 for 64x64-axis transforms, 2
/// for 32x32-axis transforms, 1 otherwise.
#[test]
fn dequant_denom_per_tx_size() {
assert_eq!(dequant_denom(TX_4X4), 1);
assert_eq!(dequant_denom(TX_8X8), 1);
assert_eq!(dequant_denom(TX_16X16), 1);
assert_eq!(dequant_denom(TX_32X32), 2);
assert_eq!(dequant_denom(TX_64X64), 4);
assert_eq!(dequant_denom(TX_16X32), 2);
assert_eq!(dequant_denom(TX_32X16), 2);
assert_eq!(dequant_denom(TX_16X64), 2);
assert_eq!(dequant_denom(TX_64X16), 2);
assert_eq!(dequant_denom(TX_32X64), 4);
assert_eq!(dequant_denom(TX_64X32), 4);
// Tiny / asymmetric below 32 ⇒ 1.
assert_eq!(dequant_denom(TX_4X8), 1);
assert_eq!(dequant_denom(TX_8X4), 1);
assert_eq!(dequant_denom(TX_16X4), 1);
assert_eq!(dequant_denom(TX_8X16), 1);
assert_eq!(dequant_denom(TX_16X8), 1);
}
/// `dequantize_step1` 4x4 DC-only block per §7.12.3 step-1: with
/// `Quant[0] = 1` and `quant.base_q_idx = 0`, the §7.12.2 lookup
/// returns `dc_q(8, 0) = 4`, so `Dequant[0] = 1 * 4 / 1 = 4`.
/// Every other cell is `0` (Quant is all-zero outside DC).
#[test]
fn dequantize_step1_dc_only_4x4_base_q_idx_zero() {
let q = QuantizerParams::neutral(0, 8);
let levels = {
let mut v = vec![0i32; 16];
v[0] = 1;
v
};
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, DCT_DCT, 15, &q);
assert_eq!(dequant.len(), 16);
assert_eq!(dequant[0], 4, "DC = level * dc_q(8, 0) / 1 = 1 * 4");
for &v in &dequant[1..] {
assert_eq!(v, 0);
}
}
/// `dequantize_step1` per-coefficient AC quant + signed product +
/// `&0xFFFFFF` clip + `dqDenom` divide.
#[test]
fn dequantize_step1_ac_and_dq_denom() {
let q = QuantizerParams::neutral(50, 8);
let dc_q_val = dc_q(8, 50);
let ac_q_val = ac_q(8, 50);
let mut levels = vec![0i32; 16];
levels[0] = 2; // DC
levels[1] = -3; // first AC (row 0, col 1)
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, DCT_DCT, 15, &q);
assert_eq!(dequant[0], (2 * dc_q_val) as i64);
// Negative product: -3 * ac_q ⇒ sign = -1 ⇒ result is
// -1 * (|product| & 0xFFFFFF) / 1.
let product = -3i64 * ac_q_val as i64;
let sign: i64 = if product < 0 { -1 } else { 1 };
let expected = sign * ((product.unsigned_abs() & 0xFFFFFF) as i64);
assert_eq!(dequant[1], expected);
}
/// `dequantize_step1` `dqDenom = 4` on `TX_64X64` halves the
/// `Dequant[]` cells (vs `dqDenom = 1` baseline). Drives only the
/// top-left 32×32 cells per spec `tw / th = Min(32, w/h)`; the
/// remaining cells stay at the §5.11.39 `Dequant[..][..] = 0`
/// pre-loop default.
#[test]
fn dequantize_step1_64x64_dq_denom_four() {
let q = QuantizerParams::neutral(0, 8);
let mut levels = vec![0i32; 64 * 64];
levels[0] = 8;
let dequant = dequantize_step1(&levels, TX_64X64, 0, 0, DCT_DCT, 15, &q);
// DC = level * dc_q(8, 0) / 4 = 8 * 4 / 4 = 8.
assert_eq!(dequant[0], 8);
// The bottom-right of the top-left 32x32 is the last cell
// touched; cell (32, 0) sits outside `tw = 32, th = 32` so
// it stays at zero (Quant is zero there anyway).
assert_eq!(dequant.len(), 64 * 64);
}
/// `dequantize_step1` clip per §7.12.3 step-1 line f:
/// `Clip3(-(1<<(7+BitDepth)), (1<<(7+BitDepth))-1, dq2)`.
/// For 8-bit, bounds are `[-32768, 32767]`.
#[test]
fn dequantize_step1_clips_dequant_to_signed_15bit_band() {
let q = QuantizerParams::neutral(255, 8);
// Very large DC level × dc_q(8, 255) = 1000 * 1336.
let mut levels = vec![0i32; 16];
levels[0] = 1000;
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, DCT_DCT, 15, &q);
// 1000 * 1336 = 1_336_000; raw, that's well above 32767.
// dqDenom = 1 for TX_4X4 ⇒ clip clamps to (1<<15) - 1 = 32767.
assert_eq!(dequant[0], 32767);
// Negative side.
levels[0] = -1000;
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, DCT_DCT, 15, &q);
assert_eq!(dequant[0], -32768);
}
/// `dequantize_step1` `seg_qm_level == 15` short-circuits the QM
/// lookup (the no-QM identity path runs regardless of
/// `using_qmatrix`).
#[test]
fn dequantize_step1_seg_qm_level_15_skips_qm() {
let mut q = QuantizerParams::neutral(50, 8);
q.using_qmatrix = true;
let mut levels = vec![0i32; 16];
levels[0] = 1;
// seg_qm_level == 15 ⇒ no QM (`q2 = q`).
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, DCT_DCT, 15, &q);
assert_eq!(dequant[0], dc_q(8, 50) as i64);
}
/// §7.12.3 step-1b QM-active arm. Level 0 luma DC entry is
/// `Quantizer_Matrix[0][0][0] = 32`, so
/// `q2 = Round2( q * 32, 5 ) = (q * 32 + 16) >> 5`. For
/// `base_q_idx = 50` and `Quant[0] = 1`, the result must match
/// the spec's Round2 form.
#[test]
fn dequantize_step1_qm_active_level0_luma_dc() {
let mut q = QuantizerParams::neutral(50, 8);
q.using_qmatrix = true;
let mut levels = vec![0i32; 16];
levels[0] = 1;
// seg_qm_level = 0 + PlaneTxType = DCT_DCT < IDTX ⇒ QM-active.
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, DCT_DCT, 0, &q);
let dc_q_val = dc_q(8, 50) as i64;
// Quantizer_Matrix[0][0][0] = 32.
let expected_q2 = (dc_q_val * 32 + 16) >> 5;
assert_eq!(dequant[0], expected_q2);
}
/// §7.12.3 step-1b QM-active arm: a chroma-plane lookup uses
/// `plane > 0` to select the chroma-class matrix. Level 0 chroma
/// 4x4 DC = 35 per spec p.511.
#[test]
fn dequantize_step1_qm_active_chroma_uses_plane_class_one() {
let mut q = QuantizerParams::neutral(60, 8);
q.using_qmatrix = true;
let mut levels = vec![0i32; 16];
levels[0] = 1;
let dequant_u = dequantize_step1(&levels, TX_4X4, 1, 0, DCT_DCT, 0, &q);
let dequant_v = dequantize_step1(&levels, TX_4X4, 2, 0, DCT_DCT, 0, &q);
let dc_q_u = get_dc_quant(&q, 1, 0) as i64;
let dc_q_v = get_dc_quant(&q, 2, 0) as i64;
// Quantizer_Matrix[0][1][0] = 35 for chroma DC.
assert_eq!(dequant_u[0], (dc_q_u * 35 + 16) >> 5);
assert_eq!(dequant_v[0], (dc_q_v * 35 + 16) >> 5);
}
/// §7.12.3 step-1b QM-active arm: `plane_tx_type >= IDTX` falls
/// through to the identity `q2 = q` branch even when the other
/// guards (using_qmatrix && seg_qm_level < 15) hold.
#[test]
fn dequantize_step1_qm_inactive_when_plane_tx_type_is_idtx() {
let mut q = QuantizerParams::neutral(50, 8);
q.using_qmatrix = true;
let mut levels = vec![0i32; 16];
levels[0] = 1;
// IDTX guard ⇒ QM short-circuits to `q2 = q`.
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, IDTX, 0, &q);
assert_eq!(dequant[0], dc_q(8, 50) as i64);
}
/// §7.12.3 step-1b QM-active arm on an AC bin. Level 0 luma 4x4
/// position (0, 1) is 43 per spec p.511, so the AC product uses
/// the `q * 43` rounded form.
#[test]
fn dequantize_step1_qm_active_ac_uses_correct_bin_weight() {
let mut q = QuantizerParams::neutral(80, 8);
q.using_qmatrix = true;
let mut levels = vec![0i32; 16];
levels[1] = 2; // first AC at (i=0, j=1).
let dequant = dequantize_step1(&levels, TX_4X4, 0, 0, DCT_DCT, 0, &q);
let ac_q_val = ac_q(8, 80) as i64;
// Quantizer_Matrix[0][0][1] = 43 for luma (0, 1).
let q2 = (ac_q_val * 43 + 16) >> 5;
let dq = 2 * q2;
let expected = (dq.unsigned_abs() & 0xFFFFFF) as i64; // sign = +1
assert_eq!(dequant[1], expected);
}
/// Tx_Type_*_Inv_Set inversion tables match the §5.11.47 spec
/// tables verbatim.
#[test]
fn tx_type_inv_set_tables_match_spec() {
assert_eq!(
TX_TYPE_INTRA_INV_SET1,
[IDTX, DCT_DCT, V_DCT, H_DCT, ADST_ADST, ADST_DCT, DCT_ADST]
);
assert_eq!(
TX_TYPE_INTRA_INV_SET2,
[IDTX, DCT_DCT, ADST_ADST, ADST_DCT, DCT_ADST]
);
assert_eq!(TX_TYPE_INTER_INV_SET3, [IDTX, DCT_DCT]);
assert_eq!(TX_TYPE_INTER_INV_SET1.len(), 16);
assert_eq!(TX_TYPE_INTER_INV_SET2.len(), 12);
}
/// `transform_type` short-circuits to `DCT_DCT` when
/// `base_q_idx == 0` AND `segmentation_enabled == false` (the
/// §5.11.47 spec guard `(segmentation_enabled ? get_qindex(1,
/// segment_id) : base_q_idx) > 0`).
#[test]
fn transform_type_base_q_idx_zero_returns_dct_dct() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let q = QuantizerParams::neutral(0, 8);
// tx_size = TX_8X8 ⇒ TX_SET_INTRA_1 admits a transform_type
// read, but the qindex == 0 guard forces DCT_DCT without
// touching the bitstream.
let pos_before = dec.position();
let r = walker
.transform_type(&mut dec, &mut cdfs, TX_8X8, false, 0, false, 0, &q)
.unwrap();
let pos_after = dec.position();
assert_eq!(r.tx_type, DCT_DCT as u8);
assert_eq!(r.w4, 2);
assert_eq!(r.h4, 2);
assert_eq!(pos_before, pos_after, "qindex == 0 ⇒ no bitstream read");
}
/// `transform_type` `TX_SET_DCTONLY` short-circuit when
/// `txSzSqrUp > TX_32X32` (so `get_tx_set` returns
/// `TX_SET_DCTONLY = 0`).
#[test]
fn transform_type_tx_set_dctonly_returns_dct_dct_no_read() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 32,
mi_col_start: 0,
mi_col_end: 32,
};
let mut walker = PartitionWalker::new(32, 32, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 8];
let mut dec = SymbolDecoder::init_symbol(&bytes, 8, true).unwrap();
let q = QuantizerParams::neutral(100, 8);
let pos_before = dec.position();
// TX_64X64 ⇒ TX_SET_DCTONLY on the intra path.
let r = walker
.transform_type(&mut dec, &mut cdfs, TX_64X64, false, 0, false, 0, &q)
.unwrap();
let pos_after = dec.position();
assert_eq!(r.tx_type, DCT_DCT as u8);
assert_eq!(pos_before, pos_after, "TX_SET_DCTONLY ⇒ no bitstream read");
}
/// `transform_type` codes a symbol on `TX_SET_INTRA_1` (TX_8X8
/// intra, base_q_idx > 0). The default CDFs return symbol 0 from
/// a zero-filled bitstream which inverts via
/// `TX_TYPE_INTRA_INV_SET1[0] = IDTX`.
#[test]
fn transform_type_intra_set1_codes_symbol() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force the §8.3.2 intra_tx_type CDF to return symbol 0
// (= IDTX after inversion) deterministically.
for sqr in 0..INTRA_TX_TYPE_SET1_SIZES {
for d in 0..INTRA_MODES {
cdfs.intra_tx_type_set1[sqr][d] = rig_cdf::<8>(0);
}
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let q = QuantizerParams::neutral(50, 8);
let r = walker
.transform_type(&mut dec, &mut cdfs, TX_8X8, false, 0, false, 0, &q)
.unwrap();
// `TX_TYPE_INTRA_INV_SET1[0] = IDTX`.
assert_eq!(r.tx_type as usize, IDTX);
}
/// r286 — `tx_type_to_symbol` (the §5.11.47 encode-side forward
/// map) is the exact inverse of the read-path `Tx_Type_*_Inv_Set*`
/// lookup: for every symbol ordinal in every set, mapping the
/// inverted `TxType` back yields the same ordinal; a `TxType` not in
/// the set returns `None`; `TX_SET_DCTONLY` returns `None`.
#[test]
fn tx_type_to_symbol_inverts_read_path_tables() {
let intra_sets: &[(u32, &[usize])] = &[
(TX_SET_INTRA_1, &TX_TYPE_INTRA_INV_SET1),
(TX_SET_INTRA_2, &TX_TYPE_INTRA_INV_SET2),
];
for &(set, table) in intra_sets {
for (sym, &tx_type) in table.iter().enumerate() {
assert_eq!(
tx_type_to_symbol(set, false, tx_type),
Some(sym as u32),
"intra set {set} symbol {sym} round-trips"
);
}
}
let inter_sets: &[(u32, &[usize])] = &[
(TX_SET_INTER_1, &TX_TYPE_INTER_INV_SET1),
(TX_SET_INTER_2, &TX_TYPE_INTER_INV_SET2),
(TX_SET_INTER_3, &TX_TYPE_INTER_INV_SET3),
];
for &(set, table) in inter_sets {
for (sym, &tx_type) in table.iter().enumerate() {
assert_eq!(
tx_type_to_symbol(set, true, tx_type),
Some(sym as u32),
"inter set {set} symbol {sym} round-trips"
);
}
}
// FLIPADST_DCT is in the inter sets but never the intra ones.
assert_eq!(tx_type_to_symbol(TX_SET_INTRA_1, false, FLIPADST_DCT), None);
assert_eq!(tx_type_to_symbol(TX_SET_INTRA_2, false, FLIPADST_DCT), None);
// V_DCT is in TX_SET_INTRA_1 but NOT TX_SET_INTRA_2.
assert!(tx_type_to_symbol(TX_SET_INTRA_1, false, V_DCT).is_some());
assert_eq!(tx_type_to_symbol(TX_SET_INTRA_2, false, V_DCT), None);
// TX_SET_DCTONLY codes no symbol on either path.
assert_eq!(tx_type_to_symbol(TX_SET_DCTONLY, false, DCT_DCT), None);
assert_eq!(tx_type_to_symbol(TX_SET_DCTONLY, true, DCT_DCT), None);
}
/// `stamp_tx_type` writes the `TxTypes[][]` grid over a `(w4, h4)`
/// footprint anchored at `(x4, y4)`. Out-of-bounds cells are
/// silently dropped.
#[test]
fn stamp_tx_type_grid_footprint() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
walker.stamp_tx_type(2, 3, 2, 2, ADST_ADST as u8);
for r in 0i32..8 {
for c in 0i32..8 {
let expected = if (3..=4).contains(&r) && (2..=3).contains(&c) {
ADST_ADST as u8
} else {
DCT_DCT as u8
};
assert_eq!(walker.tx_type_at(r, c), expected, "({r}, {c}) ⇒ {expected}");
}
}
}
/// `tx_type_at` returns `DCT_DCT` for out-of-bounds reads.
#[test]
fn tx_type_at_out_of_bounds_returns_dct_dct() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let walker = PartitionWalker::new(4, 4, geom).unwrap();
assert_eq!(walker.tx_type_at(-1, 0), DCT_DCT as u8);
assert_eq!(walker.tx_type_at(0, -1), DCT_DCT as u8);
assert_eq!(walker.tx_type_at(4, 0), DCT_DCT as u8);
assert_eq!(walker.tx_type_at(0, 4), DCT_DCT as u8);
}
/// `ResidualContext::neutral` smoke — every field defaults to a
/// no-op spec value.
#[test]
fn residual_context_neutral_defaults() {
let ctx = ResidualContext::neutral(0, 8);
assert_eq!(ctx.quant.base_q_idx, 0);
assert_eq!(ctx.quant.bit_depth, 8);
assert!(!ctx.reduced_tx_set);
assert_eq!(ctx.intra_dir, 0);
assert_eq!(ctx.segment_id, 0);
assert_eq!(ctx.seg_qm_level, [15, 15, 15]);
assert_eq!(ctx.uv_mode, 0);
}
/// End-to-end smoke for the §7.12.3 step-1 + §7.13 inverse
/// transform pipeline on a 4×4 DC-only intra-luma block.
///
/// Drives the §5.11.34 / §5.11.35 dispatcher directly with a
/// rigged `all_zero == 1` CDF (so the §5.11.39 reader emits
/// `Quant[0..16] = 0` with `eob = 0`); on that path the
/// `residuals` vec entry is `None` per the existing post-r182
/// contract, confirming the new dequant code is wired through
/// without disturbing the gate-closed path.
#[test]
fn residual_dc_only_block_pipeline_smoke() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force `all_zero == 1` for every TU at TX_4X4.
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[0][ctx] = rig_cdf::<3>(1);
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let ctx = ResidualContext {
quant: QuantizerParams::neutral(0, 8),
reduced_tx_set: false,
intra_dir: 0,
segment_id: 0,
seg_qm_level: [15, 15, 15],
uv_mode: 0,
is_intra: true,
y_mode: 0,
angle_delta_y: 0,
angle_delta_uv: 0,
cfl_alpha_u: 0,
cfl_alpha_v: 0,
filter_intra_mode: None,
avail_l: false,
avail_u: false,
avail_l_chroma: false,
avail_u_chroma: false,
enable_intra_edge_filter: false,
};
let r = walker
.residual(
&mut dec, &mut cdfs, 0, 0, BLOCK_4X4, /* has_chroma = */ false, 0, 0,
/* is_inter = */ false, /* lossless = */ false, /* skip = */ false,
TX_4X4, false, &ctx,
)
.unwrap();
assert_eq!(r.tasks.len(), 1, "1 luma TU on the monochrome 4x4 path");
assert_eq!(r.coeffs.len(), 1);
assert!(r.coeffs[0].all_zero);
assert_eq!(r.coeffs[0].eob, 0);
assert!(
r.residuals[0].is_none(),
"eob == 0 ⇒ no inverse_transform_2d"
);
}
/// `ResidualReadout` `Clone` / `Debug` / `Eq` derive smoke.
#[test]
fn residual_readout_clone_debug_smoke() {
let r = ResidualReadout {
width_chunks: 1,
height_chunks: 1,
mi_size_chunk: BLOCK_8X8 as u8,
num_planes_visited: 3,
tasks: vec![ResidualTuTask {
plane: 0,
start_x: 0,
start_y: 0,
tx_size: TX_4X4 as u8,
chunk_x: 0,
chunk_y: 0,
from_transform_tree: false,
plane_tx_type: DCT_DCT as u8,
}],
coeffs: vec![],
residuals: vec![],
skip: true,
};
let r2 = r.clone();
assert_eq!(r, r2);
// Debug formatter doesn't panic.
let _ = format!("{r:?}");
}
// ============================================================
// r185 — §7.12.3 step-3 frame-buffer merge tests.
// ============================================================
/// `curr_frame()` / `curr_frame_dims()` are `None` on a freshly
/// constructed walker — the per-plane buffers are allocated
/// lazily on the first §7.12.3 step-3 merge call.
#[test]
fn curr_frame_unallocated_on_fresh_walker() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let walker = PartitionWalker::new(4, 4, geom).unwrap();
for plane in 0..3 {
assert!(walker.curr_frame(plane).is_none());
assert!(walker.curr_frame_dims(plane).is_none());
}
// Out-of-range plane: also None (defensive).
assert!(walker.curr_frame(3).is_none());
assert!(walker.curr_frame_dims(3).is_none());
}
/// `ensure_curr_frame_plane` sizes the luma buffer at `MiRows *
/// MI_SIZE × MiCols * MI_SIZE` with a zero fill. Chroma buffer
/// dimensions follow `>> sub_y` / `>> sub_x` per §5.11.34 line
/// 19. The 4:2:0 chroma case halves both axes.
#[test]
fn ensure_curr_frame_plane_sizes_per_subsampling() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// Luma: 4 mi rows × 4 mi cols × MI_SIZE = 16 × 16.
walker.ensure_curr_frame_plane(0, 16, 16);
assert_eq!(walker.curr_frame_dims(0), Some((16, 16)));
let luma = walker.curr_frame(0).unwrap();
assert_eq!(luma.len(), 16 * 16);
assert!(luma.iter().all(|&s| s == 0));
// 4:2:0 chroma: half-rate both axes → 8 × 8.
walker.ensure_curr_frame_plane(1, 8, 8);
assert_eq!(walker.curr_frame_dims(1), Some((8, 8)));
let chroma_u = walker.curr_frame(1).unwrap();
assert_eq!(chroma_u.len(), 8 * 8);
assert!(chroma_u.iter().all(|&s| s == 0));
}
/// §7.12.3 step-3 merge with `PlaneTxType = DCT_DCT` (no flip):
/// every `Residual[i][j]` lands at `CurrFrame[plane][start_y +
/// i][start_x + j]` post-`Clip1`. Identity sample mapping; the
/// 4×4 residual is written to the top-left 4×4 corner of a 16×16
/// luma buffer.
#[test]
fn curr_frame_step3_merge_dct_dct_identity_write() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// 4×4 residual with i*4 + j+1 in each cell (1..=16).
let residual: Vec<i64> = (0..16).map(|k| (k + 1) as i64).collect();
walker.curr_frame_step3_merge(
/* plane = */ 0, /* start_x = */ 0, /* start_y = */ 0, TX_4X4, DCT_DCT,
&residual, /* bit_depth = */ 8, /* sub_x = */ 0, /* sub_y = */ 0,
);
assert_eq!(walker.curr_frame_dims(0), Some((16, 16)));
let buf = walker.curr_frame(0).unwrap();
// Top-left 4×4 matches residual; everything else is 0.
for i in 0..4 {
for j in 0..4 {
assert_eq!(
buf[i * 16 + j],
(i * 4 + j + 1) as i32,
"DCT_DCT identity write at ({i}, {j})",
);
}
}
for i in 0..16 {
for j in 0..16 {
if i < 4 && j < 4 {
continue;
}
assert_eq!(
buf[i * 16 + j],
0,
"untouched samples remain 0 at ({i}, {j})",
);
}
}
}
// ============================================================
// r342 — §7.11.2.1 intra prediction into CurrFrame tests.
// ============================================================
/// `predict_intra_into_curr_frame` with `DC_PRED` and no
/// neighbours (`haveAbove == haveLeft == 0`) writes the §7.11.2.5
/// no-neighbour DC default `1 << (BitDepth - 1) = 128` across the
/// whole 4×4 TU.
#[test]
fn predict_intra_dc_no_neighbours_writes_128() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
walker.predict_intra_into_curr_frame(
/* plane */ 0, /* start_x */ 0, /* start_y */ 0, TX_4X4, DC_PRED,
/* angle_delta */ 0, /* bit_depth */ 8, /* sub_x */ 0,
/* sub_y */ 0, /* have_above */ false, /* have_left */ false,
/* have_above_right */ false, /* have_below_left */ false,
/* enable_intra_edge_filter */ false, /* filter_type */ 0,
/* filter_intra_mode */ None,
);
let buf = walker.curr_frame(0).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(buf[i * 16 + j], 128, "DC default at ({i}, {j})");
}
}
}
/// `V_PRED` copies the reconstructed above row down every column.
/// Seed the row above the TU (`start_y == 4`) with a ramp, then
/// predict the 4×4 TU at `(start_x = 0, start_y = 4)` and assert
/// each predicted row equals the seeded above row.
#[test]
fn predict_intra_v_pred_copies_above_row() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// Allocate the plane + seed row 3 (the row above start_y = 4)
// with samples 10, 20, 30, 40 in cols 0..4.
walker.ensure_curr_frame_plane(0, 16, 16);
{
let buf = walker.curr_frame[0].as_mut().unwrap();
for j in 0..4 {
buf.samples[3 * 16 + j] = (10 * (j + 1)) as i32;
}
}
walker.predict_intra_into_curr_frame(
0, 0, 4, TX_4X4, V_PRED, 0, 8, 0, 0, /* have_above */ true,
/* have_left */ false, false, false, false, 0, None,
);
let buf = walker.curr_frame(0).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(
buf[(4 + i) * 16 + j],
(10 * (j + 1)) as i32,
"V_PRED column copy at row {i} col {j}",
);
}
}
}
/// `H_PRED` copies the reconstructed left column across every row.
/// Seed the column left of the TU (`start_x == 4`) with a ramp,
/// predict the 4×4 TU at `(start_x = 4, start_y = 0)`, assert each
/// predicted column equals the seeded left column.
#[test]
fn predict_intra_h_pred_copies_left_col() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
walker.ensure_curr_frame_plane(0, 16, 16);
{
let buf = walker.curr_frame[0].as_mut().unwrap();
for i in 0..4 {
buf.samples[i * 16 + 3] = (10 * (i + 1)) as i32;
}
}
walker.predict_intra_into_curr_frame(
0, 4, 0, TX_4X4, H_PRED, 0, 8, 0, 0, /* have_above */ false,
/* have_left */ true, false, false, false, 0, None,
);
let buf = walker.curr_frame(0).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(
buf[i * 16 + (4 + j)],
(10 * (i + 1)) as i32,
"H_PRED row copy at row {i} col {j}",
);
}
}
}
/// r417 — the buffer-parameterised §7.11.2 variant
/// ([`PartitionWalker::predict_intra_into_u16_plane`]) is
/// sample-exact against the walker's own
/// `predict_intra_into_curr_frame` on identical neighbour content
/// (the two share the `intra_predict_block_samples` core; this
/// pins the wrapper plumbing — neighbour reads, extent clamps,
/// output placement) across the non-CfL §6.10.x modes, the
/// §7.11.2.4 directional pre-pass (non-zero angle deltas), and
/// the §7.11.2.3 filter-intra arm, over square and both
/// rectangular TX orientations.
#[test]
fn r417_u16_plane_intra_variant_matches_walker() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
// 8x8 mi = 32x32 samples per plane; deterministic texture.
let fill = |y: usize, x: usize| -> u16 {
((y * 37 + x * 11 + (y / 3) * (x / 5) + 7) % 251) as u16
};
let cases: [(usize, i32, Option<usize>); 16] = [
(DC_PRED, 0, None),
(V_PRED, 0, None),
(H_PRED, 0, None),
(V_PRED, 2, None),
(H_PRED, -1, None),
(D45_PRED, 0, None),
(D135_PRED, 1, None),
(D113_PRED, -2, None),
(D157_PRED, 0, None),
(D203_PRED, 3, None),
(D67_PRED, 0, None),
(SMOOTH_PRED, 0, None),
(SMOOTH_V_PRED, 0, None),
(SMOOTH_H_PRED, 0, None),
(PAETH_PRED, 0, None),
(DC_PRED, 0, Some(2)),
];
for (mode, delta, fim) in cases {
for (tx_sz, sx, sy) in [(TX_8X8, 8u32, 8u32), (TX_16X8, 8, 16), (TX_4X16, 12, 4)] {
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
walker.ensure_curr_frame_plane(0, 32, 32);
{
let buf = walker.curr_frame[0].as_mut().unwrap();
for y in 0..32usize {
for x in 0..32usize {
buf.samples[y * 32 + x] = i32::from(fill(y, x));
}
}
}
walker.predict_intra_into_curr_frame(
0, sx, sy, tx_sz, mode, delta, 8, 0, 0, true, true, true, false, true, 1, fim,
);
let walked: Vec<i32> = walker.curr_frame(0).unwrap().to_vec();
let mut out = vec![0u16; 32 * 32];
let neigh = |y: u32, x: u32| -> u16 { fill(y as usize, x as usize) };
PartitionWalker::predict_intra_into_u16_plane(
&neigh, &mut out, 32, 31, 31, sx, sy, tx_sz, mode, delta, 8, true, true, true,
false, true, 1, fim,
);
let (w, h) = (TX_WIDTH[tx_sz], TX_HEIGHT[tx_sz]);
for i in 0..h {
for j in 0..w {
let idx = (sy as usize + i) * 32 + sx as usize + j;
assert_eq!(
i64::from(out[idx]),
i64::from(walked[idx]),
"mode {mode} delta {delta} fim {fim:?} tx {tx_sz} at ({i}, {j})"
);
}
}
}
}
}
/// Prediction-then-residual composition realises the §5.11.35
/// `reconstruct()` body: the §7.11.2.1 prediction writes the DC
/// base into CurrFrame, then the §7.12.3 step-3 merge adds the
/// residual on top with a single `Clip1`. DC default 128 + a +5
/// residual everywhere = 133.
#[test]
fn predict_then_residual_composes() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
walker.predict_intra_into_curr_frame(
0, 0, 0, TX_4X4, DC_PRED, 0, 8, 0, 0, false, false, false, false, false, 0, None,
);
let residual: Vec<i64> = vec![5; 16];
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, DCT_DCT, &residual, 8, 0, 0);
let buf = walker.curr_frame(0).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(buf[i * 16 + j], 133, "pred 128 + residual 5 at ({i}, {j})");
}
}
}
/// The inter / non-intra gate: `predict_intra_into_curr_frame`
/// returns without allocating a buffer for an out-of-range
/// `mode >= INTRA_MODES` (e.g. the `UV_CFL_PRED` sentinel before
/// the caller's DC_PRED collapse).
#[test]
fn predict_intra_out_of_range_mode_is_no_op() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
walker.predict_intra_into_curr_frame(
0,
0,
0,
TX_4X4,
UV_CFL_PRED,
0,
8,
0,
0,
false,
false,
false,
false,
false,
0,
None,
);
assert!(walker.curr_frame(0).is_none());
}
/// Neighbour propagation: a second TU's prediction reads the
/// already-reconstructed samples of the first. Reconstruct a 4×4
/// DC_PRED TU at the origin with a `+50` residual (→ 178 = 128 +
/// 50 everywhere), then predict an adjacent 4×4 TU at `(start_x =
/// 4, start_y = 0)` with H_PRED + `have_left`. H_PRED copies the
/// first TU's right column (all 178) across the second TU — proving
/// the §7.11.2.1 `LeftCol[]` derivation reads the live CurrFrame.
#[test]
fn predict_intra_reads_reconstructed_left_neighbour() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// First TU: DC_PRED (128) + residual +50 = 178 across the 4×4.
walker.predict_intra_into_curr_frame(
0, 0, 0, TX_4X4, DC_PRED, 0, 8, 0, 0, false, false, false, false, false, 0, None,
);
let residual: Vec<i64> = vec![50; 16];
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, DCT_DCT, &residual, 8, 0, 0);
// Second TU at (4, 0): H_PRED with have_left reads the first
// TU's reconstructed right column (all 178).
walker.predict_intra_into_curr_frame(
0, 4, 0, TX_4X4, H_PRED, 0, 8, 0, 0, false, true, false, false, false, 0, None,
);
let buf = walker.curr_frame(0).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(
buf[i * 16 + (4 + j)],
178,
"H_PRED reads reconstructed left neighbour at ({i}, {j})",
);
}
}
}
/// §7.11.2.8 `get_filter_type( plane )` — returns 1 iff the block
/// immediately above OR to the left uses a smooth intra mode. Stamp
/// `SMOOTH_PRED` into the above-neighbour `YModes[]` (luma) /
/// `UVModes[]` (chroma) cell and assert the helper flips from 0 (no
/// smooth neighbour) to 1, including the §7.11.2.8 chroma
/// sub-sampled neighbour-coordinate adjustment.
#[test]
fn get_filter_type_detects_smooth_neighbour() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// Luma block at (MiRow = 1, MiCol = 1). Above neighbour is
// (0, 1), left neighbour is (1, 0). Both default to DC_PRED
// (= 0), so get_filter_type is 0 even with avail flags set.
assert_eq!(
walker.get_filter_type(0, 1, 1, /* avail_u */ true, /* avail_l */ true, 0, 0),
0,
"DC_PRED neighbours ⇒ filterType 0",
);
// Stamp SMOOTH_PRED into the above neighbour's YModes cell —
// `(MiRow 0, MiCol 1)` ⇒ flat index `0 * mi_cols + 1 = 1`.
let above_cell = 1usize; // (0, 1)
let left_cell = 4usize; // (1, 0) ⇒ 1 * mi_cols + 0
walker.y_modes[above_cell] = SMOOTH_PRED as u8;
assert_eq!(
walker.get_filter_type(0, 1, 1, true, true, 0, 0),
1,
"smooth above luma neighbour ⇒ filterType 1",
);
// When AvailU is false the above neighbour is ignored even
// though its grid cell is smooth — get_filter_type falls back
// to the (DC_PRED) left neighbour ⇒ 0.
assert_eq!(
walker.get_filter_type(0, 1, 1, /* avail_u */ false, /* avail_l */ true, 0, 0),
0,
"AvailU == 0 masks the smooth above neighbour",
);
// A smooth left neighbour (SMOOTH_H_PRED) also flips the flag.
walker.y_modes[above_cell] = DC_PRED as u8;
walker.y_modes[left_cell] = SMOOTH_H_PRED as u8;
assert_eq!(
walker.get_filter_type(0, 1, 1, true, true, 0, 0),
1,
"smooth left luma neighbour ⇒ filterType 1",
);
// Chroma (`plane == 1`) reads UVModes[] at the §7.11.2.8
// sub-sampled coordinates. For block (MiRow = 2, MiCol = 2) with
// 4:2:0 (subX = subY = 1): above neighbour `r = MiRow - 1 = 1`,
// `c = MiCol = 2`; `subX && !(MiCol & 1)` is true (MiCol 2 even)
// so `c++` ⇒ (1, 3). Stamp SMOOTH_V_PRED there and assert the
// chroma filterType flips to 1.
let mut walker2 = PartitionWalker::new(4, 4, geom).unwrap();
// No smooth UV neighbour yet.
assert_eq!(
walker2.get_filter_type(1, 2, 2, true, true, 1, 1),
0,
"chroma: no smooth UV neighbour ⇒ filterType 0",
);
// (1, 3) ⇒ 1 * mi_cols + 3 = 7.
walker2.uv_modes[7] = SMOOTH_V_PRED as u8;
assert_eq!(
walker2.get_filter_type(1, 2, 2, true, true, 1, 1),
1,
"chroma: sub-sampled above UV neighbour smooth ⇒ filterType 1",
);
// The luma lookup at the same coordinates reads YModes[] (still
// DC_PRED everywhere) ⇒ 0, proving the plane axis is honoured.
assert_eq!(
walker2.get_filter_type(0, 2, 2, true, true, 1, 1),
0,
"luma lookup ignores the stamped UVModes cell",
);
}
/// §7.11.2.4 step-4 directional pre-pass — wiring check. A `D45_PRED`
/// 16×16 luma TU with a non-flat reconstructed above row produces a
/// **different** prediction when `enable_intra_edge_filter` is on
/// (the §7.11.2.9/§7.11.2.12 intra edge filter smooths the above
/// edge before the directional kernel projects it) than when it is
/// off. For `pAngle = 45` and `w + h = 32` with `filterType = 0` the
/// §7.11.2.9 strength selection picks strength 3 on the above edge
/// (`|pAngle - 90| = 45 >= 32`), and the §7.11.2.10 upsample
/// selection picks 0 (`|delta| >= 40`), so this isolates the edge
/// filter from the upsample path.
#[test]
fn predict_intra_d45_edge_filter_alters_output() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
// Build two identical walkers, seed an alternating (high-
// frequency) above row that the smoothing kernel will visibly
// change, then predict with the filter off vs on.
let seed_above = |walker: &mut PartitionWalker| {
walker.ensure_curr_frame_plane(0, 32, 32);
let buf = walker.curr_frame[0].as_mut().unwrap();
// Row 3 is the row above the TU at start_y = 4. Seed cols
// 0..32 (w + h = 32) with a 0 / 255 checkerboard — maximally
// sensitive to the 5-tap edge filter.
for j in 0..32usize {
buf.samples[3 * 32 + j] = if j % 2 == 0 { 0 } else { 255 };
}
};
let mut walker_off = PartitionWalker::new(8, 8, geom).unwrap();
seed_above(&mut walker_off);
walker_off.predict_intra_into_curr_frame(
0, 0, 4, TX_16X16, D45_PRED, 0, 8, 0, 0, /* have_above */ true,
/* have_left */ false, /* have_above_right */ true,
/* have_below_left */ false, /* enable_intra_edge_filter */ false,
/* filter_type */ 0, /* filter_intra_mode */ None,
);
let mut walker_on = PartitionWalker::new(8, 8, geom).unwrap();
seed_above(&mut walker_on);
walker_on.predict_intra_into_curr_frame(
0, 0, 4, TX_16X16, D45_PRED, 0, 8, 0, 0, /* have_above */ true,
/* have_left */ false, /* have_above_right */ true,
/* have_below_left */ false, /* enable_intra_edge_filter */ true,
/* filter_type */ 0, /* filter_intra_mode */ None,
);
let off = walker_off.curr_frame(0).unwrap();
let on = walker_on.curr_frame(0).unwrap();
// The 16×16 TU lives at rows 4..20, cols 0..16 of the 32-wide
// plane. At least one predicted sample must differ — proving the
// edge filter is applied on the `enable` path and skipped on the
// `!enable` path.
let mut differ = false;
for i in 0..16usize {
for j in 0..16usize {
let idx = (4 + i) * 32 + j;
if off[idx] != on[idx] {
differ = true;
}
}
}
assert!(
differ,
"§7.11.2.4 step-4 edge filter must change at least one D45 sample",
);
// Sanity: every filtered sample stays in the 8-bit Clip1 range.
for i in 0..16usize {
for j in 0..16usize {
let v = on[(4 + i) * 32 + j];
assert!((0..=255).contains(&v), "filtered D45 sample {v} in range");
}
}
}
/// §7.11.2.4 step-4 directional pre-pass — upsample-path wiring
/// check. A `D67_PRED` 4×4 luma TU (`pAngle = 67`, `w + h = 8`)
/// selects the §7.11.2.10 above-edge upsample (`|pAngle - 90| = 23 ∈
/// 1..40` and `w + h = 8 <= 16` with `filterType = 0`) while the
/// §7.11.2.9 strength selection picks 0 (`blk_wh <= 8` needs `d >=
/// 56`), so this isolates the §7.11.2.11 2x upsample from the edge
/// filter. Seeding a non-flat above row, the upsampled (enable-on)
/// prediction must differ from the un-upsampled (enable-off) one.
#[test]
fn predict_intra_d67_upsample_alters_output() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let seed_above = |walker: &mut PartitionWalker| {
walker.ensure_curr_frame_plane(0, 16, 16);
let buf = walker.curr_frame[0].as_mut().unwrap();
// Row 3 is the row above the TU at start_y = 4. Seed cols
// 0..8 (w + h = 8) with a rising ramp the 2x upsample's
// 4-tap interpolation visibly re-samples.
for j in 0..8usize {
buf.samples[3 * 16 + j] = (30 * j) as i32;
}
};
let mut walker_off = PartitionWalker::new(4, 4, geom).unwrap();
seed_above(&mut walker_off);
walker_off.predict_intra_into_curr_frame(
0, 0, 4, TX_4X4, D67_PRED, 0, 8, 0, 0, /* have_above */ true,
/* have_left */ false, /* have_above_right */ true,
/* have_below_left */ false, /* enable_intra_edge_filter */ false,
/* filter_type */ 0, /* filter_intra_mode */ None,
);
let mut walker_on = PartitionWalker::new(4, 4, geom).unwrap();
seed_above(&mut walker_on);
walker_on.predict_intra_into_curr_frame(
0, 0, 4, TX_4X4, D67_PRED, 0, 8, 0, 0, /* have_above */ true,
/* have_left */ false, /* have_above_right */ true,
/* have_below_left */ false, /* enable_intra_edge_filter */ true,
/* filter_type */ 0, /* filter_intra_mode */ None,
);
let off = walker_off.curr_frame(0).unwrap();
let on = walker_on.curr_frame(0).unwrap();
// The 4×4 TU lives at rows 4..8, cols 0..4 of the 16-wide plane.
let mut differ = false;
for i in 0..4usize {
for j in 0..4usize {
let idx = (4 + i) * 16 + j;
if off[idx] != on[idx] {
differ = true;
}
let v = on[idx];
assert!((0..=255).contains(&v), "upsampled D67 sample {v} in range");
}
}
assert!(
differ,
"§7.11.2.4 step-4 above-edge upsample must change at least one D67 sample",
);
}
/// `Clip1` envelope at 8-bit (av1-spec §3 / line 16339): a
/// residual that drives the sum past `(1 << BitDepth) - 1 = 255`
/// clips to 255; a sum below `0` clips to `0`. The merge is
/// additive against the existing `CurrFrame[..]` cell, so we
/// pre-stamp 200 into the (0, 0) cell, then merge a residual of
/// `+200` (would yield 400) — expect 255 after clip.
#[test]
fn curr_frame_step3_merge_clip1_8bit_saturation() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// Allocate then pre-fill the (0, 0) cell with 200 — simulating
// an upstream §7.11.x prediction sample already in the buffer.
walker.ensure_curr_frame_plane(0, 16, 16);
walker.curr_frame[0].as_mut().unwrap().samples[0] = 200;
walker.curr_frame[0].as_mut().unwrap().samples[1] = 100;
// 4×4 residual: (+200, -150, 0, ...) — first cell clips high,
// second cell clips low (100 + (-150) = -50 ⇒ 0).
let mut residual = vec![0i64; 16];
residual[0] = 200;
residual[1] = -150;
walker.curr_frame_step3_merge(
0, 0, 0, TX_4X4, DCT_DCT, &residual, /* bit_depth = */ 8, 0, 0,
);
let buf = walker.curr_frame(0).unwrap();
assert_eq!(buf[0], 255, "200 + 200 = 400 clipped to 255");
assert_eq!(buf[1], 0, "100 + (-150) = -50 clipped to 0");
}
/// `Clip1` envelope at 10-bit: the upper bound becomes `(1 << 10)
/// - 1 = 1023`. Pre-stamp 800, merge +500 → 1300 clips to 1023.
#[test]
fn curr_frame_step3_merge_clip1_10bit_saturation() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
walker.ensure_curr_frame_plane(0, 16, 16);
walker.curr_frame[0].as_mut().unwrap().samples[0] = 800;
let mut residual = vec![0i64; 16];
residual[0] = 500;
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, DCT_DCT, &residual, 10, 0, 0);
let buf = walker.curr_frame(0).unwrap();
assert_eq!(buf[0], 1023, "800 + 500 = 1300 clipped to 1023 (10-bit)");
}
/// §7.12.3 step-3 `flipUD = 1` derivation (av1-spec lines
/// 16292-16293): `PlaneTxType ∈ { FLIPADST_DCT, FLIPADST_ADST,
/// V_FLIPADST, FLIPADST_FLIPADST }`. Verify by merging a 4×4
/// residual whose row index encodes the source row — the
/// FLIPADST_DCT path lands row `i` at destination row `h - i - 1
/// = 3 - i`.
#[test]
fn curr_frame_step3_merge_flipadst_dct_flips_rows() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// Residual: row i carries value 10 + i (constant across the
// row). Source rows 0..3 → values 10, 11, 12, 13.
let mut residual = vec![0i64; 16];
for i in 0..4 {
for j in 0..4 {
residual[i * 4 + j] = (10 + i) as i64;
}
}
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, FLIPADST_DCT, &residual, 8, 0, 0);
let buf = walker.curr_frame(0).unwrap();
// Destination row r reads the source row `3 - r`. So dst row
// 0 ⇐ src row 3 = 13; dst row 1 ⇐ src row 2 = 12; etc.
for r in 0..4 {
for c in 0..4 {
let expected = (10 + (3 - r)) as i32;
assert_eq!(
buf[r * 16 + c],
expected,
"FLIPADST_DCT row-flip at dst ({r}, {c}): expect src row {} = {expected}",
3 - r,
);
}
}
}
/// §7.12.3 step-3 `flipLR = 1` derivation (av1-spec lines
/// 16295-16296): `PlaneTxType ∈ { DCT_FLIPADST, ADST_FLIPADST,
/// H_FLIPADST, FLIPADST_FLIPADST }`. Verify the H_FLIPADST path
/// mirrors per-column (dst col `c` ⇐ src col `w - c - 1`).
#[test]
fn curr_frame_step3_merge_h_flipadst_flips_cols() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// Residual: col j carries value 10 + j (constant down the
// col). Source cols 0..3 → values 10, 11, 12, 13.
let mut residual = vec![0i64; 16];
for i in 0..4 {
for j in 0..4 {
residual[i * 4 + j] = (10 + j) as i64;
}
}
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, H_FLIPADST, &residual, 8, 0, 0);
let buf = walker.curr_frame(0).unwrap();
for r in 0..4 {
for c in 0..4 {
let expected = (10 + (3 - c)) as i32;
assert_eq!(
buf[r * 16 + c],
expected,
"H_FLIPADST col-flip at dst ({r}, {c}): expect src col {} = {expected}",
3 - c,
);
}
}
}
/// §7.12.3 step-3 `FLIPADST_FLIPADST` ⇒ both `flipUD` and
/// `flipLR` fire. Dst cell at row r, col c reads src cell at row
/// `h - r - 1`, col `w - c - 1`. With a 4x4 residual whose row-
/// major linear index equals the cell value (so src top-left
/// holds zero and src bottom-right holds fifteen), the dst top-
/// left reads fifteen and the dst bottom-right reads zero.
#[test]
fn curr_frame_step3_merge_flipadst_flipadst_180_rotation() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let residual: Vec<i64> = (0..16).map(|k| k as i64).collect();
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, FLIPADST_FLIPADST, &residual, 8, 0, 0);
let buf = walker.curr_frame(0).unwrap();
// dst (r, c) = src (3 - r, 3 - c) ⇒ src linear index (3 - r)
// * 4 + (3 - c) = 12 - 4r + 3 - c = 15 - 4r - c.
for r in 0..4 {
for c in 0..4 {
let expected = (15 - 4 * r - c) as i32;
assert_eq!(
buf[r * 16 + c],
expected,
"FLIPADST_FLIPADST 180° at ({r}, {c})"
);
}
}
}
/// `start_x` / `start_y` offsets place the merge at an arbitrary
/// top-left within the per-plane buffer. The merge for a 4×4 TU
/// at `(start_y = 4, start_x = 8)` writes the top-left at row 4,
/// col 8 of a 16×16 luma buffer. Untouched cells outside the 4×4
/// footprint stay at 0.
#[test]
fn curr_frame_step3_merge_offset_top_left_within_buffer() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let residual = vec![7i64; 16]; // 4×4, all 7s.
walker.curr_frame_step3_merge(
0, /* start_x = */ 8, /* start_y = */ 4, TX_4X4, DCT_DCT, &residual, 8, 0, 0,
);
let buf = walker.curr_frame(0).unwrap();
for r in 0..16 {
for c in 0..16 {
let expected = if (4..8).contains(&r) && (8..12).contains(&c) {
7
} else {
0
};
assert_eq!(buf[r * 16 + c], expected, "offset merge at ({r}, {c})");
}
}
}
/// Multiple consecutive merges accumulate (additive — §7.12.3
/// step-3's `CurrFrame[..] = Clip1(CurrFrame[..] + Residual[..])`
/// reads the pre-write cell value and adds). Two `+3` merges at
/// the same top-left yield `6` at every touched cell.
#[test]
fn curr_frame_step3_merge_is_additive_across_calls() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let residual = vec![3i64; 16];
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, DCT_DCT, &residual, 8, 0, 0);
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, DCT_DCT, &residual, 8, 0, 0);
let buf = walker.curr_frame(0).unwrap();
for i in 0..4 {
for j in 0..4 {
assert_eq!(buf[i * 16 + j], 6, "two +3 merges at ({i}, {j})");
}
}
}
/// Out-of-buffer overhang (start_x + w > cols) is clipped
/// silently; the in-buffer slice is still merged. Position a 4×4
/// TU at start_x = 14 in a 16-wide luma buffer — cells with
/// dst_x ∈ {14, 15} land; dst_x ∈ {16, 17} drop. Verify the two
/// in-buffer columns received the merge and the rest stay at 0.
#[test]
fn curr_frame_step3_merge_silently_clips_overhang() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let residual = vec![5i64; 16];
walker.curr_frame_step3_merge(
0, /* start_x = */ 14, /* start_y = */ 0, TX_4X4, DCT_DCT, &residual, 8, 0, 0,
);
let buf = walker.curr_frame(0).unwrap();
for r in 0..4 {
// dst_x = 14, 15 in-buffer; the rest out-of-buffer.
assert_eq!(buf[r * 16 + 14], 5);
assert_eq!(buf[r * 16 + 15], 5);
}
}
/// `curr_frame_step3_merge` returns silently on `plane >= 3` —
/// defensive caller-bug guard. The luma buffer stays
/// unallocated, no panic.
#[test]
fn curr_frame_step3_merge_plane_out_of_range_is_no_op() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let residual = vec![1i64; 16];
walker.curr_frame_step3_merge(3, 0, 0, TX_4X4, DCT_DCT, &residual, 8, 0, 0);
for plane in 0..3 {
assert!(walker.curr_frame(plane).is_none());
}
}
/// `curr_frame_step3_merge` returns silently on a wrong-sized
/// residual buffer — defensive caller-bug guard.
#[test]
fn curr_frame_step3_merge_wrong_residual_size_is_no_op() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// TX_4X4 expects w*h = 16; pass 8.
let residual = vec![1i64; 8];
walker.curr_frame_step3_merge(0, 0, 0, TX_4X4, DCT_DCT, &residual, 8, 0, 0);
assert!(walker.curr_frame(0).is_none());
}
/// Chroma plane allocation uses subsampled dimensions. A 4×4
/// chroma TU on a 4:2:0 stream with luma `MiRows * MI_SIZE = 16`
/// sees `curr_frame_dims(1) = (8, 8)`.
#[test]
fn curr_frame_step3_merge_chroma_420_subsampled_dims() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let residual = vec![2i64; 16];
walker.curr_frame_step3_merge(
/* plane = */ 1, 0, 0, TX_4X4, DCT_DCT, &residual, 8, /* sub_x = */ 1,
/* sub_y = */ 1,
);
assert_eq!(walker.curr_frame_dims(1), Some((8, 8)));
let chroma = walker.curr_frame(1).unwrap();
assert_eq!(chroma.len(), 64);
// Top-left 4×4 has 2s, rest 0.
for r in 0..8 {
for c in 0..8 {
let expected = if r < 4 && c < 4 { 2 } else { 0 };
assert_eq!(chroma[r * 8 + c], expected, "chroma 4:2:0 at ({r}, {c})");
}
}
// Luma + V plane stay unallocated.
assert!(walker.curr_frame(0).is_none());
assert!(walker.curr_frame(2).is_none());
}
/// `ResidualTuTask` `plane_tx_type` field is back-filled from the
/// §5.11.40 `compute_tx_type` derivation on the `!skip` arm.
/// Drive the §5.11.5 walker's reachable path (all-zero CDFs ⇒
/// `coeffs.eob = 0`, no inverse transform, no merge) and verify
/// the tasks carry `plane_tx_type = DCT_DCT` (the `Lossless ||
/// txSzSqrUp > TX_32X32` fallback for the walker's intra-only
/// path with `YMode = DC_PRED`).
#[test]
fn residual_tu_task_plane_tx_type_back_filled_on_walker_path() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
// Force §5.11.39 `all_zero` S() = 1 ⇒ eob = 0 per TU.
for sz in 0..TX_SIZES {
for ctx in 0..TXB_SKIP_CONTEXTS {
cdfs.txb_skip[sz][ctx] = rig_cdf::<3>(1);
}
}
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r = walker
.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
/* has_chroma = */ true,
0,
0,
/* is_inter = */ false,
/* lossless = */ false,
/* skip = */ false,
TX_8X8,
false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
// All three TUs (Y / U / V) ran the §5.11.40 derivation ⇒
// plane_tx_type back-filled. Walker's reachable intra-only
// path with YMode = DC_PRED yields DCT_DCT.
assert_eq!(r.tasks.len(), 3);
for t in &r.tasks {
assert_eq!(
t.plane_tx_type, DCT_DCT as u8,
"plane_tx_type back-fill at plane {}",
t.plane,
);
}
// r342: the §7.11.2.1 intra-prediction write now runs before
// the §5.11.39 coeff read on every intra TU. Every TU is
// all-zero so the §7.12.3 step-3 residual merge contributes
// nothing, but the DC_PRED prediction (no neighbours ⇒
// `1 << (BitDepth - 1) = 128`) has already populated CurrFrame.
// So each plane buffer is allocated and uniformly 128 over the
// predicted TU footprint.
for plane in 0..3 {
let buf = walker
.curr_frame(plane)
.unwrap_or_else(|| panic!("intra prediction allocates plane {plane}"));
assert_eq!(buf[0], 128, "DC_PRED no-neighbour default on plane {plane}");
}
}
/// Skip-arm `ResidualTuTask` carries the DCT_DCT placeholder for
/// `plane_tx_type` — the §5.11.34 `if ( !skip )` gate short-
/// circuits the §5.11.40 derivation. The §5.11.35 `if ( !is_inter )`
/// intra-prediction write (r342) is **outside** the `!skip` gate, so
/// it still fires on a skipped intra block: CurrFrame is populated
/// with the DC_PRED no-neighbour default even though no residual is
/// read.
#[test]
fn residual_tu_task_skip_arm_plane_tx_type_is_dct_dct_placeholder() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 16,
mi_col_start: 0,
mi_col_end: 16,
};
let mut walker = PartitionWalker::new(16, 16, geom).unwrap();
let mut cdfs = TileCdfContext::new_from_defaults();
let bytes = [0u8; 16];
let mut dec = SymbolDecoder::init_symbol(&bytes, 16, true).unwrap();
let r = walker
.residual(
&mut dec,
&mut cdfs,
0,
0,
BLOCK_8X8,
true,
0,
0,
false,
false,
/* skip = */ true,
TX_8X8,
false,
&ResidualContext::neutral(0, 8),
)
.unwrap();
assert_eq!(r.tasks.len(), 3);
for t in &r.tasks {
assert_eq!(t.plane_tx_type, DCT_DCT as u8);
}
// r342: intra prediction runs ahead of the skip gate, so every
// plane is allocated and DC-filled (128, no neighbours).
for plane in 0..3 {
let buf = walker
.curr_frame(plane)
.unwrap_or_else(|| panic!("intra prediction allocates plane {plane}"));
assert_eq!(buf[0], 128, "DC_PRED default on skipped plane {plane}");
}
}
// ---------------------------------------------------------------
// Round 188 — §7.11.2.4 D-mode directional intra prediction tests.
// ---------------------------------------------------------------
/// r188: `Mode_To_Angle[ INTRA_MODES ]` length + non-directional
/// entries are 0 + the seven directional entries match the
/// av1-spec p.485 transcription.
#[test]
fn mode_to_angle_table_matches_spec_transcription() {
assert_eq!(MODE_TO_ANGLE.len(), INTRA_MODES);
assert_eq!(MODE_TO_ANGLE[DC_PRED], 0);
assert_eq!(MODE_TO_ANGLE[V_PRED], 90);
assert_eq!(MODE_TO_ANGLE[H_PRED], 180);
assert_eq!(MODE_TO_ANGLE[D45_PRED], 45);
assert_eq!(MODE_TO_ANGLE[D135_PRED], 135);
assert_eq!(MODE_TO_ANGLE[D113_PRED], 113);
assert_eq!(MODE_TO_ANGLE[D157_PRED], 157);
assert_eq!(MODE_TO_ANGLE[D203_PRED], 203);
assert_eq!(MODE_TO_ANGLE[D67_PRED], 67);
assert_eq!(MODE_TO_ANGLE[SMOOTH_PRED], 0);
assert_eq!(MODE_TO_ANGLE[SMOOTH_V_PRED], 0);
assert_eq!(MODE_TO_ANGLE[SMOOTH_H_PRED], 0);
assert_eq!(MODE_TO_ANGLE[PAETH_PRED], 0);
}
/// r188: `Dr_Intra_Derivative[ 90 ]` length + the 27 non-zero
/// transcription pins per av1-spec p.487. Every other slot is `0`.
#[test]
fn dr_intra_derivative_table_matches_spec_transcription() {
assert_eq!(DR_INTRA_DERIVATIVE.len(), 90);
let nonzero = [
(3, 1023),
(6, 547),
(9, 372),
(14, 273),
(17, 215),
(20, 178),
(23, 151),
(26, 132),
(29, 116),
(32, 102),
(36, 90),
(39, 80),
(42, 71),
(45, 64),
(48, 57),
(51, 51),
(54, 45),
(58, 40),
(61, 35),
(64, 31),
(67, 27),
(70, 23),
(73, 19),
(76, 15),
(81, 11),
(84, 7),
(87, 3),
];
let nonzero_idx: std::collections::HashSet<usize> =
nonzero.iter().map(|&(i, _)| i).collect();
for &(i, v) in &nonzero {
assert_eq!(DR_INTRA_DERIVATIVE[i], v, "Dr_Intra_Derivative[{i}]");
}
for (i, &v) in DR_INTRA_DERIVATIVE.iter().enumerate() {
if !nonzero_idx.contains(&i) {
assert_eq!(v, 0, "Dr_Intra_Derivative[{i}] must be 0");
}
}
}
/// r188: `Intra_Edge_Kernel[ 3 ][ 5 ]` transcription pin per av1-
/// spec p.256. Three rows: `[0, 4, 8, 4, 0]`, `[0, 5, 6, 5, 0]`,
/// `[2, 4, 4, 4, 2]`. Each row sums to 16 (so `(s + 8) >> 4`
/// preserves DC).
#[test]
fn intra_edge_kernel_transcription_pins() {
assert_eq!(INTRA_EDGE_KERNEL[0], [0, 4, 8, 4, 0]);
assert_eq!(INTRA_EDGE_KERNEL[1], [0, 5, 6, 5, 0]);
assert_eq!(INTRA_EDGE_KERNEL[2], [2, 4, 4, 4, 2]);
for (k, row) in INTRA_EDGE_KERNEL.iter().enumerate() {
let sum: i32 = row.iter().sum();
assert_eq!(sum, 16, "row {k} DC-preserving sum");
}
}
/// r188: §7.11.2.7 `filter_corner` averages
/// `(5 * 100 + 6 * 100 + 5 * 100 + 8) >> 4 = (1608) >> 4 = 100`
/// for a constant input (the constant-DC identity within rounding).
#[test]
fn filter_corner_constant_input_is_dc_preserving() {
assert_eq!(filter_corner(100, 100, 100), 100);
assert_eq!(filter_corner(0, 0, 0), 0);
assert_eq!(filter_corner(255, 255, 255), 255);
}
/// r188: §7.11.2.7 `filter_corner` mid-point hand-trace.
/// `5 * 50 + 6 * 200 + 5 * 50 = 250 + 1200 + 250 = 1700`,
/// `(1700 + 8) >> 4 = 1708 / 16 = 106`.
#[test]
fn filter_corner_hand_trace() {
assert_eq!(filter_corner(50, 200, 50), 106);
}
/// r188: §7.11.2.10 `intra_edge_upsample_selection`. `|delta| <= 0`
/// or `|delta| >= 40` ⇒ `0`. Otherwise filter_type == 0 ⇒
/// `w + h <= 16`; filter_type == 1 ⇒ `w + h <= 8`.
#[test]
fn intra_edge_upsample_selection_pin() {
// |delta| == 0 ⇒ 0 (degenerate-axis: V_PRED / H_PRED handled
// by dedicated leaves).
assert_eq!(intra_edge_upsample_selection(4, 4, 0, 0), 0);
// |delta| == 40 ⇒ 0 (boundary).
assert_eq!(intra_edge_upsample_selection(4, 4, 0, 40), 0);
assert_eq!(intra_edge_upsample_selection(4, 4, 0, -40), 0);
// filter_type 0, w+h = 8, |delta| = 20 (in range) ⇒ 1.
assert_eq!(intra_edge_upsample_selection(4, 4, 0, 20), 1);
// filter_type 0, w+h = 16 ⇒ 1 (boundary).
assert_eq!(intra_edge_upsample_selection(8, 8, 0, 20), 1);
// filter_type 0, w+h = 17 ⇒ 0 (above bound).
assert_eq!(intra_edge_upsample_selection(8, 9, 0, 20), 0);
// filter_type 1, w+h = 8 ⇒ 1.
assert_eq!(intra_edge_upsample_selection(4, 4, 1, 20), 1);
// filter_type 1, w+h = 9 ⇒ 0.
assert_eq!(intra_edge_upsample_selection(4, 5, 1, 20), 0);
}
/// r188: §7.11.2.9 `intra_edge_filter_strength_selection` truth
/// table. Spot-checks each of the per-(filter_type, blkWh) branches
/// per av1-spec p.253.
#[test]
fn intra_edge_filter_strength_selection_pin() {
// filter_type 0, blkWh = 8 ⇒ strength 1 only when |d| >= 56.
assert_eq!(intra_edge_filter_strength_selection(4, 4, 0, 0), 0);
assert_eq!(intra_edge_filter_strength_selection(4, 4, 0, 56), 1);
assert_eq!(intra_edge_filter_strength_selection(4, 4, 0, -56), 1);
// filter_type 0, blkWh = 12 ⇒ strength 1 when |d| >= 40.
assert_eq!(intra_edge_filter_strength_selection(4, 8, 0, 40), 1);
assert_eq!(intra_edge_filter_strength_selection(4, 8, 0, 39), 0);
// filter_type 0, blkWh = 24 ⇒ {1, 2, 3} ladder.
assert_eq!(intra_edge_filter_strength_selection(8, 16, 0, 8), 1);
assert_eq!(intra_edge_filter_strength_selection(8, 16, 0, 16), 2);
assert_eq!(intra_edge_filter_strength_selection(8, 16, 0, 32), 3);
// filter_type 0, blkWh > 32 ⇒ always 3.
assert_eq!(intra_edge_filter_strength_selection(32, 32, 0, 0), 3);
// filter_type 1, blkWh = 8 ⇒ |d| >= 40 ⇒ 1; |d| >= 64 ⇒ 2.
assert_eq!(intra_edge_filter_strength_selection(4, 4, 1, 0), 0);
assert_eq!(intra_edge_filter_strength_selection(4, 4, 1, 40), 1);
assert_eq!(intra_edge_filter_strength_selection(4, 4, 1, 64), 2);
// filter_type 1, blkWh > 24 ⇒ always 3.
assert_eq!(intra_edge_filter_strength_selection(16, 16, 1, 0), 3);
}
/// r188: §7.11.2.12 `intra_edge_filter` strength == 0 is a no-op
/// (the spec's `if (strength == 0) return` short-circuit).
#[test]
fn intra_edge_filter_strength_zero_is_no_op() {
// Layout: [index -2, index -1, index 0, index 1, ..., index 7].
// sz = 5 (a 5-sample edge); buf length needs >= sz + 1 = 6.
let mut buf = vec![0u16, 100, 10, 20, 30, 40, 50];
let before = buf.clone();
intra_edge_filter(5, 0, &mut buf).unwrap();
assert_eq!(buf, before);
}
/// r188: §7.11.2.12 `intra_edge_filter` on constant input preserves
/// the constant (each kernel row sums to 16, so `(16 * c + 8) >>
/// 4 = c`). The output at offset `i + 1` (= spec's `buf[i - 1]`)
/// equals `c` for `i = 1..sz` after the filter; the index `-1` /
/// `-2` head slots are untouched.
#[test]
fn intra_edge_filter_constant_input_dc_preserving() {
let sz: usize = 8;
for strength in 1..=3u8 {
// Constant edge of `50` across `index 0 .. sz - 1`.
let mut buf = vec![50u16; sz + 1];
// Head slot index -1 = offset 1.
buf[0] = 50; // index -2 (untouched)
buf[1] = 50; // index -1 (untouched)
intra_edge_filter(sz, strength, &mut buf).unwrap();
for (i, &v) in buf.iter().enumerate().skip(1) {
assert_eq!(v, 50, "strength {strength} offset {i}");
}
}
}
/// r188: §7.11.2.11 `intra_edge_upsample` on constant input
/// preserves the constant (the 4-tap kernel `(-1, 9, 9, -1) / 16`
/// sums to 16 in the inner term, so a constant `c` produces
/// `((−c + 9c + 9c − c) + 8) >> 4 = (16c + 8) >> 4 = c` on every
/// upsampled slot).
#[test]
fn intra_edge_upsample_constant_input_dc_preserving() {
let num_px: usize = 4;
// Layout: [-2, -1, 0, 1, 2, 3, _ , _ , _ , _ ] — buf needs
// 2 * num_px + 2 = 10 slots for output.
let mut buf = vec![25u16; 2 * num_px + 2];
intra_edge_upsample(num_px, 8, &mut buf).unwrap();
// After upsample valid range is -2..2 * num_px - 2; in our
// offset convention this is offsets 0..2*num_px. Every cell
// should still be 25.
for (i, &v) in buf.iter().take(2 * num_px).enumerate() {
assert_eq!(v, 25, "offset {i} after upsample");
}
}
/// r188: §7.11.2.4 step 7 `predict_intra_directional` on
/// `D45_PRED` (`pAngle == 45`) with a constant `AboveRow`
/// produces a constant block (the per-cell `(32 - shift) * c +
/// shift * c = 32 * c` collapses to `Round2(32 * c, 5) = c`).
#[test]
fn directional_d45_constant_above_produces_constant() {
// 4×4 block; we read up to base = (i + 1) * dx >> 6 + j with
// dx = Dr_Intra_Derivative[45] = 64 and i ∈ 0..3, j ∈ 0..3.
// Max base index = ((3+1)*64 >> 6) + 3 = 4 + 3 = 7.
// (w + h - 1) = 7, so max_base_x = 7. Need AboveRow[0..=7]
// populated. Layout: [-2, -1, 0, 1, …, 7] (10 entries).
let above: Vec<u16> = vec![77u16; 10];
let left: Vec<u16> = vec![77u16; 10];
let mut pred = [0u16; 16];
predict_intra_directional(4, 4, 45, 0, 0, &above, &left, &mut pred).unwrap();
for (k, &v) in pred.iter().enumerate() {
assert_eq!(v, 77, "D45 cell {k}");
}
}
/// r188: §7.11.2.4 step 8 `predict_intra_directional` on
/// `D135_PRED` (`pAngle == 135`) with constant neighbours produces
/// a constant block — both the above-axis and the left-axis fall-
/// back arms blend `c * 32 / 32 = c`.
#[test]
fn directional_d135_constant_neighbours_produce_constant() {
let above: Vec<u16> = vec![123u16; 10];
let left: Vec<u16> = vec![123u16; 10];
let mut pred = [0u16; 16];
predict_intra_directional(4, 4, 135, 0, 0, &above, &left, &mut pred).unwrap();
for &v in pred.iter() {
assert_eq!(v, 123);
}
}
/// r188: §7.11.2.4 step 9 `predict_intra_directional` on
/// `D203_PRED` (`pAngle == 203`) with constant `LeftCol` produces
/// a constant block — the step-9 mirror of D45.
#[test]
fn directional_d203_constant_left_produces_constant() {
let above: Vec<u16> = vec![200u16; 10];
let left: Vec<u16> = vec![200u16; 10];
let mut pred = [0u16; 16];
predict_intra_directional(4, 4, 203, 0, 0, &above, &left, &mut pred).unwrap();
for &v in pred.iter() {
assert_eq!(v, 200);
}
}
/// r188: §7.11.2.4 step 7 D45 — when `AboveRow` is monotone
/// (ramp `j -> j * 10`), the prediction copies `AboveRow` along
/// the 45° diagonal: `pred[0][0]` reads near `AboveRow[1]`,
/// `pred[1][0]` reads near `AboveRow[2]`, etc. With `dx = 64`,
/// `idx = (i + 1) * 64`, `base = idx >> 6 = i + 1`, `shift = 0`
/// (so the upper sample dominates). `pred[i][j] = AboveRow[i + 1 +
/// j]` for `base < max_base_x = w + h - 1 = 7`. Beyond that the
/// `AboveRow[7]` ceiling pin fires.
#[test]
fn directional_d45_ramp_above_diagonal_copy() {
// Layout: [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7]; offset i + 2 holds
// index i. AboveRow[i] = (i + 1) * 10 for i ∈ 0..=7.
let mut above = vec![0u16; 10];
for i in 0..=7i32 {
above[(i + 2) as usize] = ((i + 1) * 10) as u16;
}
let left = vec![0u16; 10];
let mut pred = [0u16; 16];
predict_intra_directional(4, 4, 45, 0, 0, &above, &left, &mut pred).unwrap();
// pred[i][j] = AboveRow[i + 1 + j] = (i + 2 + j) * 10 when in
// range; clamps to AboveRow[7] = 80 when base >= 7.
for i in 0..4 {
for j in 0..4 {
let base = (i + 1) + j;
let expected = if base < 7 {
((base + 1) * 10) as u16
} else {
80
};
assert_eq!(pred[i * 4 + j], expected, "i={i} j={j}");
}
}
}
/// r188: `predict_intra_d_mode` ordinal-to-angle dispatch. Each
/// of the six D-modes routes through `MODE_TO_ANGLE[ mode ] + 0 *
/// ANGLE_STEP` (the r188 walker emits `angleDelta == 0`).
#[test]
fn predict_intra_d_mode_constant_neighbours_match_directional() {
let above: Vec<u16> = vec![64u16; 10];
let left: Vec<u16> = vec![64u16; 10];
for &m in &[
D45_PRED, D135_PRED, D113_PRED, D157_PRED, D203_PRED, D67_PRED,
] {
let mut pred = [0u16; 16];
predict_intra_d_mode(m, 0, 4, 4, 0, 0, &above, &left, &mut pred).unwrap();
for &v in pred.iter() {
assert_eq!(v, 64, "mode {m}");
}
}
}
/// r188: `predict_intra_d_mode` caller-bug guards — out-of-range
/// mode + out-of-range angle_delta + degenerate `V_PRED` /
/// `H_PRED` rejection.
#[test]
fn predict_intra_d_mode_caller_bug_guards() {
let above = vec![0u16; 10];
let left = vec![0u16; 10];
let mut pred = [0u16; 16];
// Below D45_PRED (the V_PRED degenerate, handled elsewhere).
assert_eq!(
predict_intra_d_mode(V_PRED, 0, 4, 4, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
// Above D67_PRED (the SMOOTH_PRED ordinal).
assert_eq!(
predict_intra_d_mode(SMOOTH_PRED, 0, 4, 4, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
// angle_delta out of range.
assert_eq!(
predict_intra_d_mode(D45_PRED, 4, 4, 4, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
predict_intra_d_mode(D45_PRED, -4, 4, 4, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
/// r188: `predict_intra_directional` caller-bug guards.
#[test]
fn predict_intra_directional_caller_bug_guards() {
let above = vec![0u16; 16];
let left = vec![0u16; 16];
let mut pred = [0u16; 16];
// p_angle == 90 (V_PRED degenerate) is rejected — caller must
// route through predict_intra_v_pred.
assert_eq!(
predict_intra_directional(4, 4, 90, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
// p_angle == 180 (H_PRED degenerate).
assert_eq!(
predict_intra_directional(4, 4, 180, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
// p_angle out of range.
assert_eq!(
predict_intra_directional(4, 4, -1, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
predict_intra_directional(4, 4, 360, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
// w / h out of range.
assert_eq!(
predict_intra_directional(0, 4, 45, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
predict_intra_directional(4, 65, 45, 0, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
// upsample flag out of range.
assert_eq!(
predict_intra_directional(4, 4, 45, 2, 0, &above, &left, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
);
// pred too short.
let mut tiny = [0u16; 8];
assert_eq!(
predict_intra_directional(4, 4, 45, 0, 0, &above, &left, &mut tiny),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
/// r188: `intra_edge_filter` caller-bug guards — `sz > 129` /
/// `strength > 3` / short buffer.
#[test]
fn intra_edge_filter_caller_bug_guards() {
let mut buf = vec![0u16; 8];
assert_eq!(
intra_edge_filter(130, 1, &mut buf),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
intra_edge_filter(4, 4, &mut buf),
Err(crate::Error::PartitionWalkOutOfRange)
);
let mut tiny = vec![0u16; 2];
assert_eq!(
intra_edge_filter(8, 1, &mut tiny),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
/// r188: `intra_edge_upsample` caller-bug guards — `num_px == 0` /
/// `bit_depth` out of range / short buffer.
#[test]
fn intra_edge_upsample_caller_bug_guards() {
let mut buf = vec![0u16; 10];
assert_eq!(
intra_edge_upsample(0, 8, &mut buf),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
intra_edge_upsample(4, 7, &mut buf),
Err(crate::Error::PartitionWalkOutOfRange)
);
assert_eq!(
intra_edge_upsample(4, 13, &mut buf),
Err(crate::Error::PartitionWalkOutOfRange)
);
let mut tiny = vec![0u16; 4];
assert_eq!(
intra_edge_upsample(4, 8, &mut tiny),
Err(crate::Error::PartitionWalkOutOfRange)
);
}
// -----------------------------------------------------------------
// r284 — §8.3.2 `all_zero` / `dc_sign` ctx derivations + the
// §5.11.39 tail stamps / §5.11.42 reset / §6.10.2 clears over the
// `Above{Level,Dc}Context` / `Left{Level,Dc}Context` arrays.
// -----------------------------------------------------------------
fn txb_ctx_walker(mi_rows: u32, mi_cols: u32) -> PartitionWalker {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
};
PartitionWalker::new(mi_rows, mi_cols, geom).unwrap()
}
/// §8.3.2 luma `all_zero` ladder (av1-spec p.370-371) — every arm,
/// driven by stamping controlled `culLevel` values around a TX_8X8
/// TU inside a BLOCK_16X16 (so `bw != w` keeps the ladder live).
#[test]
fn txb_skip_ctx_luma_ladder_all_arms() {
let mut w = txb_ctx_walker(8, 8);
// Arm 1: TU == plane residual size ⇒ ctx 0 even with stamped
// neighbours.
w.stamp_txb_level_context(0, TX_8X8, 0, 0, 60, 2);
assert_eq!(
w.txb_skip_ctx(0, TX_16X16, BLOCK_16X16, 2, 2, 0, 0),
Some(0),
"bw == w && bh == h arm ignores neighbours"
);
// Reset to a fresh walker for the neighbour arms; the probe TU
// is TX_8X8 at (x4, y4) = (2, 2) inside BLOCK_16X16.
let probe = |w: &PartitionWalker| w.txb_skip_ctx(0, TX_8X8, BLOCK_16X16, 2, 2, 0, 0);
let mut w = txb_ctx_walker(8, 8);
// Arm 2: top == 0 && left == 0 ⇒ 1.
assert_eq!(probe(&w), Some(1));
// Arm 3a: one side zero, max <= 3 ⇒ 2. Stamp a culLevel = 2
// TX_8X8 column band covering x4 = 2..3.
w.stamp_txb_level_context(0, TX_8X8, 2, 0, 2, 0);
// The stamp also wrote Left rows 0..1 — the probe at y4 = 2
// reads rows 2..3, still zero.
assert_eq!(probe(&w), Some(2));
// Arm 3b: one side zero, max > 3 ⇒ 3.
w.stamp_txb_level_context(0, TX_8X8, 2, 0, 9, 0);
assert_eq!(probe(&w), Some(3));
// Arm 4: both non-zero, max <= 3 ⇒ 4.
let mut w = txb_ctx_walker(8, 8);
w.stamp_txb_level_context(0, TX_8X8, 2, 0, 3, 0); // above cols 2..3
w.stamp_txb_level_context(0, TX_8X8, 0, 2, 1, 0); // left rows 2..3
assert_eq!(probe(&w), Some(4));
// Arm 5: min <= 3 < max ⇒ 5.
w.stamp_txb_level_context(0, TX_8X8, 2, 0, 30, 0);
assert_eq!(probe(&w), Some(5));
// Arm 6: both > 3 ⇒ 6.
w.stamp_txb_level_context(0, TX_8X8, 0, 2, 12, 0);
assert_eq!(probe(&w), Some(6));
}
/// §8.3.2 chroma `all_zero` arm: base 7 + OR-census of level|dc
/// per side + the `bw * bh > w * h` `+= 3` adjustment.
#[test]
fn txb_skip_ctx_chroma_census_and_bsize_adjustment() {
// 4:2:0 BLOCK_16X16 ⇒ chroma residual BLOCK_8X8 (bw = bh = 8).
// A chroma TX_8X8 TU matches it ⇒ no +3; both sides clear ⇒ 7.
let mut w = txb_ctx_walker(8, 8);
assert_eq!(w.txb_skip_ctx(1, TX_8X8, BLOCK_16X16, 0, 0, 1, 1), Some(7));
// Above census fires through the DC array alone (level still
// 0): ctx 8.
w.stamp_txb_level_context(1, TX_8X8, 0, 4, 0, 2);
assert_eq!(w.txb_skip_ctx(1, TX_8X8, BLOCK_16X16, 0, 0, 1, 1), Some(8));
// Left census too (level path): ctx 9.
w.stamp_txb_level_context(1, TX_8X8, 4, 0, 5, 0);
assert_eq!(w.txb_skip_ctx(1, TX_8X8, BLOCK_16X16, 0, 0, 1, 1), Some(9));
// TX_4X4 against the BLOCK_8X8 chroma residual size: 64 > 16
// ⇒ += 3 on top of the census ⇒ 12.
assert_eq!(w.txb_skip_ctx(1, TX_4X4, BLOCK_16X16, 0, 0, 1, 1), Some(12));
// Caller bugs ⇒ None.
assert_eq!(w.txb_skip_ctx(3, TX_4X4, BLOCK_16X16, 0, 0, 1, 1), None);
assert_eq!(
w.txb_skip_ctx(0, TX_SIZES_ALL, BLOCK_16X16, 0, 0, 0, 0),
None
);
assert_eq!(w.txb_skip_ctx(0, TX_4X4, BLOCK_SIZES, 0, 0, 0, 0), None);
}
/// §8.3.2 `dc_sign` ctx (av1-spec p.377): the ±census over
/// `AboveDcContext` / `LeftDcContext` (1 ⇒ −1, 2 ⇒ +1), reduced to
/// 0 (tie) / 1 (negative) / 2 (positive); cells at or past
/// `maxX4` / `maxY4` never vote.
#[test]
fn dc_sign_ctx_census_and_frame_edge_clip() {
let mut w = txb_ctx_walker(4, 4);
assert_eq!(w.dc_sign_ctx(0, TX_8X8, 0, 0, 0, 0), 0, "clean state ties");
// One negative above vote.
w.stamp_txb_level_context(0, TX_4X4, 0, 2, 1, 1);
assert_eq!(w.dc_sign_ctx(0, TX_8X8, 0, 0, 0, 0), 1);
// Two positive left votes outweigh it.
w.stamp_txb_level_context(0, TX_8X8, 2, 0, 1, 2);
assert_eq!(w.dc_sign_ctx(0, TX_8X8, 0, 0, 0, 0), 2);
// Balanced (one −1 above + one +1 above) + clean left ⇒ tie.
let mut w = txb_ctx_walker(4, 4);
w.stamp_txb_level_context(0, TX_4X4, 0, 2, 1, 1);
w.stamp_txb_level_context(0, TX_4X4, 1, 2, 1, 2);
assert_eq!(w.dc_sign_ctx(0, TX_8X8, 0, 0, 0, 0), 0);
// Chroma maxX4 clip: a 4:2:0 chroma TX_8X8 at x4 = 1 spans
// columns 1..2, but maxX4 = 4 >> 1 = 2 — column 2 (stamped
// positive) must not vote.
let mut w = txb_ctx_walker(4, 4);
w.stamp_txb_level_context(1, TX_4X4, 2, 3, 1, 2);
assert_eq!(w.dc_sign_ctx(1, TX_8X8, 1, 0, 1, 1), 0);
}
/// §5.11.42 `reset_block_context` zeroes exactly the footprint
/// (per plane, chroma-halved), and §6.10.2
/// `clear_txb_left_context` zeroes the whole Left pair.
#[test]
fn reset_txb_block_context_footprint_and_left_clear() {
let mut w = txb_ctx_walker(8, 8);
// Stamp every plane's full extent.
for plane in 0..3u8 {
for x4 in 0..8 {
w.stamp_txb_level_context(plane, TX_4X4, x4, x4, 21, 2);
}
}
// Reset a bw4 = bh4 = 4 footprint at (mi_row, mi_col) = (4, 4)
// with 4:2:0 chroma.
w.reset_txb_block_context(4, 4, 4, 4, true, 1, 1);
let alc = w.above_level_context();
let adc = w.above_dc_context();
let llc = w.left_level_context();
// Luma: columns 4..8 zeroed, 0..4 retained.
assert!(alc[0..4].iter().all(|&v| v == 21));
assert!(alc[4..8].iter().all(|&v| v == 0));
assert!(adc[4..8].iter().all(|&v| v == 0));
// Left luma rows (plane base 0): 4..8 zeroed, 0..4 retained.
let llc0 = &llc[0..8];
assert!(llc0[0..4].iter().all(|&v| v == 21));
assert!(llc0[4..8].iter().all(|&v| v == 0));
// Chroma plane 1: subsampled footprint 2..4 zeroed, 0..2
// retained.
let alc1 = &w.above_level_context()[8..16];
assert!(alc1[0..2].iter().all(|&v| v == 21));
assert!(alc1[2..4].iter().all(|&v| v == 0));
assert!(
alc1[4..8].iter().all(|&v| v == 21),
"past footprint retained"
);
// mono-chrome reset leaves chroma planes alone.
let mut w2 = txb_ctx_walker(8, 8);
for plane in 0..3u8 {
w2.stamp_txb_level_context(plane, TX_4X4, 0, 0, 5, 1);
}
w2.reset_txb_block_context(0, 0, 8, 8, false, 1, 1);
assert_eq!(w2.above_level_context()[0], 0, "luma reset");
assert_eq!(w2.above_level_context()[8], 5, "chroma retained");
// §6.10.2 clear_left_context: Left pair + LeftSegPredContext
// zeroed, Above pair retained.
let mut w3 = txb_ctx_walker(8, 8);
for plane in 0..3u8 {
w3.stamp_txb_level_context(plane, TX_4X4, 3, 3, 9, 2);
}
w3.clear_txb_left_context();
assert!(w3.left_level_context().iter().all(|&v| v == 0));
assert!(w3.left_dc_context().iter().all(|&v| v == 0));
assert_eq!(w3.above_level_context()[3], 9, "above retained");
assert_eq!(w3.above_dc_context()[3], 2, "above retained");
}
/// r303 §5.11.33 inter-intra end-to-end: the walker bridge
/// [`PartitionWalker::reconstruct_inter_intra_into_curr_frame`]
/// must drive the §7.11.3.14 blend against the walker's own tracked
/// `CurrFrame[plane]` buffers byte-for-byte identically to the
/// per-block [`crate::reconstruct_inter_block_interintra`] driver —
/// i.e. the walker now reconstructs one inter-intra block's pixels
/// inline, not just surfacing the `is_inter_intra` verdict.
#[test]
fn r303_walker_reconstructs_inter_intra_block_pixels() {
use crate::inter_pred::{EIGHTTAP, II_H_PRED};
use crate::{
reconstruct_inter_block_interintra, InterIntraLeaf, InterIntraModeInfo, PlaneRefSpec,
RefFrameStoreEntry,
};
// §7.11.3.3 reference frame (one plane, 16×16) — caller-owned
// decoder state the parser doesn't track.
let ref_w: usize = 16;
let ref_h: usize = 16;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = ((r * 13 + c * 7) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
const BLOCK_8X8: usize = 3;
let ii_mode = II_H_PRED;
let mv = [4i16, 6];
let curr_w = 8usize;
let curr_h = 8usize;
// The §7.11.2 intra half the §7.12.3 step-3 merge would have
// written into CurrFrame[0] before the §7.11.3.14 blend.
let mut seed = vec![0u16; curr_w * curr_h];
for i in 0..curr_h {
for j in 0..curr_w {
seed[i * curr_w + j] = ((i * 5 + j * 11 + 3) & 0xFF) as u16;
}
}
let leaf = InterIntraLeaf {
mode_info: InterIntraModeInfo {
ref_frame: last,
mv,
interintra_mode: ii_mode,
wedge: None,
},
mi_size: BLOCK_8X8,
mi_row: 0,
mi_col: 0,
interp_filter_x: EIGHTTAP,
interp_filter_y: EIGHTTAP,
};
// Oracle: the per-block driver on the seed (the proven path).
let mut oracle = seed.clone();
reconstruct_inter_block_interintra(
leaf.mode_info,
&ref_frame_idx,
&store,
0,
0,
0,
8,
8,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
None,
/* warp */ None,
&mut oracle,
curr_w,
)
.unwrap();
// The §5.11.33 dispatcher readout for the same luma-only block:
// one intra-half task (mode II_H_PRED → H_PRED) ahead of the
// inter tasks.
let readout = ComputePredictionReadout {
is_inter_intra: true,
is_inter: true,
num_planes_visited: 1,
tasks: vec![PlanePredictionTask {
plane: 0,
start_x: 0,
start_y: 0,
log2_w: 3,
log2_h: 3,
mode: H_PRED as u8, // II_H_PRED → H_PRED
have_left: false,
have_above: false,
}],
};
// Walker with an 8×8 luma frame (2 mi rows/cols × MI_SIZE). The
// walker's curr_frame buffer is the inter-intra blend target.
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let mut walker = PartitionWalker::new(2, 2, geom).unwrap();
// Seed CurrFrame[0] with the intra half (what the §7.12.3
// step-3 merge writes before the blend). Buffer is i32.
walker.ensure_curr_frame_plane(0, curr_h as u32, curr_w as u32);
{
let cp = walker.curr_frame[0].as_mut().unwrap();
assert_eq!(cp.samples.len(), curr_w * curr_h);
for (dst, &src) in cp.samples.iter_mut().zip(seed.iter()) {
*dst = src as i32;
}
}
let plane_refs = [PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
}];
walker
.reconstruct_inter_intra_into_curr_frame(
&readout,
&leaf,
&ref_frame_idx,
8,
&plane_refs,
)
.expect("walker inter-intra reconstruction");
// The walker's CurrFrame[0] now holds the §7.11.3.14-blended
// result, matching the per-block oracle, and differing from the
// seed (the blend actually modified the plane).
let got: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(
got, oracle,
"the walker bridge must reconstruct inter-intra pixels identically to the per-block driver"
);
assert_ne!(got, seed, "the inter-intra blend must modify CurrFrame");
}
/// r303 bridge guards: empty `plane_refs`, an out-of-range plane,
/// and a plane whose `curr_frame` buffer has not been allocated
/// (intra half not yet written) are all caller bugs.
#[test]
fn r303_walker_inter_intra_bridge_guards() {
use crate::inter_pred::{EIGHTTAP, II_H_PRED};
use crate::{InterIntraLeaf, InterIntraModeInfo, PlaneRefSpec, RefFrameStoreEntry};
let refp = vec![0u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let leaf = InterIntraLeaf {
mode_info: InterIntraModeInfo {
ref_frame: last,
mv: [0, 0],
interintra_mode: II_H_PRED,
wedge: None,
},
mi_size: 3, // BLOCK_8X8
mi_row: 0,
mi_col: 0,
interp_filter_x: EIGHTTAP,
interp_filter_y: EIGHTTAP,
};
let readout = ComputePredictionReadout {
is_inter_intra: true,
is_inter: true,
num_planes_visited: 1,
tasks: vec![PlanePredictionTask {
plane: 0,
start_x: 0,
start_y: 0,
log2_w: 3,
log2_h: 3,
mode: H_PRED as u8,
have_left: false,
have_above: false,
}],
};
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
// Empty plane_refs.
let mut w = PartitionWalker::new(2, 2, geom).unwrap();
w.ensure_curr_frame_plane(0, 8, 8);
assert!(matches!(
w.reconstruct_inter_intra_into_curr_frame(&readout, &leaf, &ref_frame_idx, 8, &[]),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Out-of-range plane.
let bad_plane = [PlaneRefSpec {
plane: 3,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
}];
assert!(matches!(
w.reconstruct_inter_intra_into_curr_frame(
&readout,
&leaf,
&ref_frame_idx,
8,
&bad_plane
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Plane with no tracked buffer (intra half not written): a
// fresh walker has no allocated curr_frame[0].
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
let plane_refs = [PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
}];
assert!(matches!(
w2.reconstruct_inter_intra_into_curr_frame(
&readout,
&leaf,
&ref_frame_idx,
8,
&plane_refs
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r304 §7.11.3.1 single-ref end-to-end: the walker bridge
/// [`PartitionWalker::reconstruct_inter_block_into_curr_frame`] must
/// drive the single-reference translational final stitch
/// (`CurrFrame[plane][y+i][x+j] = Clip1(preds[0][i][j])`) into the
/// walker's own tracked `CurrFrame[plane]` buffer byte-for-byte
/// identically to the per-block [`crate::reconstruct_inter_block`]
/// driver — i.e. the walker now reconstructs one plain single-ref
/// inter block's pixels inline (no inter-intra blend). The plane
/// buffer is allocated by the bridge (single-ref overwrites; no
/// intra-half prerequisite).
#[test]
fn r304_walker_reconstructs_single_ref_inter_block_pixels() {
use crate::inter_pred::EIGHTTAP;
use crate::{reconstruct_inter_block, InterModeInfo, PlaneRefSpec, RefFrameStoreEntry};
// §7.11.3.3 reference frame (one plane, 16×16) — caller-owned
// decoder state the parser doesn't track.
let ref_w: usize = 16;
let ref_h: usize = 16;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = ((r * 9 + c * 5 + 1) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let mode_info = InterModeInfo {
ref_frame: last,
mv: [4i16, 6],
};
let curr_w = 8usize;
let curr_h = 8usize;
// The walker allocates an 8×8 luma frame buffer
// (`MiRows × MiCols × MI_SIZE >> 0` = 2 × 4 = 8). Drive the
// per-block oracle into a fresh zero buffer with the SAME stride
// the walker buffer will have (8 columns), so the bridge result
// (which writes into the full-frame-sized buffer) matches the
// `w × h` footprint exactly.
let mut oracle = vec![0u16; curr_w * curr_h];
reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store,
0,
0,
0,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
curr_w,
)
.unwrap();
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let mut walker = PartitionWalker::new(2, 2, geom).unwrap();
// Fresh walker: curr_frame[0] is unallocated. The single-ref
// bridge must allocate it (no intra-half prerequisite).
assert!(walker.curr_frame(0).is_none());
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_block_into_curr_frame(
mode_info,
&ref_frame_idx,
&ref_spec,
0,
0,
curr_w,
curr_h,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker single-ref reconstruction");
// The walker's CurrFrame[0] now holds the single-ref final
// stitch, matching the per-block oracle over the 8×8 footprint.
let (rows, cols) = walker.curr_frame_dims(0).unwrap();
assert_eq!((rows, cols), (curr_h as u32, curr_w as u32));
let got: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(
got, oracle,
"the walker bridge must reconstruct single-ref pixels identically to the per-block driver"
);
// The block actually wrote non-trivial pixels (not all zero).
assert!(
got.iter().any(|&s| s != 0),
"single-ref MC must write pixels"
);
}
/// r346 §5.11.33 **frame-scope** single-ref end-to-end: after the
/// §5.11 syntax walk has stamped the walker's `IsInters[]` /
/// `RefFrames[]` / `Mvs[]` / `InterpFilters[]` / `MiSizes[]` grids for
/// a single-reference translational inter leaf, the frame bridge
/// [`PartitionWalker::reconstruct_inter_frame_into_curr_frame`] must
/// reconstruct that leaf's §7.11.3.1 motion-compensated pixels into the
/// walker's own `CurrFrame[plane]` buffer — byte-for-byte identically
/// to driving the per-block [`crate::reconstruct_inter_block`] oracle.
/// This closes the §5.11.33 frame walk on the single-ref path: the
/// walker reads inter mode-info into the grids and the bridge then
/// reconstructs the inter frame from those grids + a reference store.
#[test]
fn r346_walker_reconstructs_inter_frame_from_grids() {
use crate::inter_pred::EIGHTTAP;
use crate::{reconstruct_inter_block, InterModeInfo, PlaneRefSpec, RefFrameStoreEntry};
// §7.11.3.3 reference frame (one plane, 16×16).
let ref_w: usize = 16;
let ref_h: usize = 16;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = ((r * 7 + c * 3 + 2) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
// 2×2 mi walker → 8×8 luma frame. One BLOCK_8X8 inter leaf covers
// the whole frame.
let curr_w = 8usize;
let curr_h = 8usize;
let mv = [4i16, 6];
// Per-block oracle: drive the single leaf's MC into a fresh zero
// buffer with the walker's eventual 8-column stride.
let mut oracle = vec![0u16; curr_w * curr_h];
reconstruct_inter_block(
InterModeInfo {
ref_frame: last,
mv,
},
&ref_frame_idx,
&store,
0,
0,
0,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
curr_w,
)
.unwrap();
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let mut walker = PartitionWalker::new(2, 2, geom).unwrap();
// Stamp the §5.11 grids as the inter-syntax cascade would for a
// single-ref translational BLOCK_8X8 leaf at (0, 0).
walker.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row: 0,
mi_col: 0,
sub_size: BLOCK_8X8,
skip: 1,
skip_mode: 0,
segment_id: 0,
is_inter: 1,
y_mode: MODE_NEARESTMV,
// §5.11.25 single-ref: slot 0 = LAST_FRAME, slot 1 = NONE.
ref_frame: [last as i8, -1],
mv: [mv[0] as i32, mv[1] as i32],
mv2: [0, 0],
interp_filter: [EIGHTTAP, EIGHTTAP],
motion_mode: MOTION_MODE_SIMPLE,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: None,
tx_size: 0,
// r415 §5.11.29 compound defaults (AVERAGE, pre-set
// comp_group_idx/compound_idx, no side data).
comp_group_idx: 0,
compound_idx: 1,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: 0,
wedge_interintra: 0,
interintra_wedge_index: 0,
});
// Fresh walker: curr_frame[0] is unallocated; the frame bridge
// allocates it (skip-coded leaf — no residual writer).
assert!(walker.curr_frame(0).is_none());
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_frame_into_curr_frame(&ref_frame_idx, 8, &[ref_spec])
.expect("frame-scope single-ref reconstruction");
let (rows, cols) = walker.curr_frame_dims(0).unwrap();
assert_eq!((rows, cols), (curr_h as u32, curr_w as u32));
let got: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(
got, oracle,
"the frame bridge must reconstruct the single-ref inter leaf's pixels \
identically to the per-block driver"
);
assert!(
got.iter().any(|&s| s != 0),
"frame-scope single-ref MC must write pixels"
);
}
/// r412 — [`PartitionWalker::snapshot_encoder_stamp_rect`] /
/// [`PartitionWalker::restore_encoder_stamp_rect`] must round-trip
/// every grid [`PartitionWalker::stamp_encoder_block_syntax`]
/// writes: after a trial stamp of a DIFFERENT leaf over the same
/// footprint, restoring the snapshot returns the mirror to a state
/// whose §7.10.2 `find_mv_stack` output (and raw grid reads) equal
/// the pre-trial ones exactly — the contract the r412 driver-side
/// MV-prediction mirror rests on.
#[test]
fn r412_encoder_stamp_rect_snapshot_restores_mv_mirror_state() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 8,
mi_col_start: 0,
mi_col_end: 8,
};
let mut walker = PartitionWalker::new(8, 8, geom).unwrap();
// A committed inter neighbour ABOVE the trial rect (row 0-1)
// so find_mv_stack has a real §7.10.2.2 row-scan candidate.
walker.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row: 0,
mi_col: 2,
sub_size: BLOCK_8X8,
skip: 0,
skip_mode: 0,
segment_id: 0,
is_inter: 1,
y_mode: MODE_NEWMV,
ref_frame: [1, -1],
mv: [16, -8],
mv2: [0, 0],
interp_filter: [EIGHTTAP, EIGHTTAP],
motion_mode: MOTION_MODE_SIMPLE,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: None,
tx_size: 0,
// r415 §5.11.29 compound defaults (AVERAGE, pre-set
// comp_group_idx/compound_idx, no side data).
comp_group_idx: 0,
compound_idx: 1,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: 0,
wedge_interintra: 0,
interintra_wedge_index: 0,
});
// Baseline stamp INSIDE the rect (the committed pre-trial leaf).
let base = EncoderBlockSyntaxStamp {
mi_row: 2,
mi_col: 2,
sub_size: BLOCK_8X8,
skip: 1,
skip_mode: 0,
segment_id: 0,
is_inter: 1,
y_mode: MODE_NEARESTMV,
ref_frame: [1, -1],
mv: [8, 24],
mv2: [0, 0],
interp_filter: [EIGHTTAP, EIGHTTAP],
motion_mode: MOTION_MODE_SIMPLE,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: None,
tx_size: 0,
// r415 §5.11.29 compound defaults (AVERAGE, pre-set
// comp_group_idx/compound_idx, no side data).
comp_group_idx: 0,
compound_idx: 1,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: 0,
wedge_interintra: 0,
interintra_wedge_index: 0,
};
walker.stamp_encoder_block_syntax(&base);
let gm_params = {
let mut p = [[0i32; 6]; 8];
for row in p.iter_mut() {
row[2] = 1 << WARPEDMODEL_PREC_BITS;
row[5] = 1 << WARPEDMODEL_PREC_BITS;
}
p
};
let mfm = MotionFieldMvs::new_invalid(8, 8);
let stack_at = |w: &PartitionWalker| {
w.find_mv_stack(
4,
2,
BLOCK_8X8,
[1, -1],
false,
false,
[GM_TYPE_IDENTITY; 8],
gm_params,
[0; 8],
false,
false,
&mfm,
)
.unwrap()
};
let before = stack_at(&walker);
let grids_before = (
walker.mi_sizes().to_vec(),
walker.skips().to_vec(),
walker.is_inters().to_vec(),
walker.y_modes().to_vec(),
walker.ref_frames().to_vec(),
walker.mvs().to_vec(),
walker.interp_filters().to_vec(),
walker.motion_modes().to_vec(),
walker.palette_sizes().to_vec(),
);
// Snapshot the 4×4 rect at (2, 2), trial-stamp a different
// leaf shape over it (intra this time, plus a palette to
// exercise the plane-major grids), then restore.
let snap = walker.snapshot_encoder_stamp_rect(2, 2, 4);
walker.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row: 2,
mi_col: 2,
sub_size: BLOCK_16X16,
skip: 0,
skip_mode: 0,
segment_id: 3,
is_inter: 0,
y_mode: 0,
ref_frame: [0, -1],
mv: [0, 0],
mv2: [0, 0],
interp_filter: [EIGHTTAP, EIGHTTAP],
motion_mode: MOTION_MODE_SIMPLE,
palette_size_y: 2,
palette_colors_y: &[10, 20],
palette_size_uv: 2,
palette_colors_u: &[30, 40],
palette_colors_v: &[50, 60],
cdef: None,
tx_size: 4,
// r415 §5.11.29 compound defaults (AVERAGE, pre-set
// comp_group_idx/compound_idx, no side data).
comp_group_idx: 0,
compound_idx: 1,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: 0,
wedge_interintra: 0,
interintra_wedge_index: 0,
});
assert_ne!(
stack_at(&walker).ref_stack_mv,
before.ref_stack_mv,
"the trial stamp must actually perturb the §7.10.2 scan \
(otherwise this test proves nothing)"
);
walker.restore_encoder_stamp_rect(&snap);
let after = stack_at(&walker);
assert_eq!(after.num_mv_found, before.num_mv_found);
assert_eq!(after.ref_stack_mv, before.ref_stack_mv);
assert_eq!(after.weight_stack, before.weight_stack);
assert_eq!(after.new_mv_context, before.new_mv_context);
assert_eq!(after.ref_mv_context, before.ref_mv_context);
assert_eq!(after.zero_mv_context, before.zero_mv_context);
assert_eq!(after.drl_ctx_stack, before.drl_ctx_stack);
let grids_after = (
walker.mi_sizes().to_vec(),
walker.skips().to_vec(),
walker.is_inters().to_vec(),
walker.y_modes().to_vec(),
walker.ref_frames().to_vec(),
walker.mvs().to_vec(),
walker.interp_filters().to_vec(),
walker.motion_modes().to_vec(),
walker.palette_sizes().to_vec(),
);
assert_eq!(
grids_after, grids_before,
"restore must return every stamped grid to its pre-trial bytes"
);
}
/// r346 §5.11.33 frame-scope **multi-leaf, sub-pel** single-ref
/// reconstruction: a 4×2 mi frame (16×8 luma) split into two
/// side-by-side BLOCK_8X8 inter leaves carrying **different non-zero**
/// MVs (one at a 1/2-pel position, one at a 1/4-pel position) must be
/// reconstructed leaf-by-leaf by
/// [`PartitionWalker::reconstruct_inter_frame_into_curr_frame`] — each
/// leaf's §7.11.3.1 sub-pel MC matching its own per-block
/// [`crate::reconstruct_inter_block`] oracle over the leaf's 8×8
/// footprint. This proves the bridge's grid-driven leaf-origin walk
/// dispatches each leaf with its own decoded MV / interp-filter (not a
/// single shared block) through the real sub-pel interpolation kernel.
#[test]
fn r346_walker_reconstructs_multi_leaf_subpel_inter_frame() {
use crate::inter_pred::EIGHTTAP;
use crate::{reconstruct_inter_block, InterModeInfo, PlaneRefSpec, RefFrameStoreEntry};
// Reference frame large enough for both leaves' sub-pel taps.
let ref_w: usize = 24;
let ref_h: usize = 24;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = ((r * 11 + c * 13 + 5) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
// Frame: 16×8 luma (4 mi cols × 2 mi rows). Leaf A at mi (0,0),
// leaf B at mi (0,2), each BLOCK_8X8 (2×2 mi).
let frame_w = 16usize;
let frame_h = 8usize;
let mv_a = [4i16, 0]; // 1/2-pel vertical (4/8) — sub-pel.
let mv_b = [0i16, 2]; // 1/4-pel horizontal (2/8) — sub-pel.
// Per-block oracles into a shared 16-wide frame buffer (the walker
// buffer's stride), each at its plane-space origin.
let mut oracle = vec![0u16; frame_w * frame_h];
reconstruct_inter_block(
InterModeInfo {
ref_frame: last,
mv: mv_a,
},
&ref_frame_idx,
&store,
0,
/* x */ 0,
/* y */ 0,
8,
8,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
frame_w,
)
.unwrap();
reconstruct_inter_block(
InterModeInfo {
ref_frame: last,
mv: mv_b,
},
&ref_frame_idx,
&store,
0,
/* x */ 8,
/* y */ 0,
8,
8,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
frame_w,
)
.unwrap();
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(2, 4, geom).unwrap();
for (mi_col, mv) in [(0u32, mv_a), (2u32, mv_b)] {
walker.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row: 0,
mi_col,
sub_size: BLOCK_8X8,
skip: 1,
skip_mode: 0,
segment_id: 0,
is_inter: 1,
y_mode: MODE_NEARESTMV,
ref_frame: [last as i8, -1],
mv: [mv[0] as i32, mv[1] as i32],
mv2: [0, 0],
interp_filter: [EIGHTTAP, EIGHTTAP],
motion_mode: MOTION_MODE_SIMPLE,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: None,
tx_size: 0,
// r415 §5.11.29 compound defaults (AVERAGE, pre-set
// comp_group_idx/compound_idx, no side data).
comp_group_idx: 0,
compound_idx: 1,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: 0,
wedge_interintra: 0,
interintra_wedge_index: 0,
});
}
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_frame_into_curr_frame(&ref_frame_idx, 8, &[ref_spec])
.expect("multi-leaf sub-pel frame reconstruction");
let (rows, cols) = walker.curr_frame_dims(0).unwrap();
assert_eq!((rows, cols), (frame_h as u32, frame_w as u32));
let got: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(
got, oracle,
"the frame bridge must reconstruct each leaf with its own decoded sub-pel MV, \
matching the per-block oracle across both leaves"
);
}
/// r378 §5.11.33 frame-scope **OBMC** dispatch through the walker
/// bridge: a 4×4 mi frame (16×16 luma) with an 8×8 `motion_mode ==
/// OBMC` leaf at mi (2,2) plus inter neighbours above (mi 0,2) and left
/// (mi 2,0) must be reconstructed by
/// [`PartitionWalker::reconstruct_inter_frame_into_curr_frame`] through
/// the §7.11.3.9-10 overlap path — the OBMC leaf footprint differing
/// from the same frame decoded with every leaf forced to SIMPLE (which
/// proves the bridge's OBMC context threaded the neighbour blend), while
/// the three non-OBMC leaves stay byte-identical.
#[test]
fn r378_walker_reconstructs_obmc_inter_frame() {
use crate::inter_pred::EIGHTTAP;
use crate::{PlaneRefSpec, RefFrameStoreEntry};
let ref_w: usize = 32;
let ref_h: usize = 32;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = ((r * 13 + c * 5 + 1) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
// Build a walker and stamp four 8×8 inter leaves; the (2,2) leaf is
// OBMC, the rest SIMPLE. `motion_mode` controls whether the bridge's
// OBMC arm fires.
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let leaves: [(u32, u32, [i32; 2], u8); 4] = [
(0, 2, [0, 8], MOTION_MODE_SIMPLE), // above neighbour
(2, 0, [8, 0], MOTION_MODE_SIMPLE), // left neighbour
(0, 0, [4, 4], MOTION_MODE_SIMPLE), // filler
(2, 2, [2, 6], MOTION_MODE_OBMC), // OBMC target
];
let build = |force_simple: bool| -> Vec<u16> {
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
for &(mr, mc, mv, mm) in &leaves {
let mode = if force_simple { MOTION_MODE_SIMPLE } else { mm };
walker.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row: mr,
mi_col: mc,
sub_size: BLOCK_8X8,
skip: 1,
skip_mode: 0,
segment_id: 0,
is_inter: 1,
y_mode: MODE_NEARESTMV,
ref_frame: [last as i8, -1],
mv,
mv2: [0, 0],
interp_filter: [EIGHTTAP, EIGHTTAP],
motion_mode: mode,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: None,
tx_size: 0,
// r415 §5.11.29 compound defaults (AVERAGE, pre-set
// comp_group_idx/compound_idx, no side data).
comp_group_idx: 0,
compound_idx: 1,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: 0,
wedge_interintra: 0,
interintra_wedge_index: 0,
});
}
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_frame_into_curr_frame(&ref_frame_idx, 8, &[ref_spec])
.expect("frame-scope OBMC reconstruction");
walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect()
};
let got_obmc = build(false);
let got_simple = build(true);
let curr_w = 16usize;
// The OBMC leaf footprint (luma rows 8..16, cols 8..16) must differ
// from the all-SIMPLE walk; every other sample must be identical.
let mut obmc_differs = false;
for r in 0..16 {
for c in 0..16 {
let in_obmc = (8..16).contains(&r) && (8..16).contains(&c);
if in_obmc {
if got_obmc[r * curr_w + c] != got_simple[r * curr_w + c] {
obmc_differs = true;
}
} else {
assert_eq!(
got_obmc[r * curr_w + c],
got_simple[r * curr_w + c],
"non-OBMC sample ({c},{r}) must be identical with/without OBMC"
);
}
}
}
assert!(
obmc_differs,
"the OBMC leaf must reconstruct differently from the SIMPLE fallback \
(the §7.11.3.9 overlap blend must have fired through the bridge)"
);
}
/// r346 frame bridge guards: an empty `plane_refs`, an out-of-range
/// plane, and an inter leaf whose `RefFrame[0]` is `INTRA_FRAME`
/// (rejected downstream by [`crate::reconstruct_inter_block`]) are all
/// caller bugs surfaced as [`crate::Error::PartitionWalkOutOfRange`].
#[test]
fn r346_walker_inter_frame_bridge_guards() {
use crate::inter_pred::EIGHTTAP;
use crate::{PlaneRefSpec, RefFrameStoreEntry};
let refp = vec![0u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
// Empty plane_refs.
let mut w0 = PartitionWalker::new(2, 2, geom).unwrap();
assert!(matches!(
w0.reconstruct_inter_frame_into_curr_frame(&ref_frame_idx, 8, &[]),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Out-of-range plane.
let mut w1 = PartitionWalker::new(2, 2, geom).unwrap();
let bad_plane = PlaneRefSpec {
plane: 3,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w1.reconstruct_inter_frame_into_curr_frame(&ref_frame_idx, 8, &[bad_plane]),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Inter leaf with INTRA_FRAME RefFrame[0] — rejected by the
// downstream §7.11.3.1 driver.
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
w2.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row: 0,
mi_col: 0,
sub_size: BLOCK_8X8,
skip: 1,
skip_mode: 0,
segment_id: 0,
is_inter: 1,
y_mode: MODE_NEARESTMV,
// INTRA_FRAME (0) in slot 0 is a caller bug on the inter arm.
ref_frame: [crate::uncompressed_header_tail::INTRA_FRAME as i8, -1],
mv: [0, 0],
mv2: [0, 0],
interp_filter: [EIGHTTAP, EIGHTTAP],
motion_mode: MOTION_MODE_SIMPLE,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: None,
tx_size: 0,
// r415 §5.11.29 compound defaults (AVERAGE, pre-set
// comp_group_idx/compound_idx, no side data).
comp_group_idx: 0,
compound_idx: 1,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: 0,
wedge_interintra: 0,
interintra_wedge_index: 0,
});
let good_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w2.reconstruct_inter_frame_into_curr_frame(&ref_frame_idx, 8, &[good_spec]),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r304 single-ref bridge guards: an out-of-range plane, a negative
/// origin, and an `INTRA_FRAME` `RefFrame[0]` (rejected by
/// [`crate::reconstruct_inter_block`]) are all caller bugs surfaced
/// as [`crate::Error::PartitionWalkOutOfRange`].
#[test]
fn r304_walker_single_ref_bridge_guards() {
use crate::inter_pred::EIGHTTAP;
use crate::{InterModeInfo, PlaneRefSpec, RefFrameStoreEntry};
let refp = vec![0u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let good_mode = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
// Out-of-range plane.
let mut w = PartitionWalker::new(2, 2, geom).unwrap();
let bad_plane = PlaneRefSpec {
plane: 3,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w.reconstruct_inter_block_into_curr_frame(
good_mode,
&ref_frame_idx,
&bad_plane,
0,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
let good_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
// Negative origin (rejected by reconstruct_inter_block).
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
assert!(matches!(
w2.reconstruct_inter_block_into_curr_frame(
good_mode,
&ref_frame_idx,
&good_spec,
-1,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// INTRA_FRAME RefFrame[0] (0): not an inter reference.
let mut w3 = PartitionWalker::new(2, 2, geom).unwrap();
let intra_mode = InterModeInfo {
ref_frame: 0,
mv: [0, 0],
};
assert!(matches!(
w3.reconstruct_inter_block_into_curr_frame(
intra_mode,
&ref_frame_idx,
&good_spec,
0,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r338 §7.11.3.1 WARP end-to-end: the walker bridge
/// [`PartitionWalker::reconstruct_inter_block_warp_into_curr_frame`]
/// drives a `motion_mode == WARPED_CAUSAL` LOCALWARP block into its
/// own allocated `CurrFrame[0]`, matching the per-block
/// [`crate::reconstruct_inter_block_warp`] driver byte-for-byte, and
/// differing from the translational SIMPLE bridge (the warp dispatch
/// actually fired).
#[test]
fn r338_walker_reconstructs_warped_causal_block_pixels() {
use crate::inter_pred::EIGHTTAP;
use crate::{
reconstruct_inter_block, reconstruct_inter_block_warp, InterModeInfo, PlaneRefSpec,
RefFrameStoreEntry, WarpDriverParams,
};
// High-frequency reference so a sub-pel warp shift diverges from
// the integer-position translational copy.
let ref_w: usize = 32;
let ref_h: usize = 32;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = (((r * 37) ^ (c * 53)) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let mode_info = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
// A small valid horizontal-shear affine matrix (`setup_shear`
// accepts it) so the §7.11.3.5 warped convolution produces a
// result distinct from the translational fallback.
const WARP_SHEAR: [i32; 6] = [0, 0, (1 << 16) + 4096, 0, 0, 1 << 16];
const WARP_ID: [i32; 6] = [0, 0, 1 << 16, 0, 0, 1 << 16];
let warp = WarpDriverParams {
y_mode: [crate::cdf::MODE_NEWMV, 0],
gm_type: [crate::cdf::GM_TYPE_IDENTITY as u8, 0],
gm_params: [WARP_ID, WARP_ID],
local_warp_params: WARP_SHEAR,
local_valid: true,
ref_is_scaled: [false, false],
force_integer_mv: false,
};
// 8×8 luma buffer (MiRows × MiCols × MI_SIZE = 2 × 4).
let (curr_w, curr_h) = (8usize, 8usize);
// Per-block warp oracle into a fresh zero buffer with the walker's
// 8-column stride.
let mut oracle = vec![0u16; curr_w * curr_h];
reconstruct_inter_block_warp(
mode_info,
&warp,
&ref_frame_idx,
&store,
0,
0,
0,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
curr_w,
)
.unwrap();
// Translational oracle (for the divergence check).
let mut simple = vec![0u16; curr_w * curr_h];
reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store,
0,
0,
0,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut simple,
curr_w,
)
.unwrap();
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let mut walker = PartitionWalker::new(2, 2, geom).unwrap();
assert!(walker.curr_frame(0).is_none());
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_block_warp_into_curr_frame(
mode_info,
&warp,
&ref_frame_idx,
&ref_spec,
0,
0,
curr_w,
curr_h,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker warped reconstruction");
let (rows, cols) = walker.curr_frame_dims(0).unwrap();
assert_eq!((rows, cols), (curr_h as u32, curr_w as u32));
let got: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(
got, oracle,
"the walker warp bridge must reconstruct warped pixels identically to the per-block driver"
);
assert_ne!(
got, simple,
"the WARPED_CAUSAL bridge must diverge from the translational fallback"
);
}
/// r338 walker warp-bridge guards: an out-of-range plane and a
/// negative origin are caller bugs surfaced as
/// [`crate::Error::PartitionWalkOutOfRange`].
#[test]
fn r338_walker_warp_bridge_guards() {
use crate::inter_pred::EIGHTTAP;
use crate::{InterModeInfo, PlaneRefSpec, RefFrameStoreEntry, WarpDriverParams};
let refp = vec![3u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
const WARP_ID: [i32; 6] = [0, 0, 1 << 16, 0, 0, 1 << 16];
let warp = WarpDriverParams {
y_mode: [crate::cdf::MODE_NEWMV, 0],
gm_type: [crate::cdf::GM_TYPE_IDENTITY as u8, 0],
gm_params: [WARP_ID, WARP_ID],
local_warp_params: WARP_ID,
local_valid: true,
ref_is_scaled: [false, false],
force_integer_mv: false,
};
let good_mode = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
// Out-of-range plane.
let mut w = PartitionWalker::new(2, 2, geom).unwrap();
let bad_plane = PlaneRefSpec {
plane: 3,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w.reconstruct_inter_block_warp_into_curr_frame(
good_mode,
&warp,
&ref_frame_idx,
&bad_plane,
0,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Negative origin.
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
let good_plane = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w2.reconstruct_inter_block_warp_into_curr_frame(
good_mode,
&warp,
&ref_frame_idx,
&good_plane,
-4,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r355 §7.11.3.1 OBMC end-to-end: the walker bridge
/// [`PartitionWalker::reconstruct_inter_block_obmc_into_curr_frame`]
/// drives a `motion_mode == OBMC` block into its own allocated
/// `CurrFrame[0]`, matching the per-block
/// [`crate::reconstruct_inter_block_obmc`] driver byte-for-byte, and
/// — with a qualifying above-row neighbour carrying a **distinct** MV
/// — diverging from the translational SIMPLE bridge (the §7.11.3.9
/// overlap blend actually fired).
#[test]
fn r355_walker_reconstructs_obmc_block_pixels() {
use crate::inter_pred::{ObmcNeighbour, ObmcParams, PredictInterRef, EIGHTTAP};
use crate::{
reconstruct_inter_block, reconstruct_inter_block_obmc, InterModeInfo, PlaneRefSpec,
RefFrameStoreEntry,
};
// High-frequency reference so a neighbour's shifted MV diverges
// from the block's own integer-position translational copy. Sized
// 48×48 so the §7.11.3.9 neighbour MC's 8-tap window at the
// shifted MV stays inside the reference (matching the proven-safe
// leaf-kernel setup).
let ref_w: usize = 48;
let ref_h: usize = 48;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = (((r * 37) ^ (c * 53)) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
// Block at plane (8, 8) ⇒ MiRow = MiCol = 2 in the luma frame.
let mode_info = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
let (curr_w, curr_h) = (8usize, 8usize);
// §7.11.3.9 above-row neighbour carrying a distinct MV (1 full
// luma sample down in 1/8-pel units) so its overlap contribution
// is not the block's own prediction.
let above_bundle = PredictInterRef {
ref_plane: &refp[..],
ref_stride: stride,
ref_upscaled_width: ref_w as u32,
ref_width: ref_w as u32,
ref_height: ref_h as u32,
mv: [8, 0],
};
let above_nb = ObmcNeighbour {
bundle: above_bundle,
step4: 2,
interp_filter_x: crate::inter_pred::EIGHTTAP,
interp_filter_y: crate::inter_pred::EIGHTTAP,
axis_pos4: 2,
};
let obmc = ObmcParams {
mi_row: 2,
mi_col: 2,
mi_cols: 16,
mi_rows: 16,
mi_width_log2: 1,
mi_height_log2: 1,
avail_u: true,
avail_l: false,
plane_residual_size_ge_block_8x8: true,
above_neighbours: &[above_nb],
left_neighbours: &[],
};
// Per-block oracles into a fresh 16×16 plane (the walker's
// eventual extent) so the block writes at its plane-space origin
// (8, 8) — the §7.11.3.9 neighbour walk reads `(x, y)` as plane
// coordinates, so the oracle must share the block's position.
let oracle_stride = 16usize;
let mut oracle = vec![0u16; oracle_stride * 16];
reconstruct_inter_block_obmc(
mode_info,
&obmc,
&ref_frame_idx,
&store,
0,
8,
8,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
oracle_stride,
)
.unwrap();
// Translational oracle (for the divergence check).
let mut simple = vec![0u16; oracle_stride * 16];
reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store,
0,
8,
8,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut simple,
oracle_stride,
)
.unwrap();
// Extract the 8×8 footprint at (8, 8) from both oracle planes.
let mut oracle_blk = vec![0u16; curr_w * curr_h];
let mut simple_blk = vec![0u16; curr_w * curr_h];
for i in 0..curr_h {
for j in 0..curr_w {
oracle_blk[i * curr_w + j] = oracle[(8 + i) * oracle_stride + (8 + j)];
simple_blk[i * curr_w + j] = simple[(8 + i) * oracle_stride + (8 + j)];
}
}
// 4×4 mi walker → 16×16 luma frame; the block sits at mi (2, 2).
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
assert!(walker.curr_frame(0).is_none());
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_block_obmc_into_curr_frame(
mode_info,
&obmc,
&ref_frame_idx,
&ref_spec,
8,
8,
curr_w,
curr_h,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker OBMC reconstruction");
// Read back the 8×8 footprint at plane (8, 8) out of the 16×16
// walker buffer.
let (rows, cols) = walker.curr_frame_dims(0).unwrap();
assert_eq!((rows, cols), (16, 16));
let plane = walker.curr_frame(0).unwrap();
let mut got = vec![0u16; curr_w * curr_h];
for i in 0..curr_h {
for j in 0..curr_w {
got[i * curr_w + j] = plane[(8 + i) * cols as usize + (8 + j)] as u16;
}
}
assert_eq!(
got, oracle_blk,
"the walker OBMC bridge must reconstruct overlapped pixels identically to the per-block driver"
);
assert_ne!(
got, simple_blk,
"the OBMC bridge with a distinct-MV neighbour must diverge from the translational fallback"
);
}
/// r355 walker OBMC-bridge guards: an out-of-range plane and a
/// negative origin are caller bugs surfaced as
/// [`crate::Error::PartitionWalkOutOfRange`].
#[test]
fn r355_walker_obmc_bridge_guards() {
use crate::inter_pred::{ObmcParams, EIGHTTAP};
use crate::{InterModeInfo, PlaneRefSpec, RefFrameStoreEntry};
let refp = vec![3u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let good_mode = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
// Empty-neighbour OBMC params (no overlap; the guard checks fire
// before the walk regardless).
let obmc = ObmcParams {
mi_row: 0,
mi_col: 0,
mi_cols: 4,
mi_rows: 4,
mi_width_log2: 1,
mi_height_log2: 1,
avail_u: false,
avail_l: false,
plane_residual_size_ge_block_8x8: true,
above_neighbours: &[],
left_neighbours: &[],
};
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
// Out-of-range plane.
let mut w = PartitionWalker::new(2, 2, geom).unwrap();
let bad_plane = PlaneRefSpec {
plane: 3,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w.reconstruct_inter_block_obmc_into_curr_frame(
good_mode,
&obmc,
&ref_frame_idx,
&bad_plane,
0,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Negative origin.
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
let good_plane = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w2.reconstruct_inter_block_obmc_into_curr_frame(
good_mode,
&obmc,
&ref_frame_idx,
&good_plane,
-4,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r355 §7.11.3.9 **left-pass** OBMC through the walker bridge: a
/// qualifying left-column neighbour with a distinct MV exercises the
/// `mask[j]` (column) blend axis (vs the above-pass `mask[i]` row
/// axis), and the bridge result matches the per-block
/// [`crate::reconstruct_inter_block_obmc`] driver while diverging from
/// the translational SIMPLE fallback.
#[test]
fn r355_walker_obmc_left_pass_bridge() {
use crate::inter_pred::{ObmcNeighbour, ObmcParams, PredictInterRef, EIGHTTAP};
use crate::{
reconstruct_inter_block, reconstruct_inter_block_obmc, InterModeInfo, PlaneRefSpec,
RefFrameStoreEntry,
};
let ref_w: usize = 48;
let ref_h: usize = 48;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = (((r * 29) ^ (c * 41)) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let mode_info = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
let (curr_w, curr_h) = (8usize, 8usize);
// Left-column neighbour, distinct MV (1 sample right).
let left_bundle = PredictInterRef {
ref_plane: &refp[..],
ref_stride: stride,
ref_upscaled_width: ref_w as u32,
ref_width: ref_w as u32,
ref_height: ref_h as u32,
mv: [0, 8],
};
let left_nb = ObmcNeighbour {
bundle: left_bundle,
step4: 2,
interp_filter_x: crate::inter_pred::EIGHTTAP,
interp_filter_y: crate::inter_pred::EIGHTTAP,
axis_pos4: 2,
};
let obmc = ObmcParams {
mi_row: 2,
mi_col: 2,
mi_cols: 16,
mi_rows: 16,
mi_width_log2: 1,
mi_height_log2: 1,
avail_u: false,
avail_l: true,
plane_residual_size_ge_block_8x8: true,
above_neighbours: &[],
left_neighbours: &[left_nb],
};
// Per-block oracles into a 16×16 plane (block writes at (8, 8)).
let st = 16usize;
let mut oracle = vec![0u16; st * 16];
reconstruct_inter_block_obmc(
mode_info,
&obmc,
&ref_frame_idx,
&store,
0,
8,
8,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
st,
)
.unwrap();
let mut simple = vec![0u16; st * 16];
reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store,
0,
8,
8,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut simple,
st,
)
.unwrap();
let mut oracle_blk = vec![0u16; curr_w * curr_h];
let mut simple_blk = vec![0u16; curr_w * curr_h];
for i in 0..curr_h {
for j in 0..curr_w {
oracle_blk[i * curr_w + j] = oracle[(8 + i) * st + (8 + j)];
simple_blk[i * curr_w + j] = simple[(8 + i) * st + (8 + j)];
}
}
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_block_obmc_into_curr_frame(
mode_info,
&obmc,
&ref_frame_idx,
&ref_spec,
8,
8,
curr_w,
curr_h,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker OBMC left-pass reconstruction");
let (_, cols) = walker.curr_frame_dims(0).unwrap();
let plane = walker.curr_frame(0).unwrap();
let mut got = vec![0u16; curr_w * curr_h];
for i in 0..curr_h {
for j in 0..curr_w {
got[i * curr_w + j] = plane[(8 + i) * cols as usize + (8 + j)] as u16;
}
}
assert_eq!(
got, oracle_blk,
"left-pass bridge must match per-block driver"
);
assert_ne!(
got, simple_blk,
"left-pass OBMC must diverge from translational"
);
}
/// r355 §7.11.3.9 OBMC bridge on a **4:2:0 chroma** plane: the bridge
/// drives a `motion_mode == OBMC` chroma block (subsampling 1/1)
/// through the per-plane §7.11.3.3 ref resolution + §7.11.3.9 walk,
/// matching the per-block driver and writing into the correctly-sized
/// (half-extent) chroma `CurrFrame[1]` buffer.
#[test]
fn r355_walker_obmc_chroma_plane_bridge() {
use crate::inter_pred::{ObmcNeighbour, ObmcParams, PredictInterRef, EIGHTTAP};
use crate::{
reconstruct_inter_block_obmc, InterModeInfo, PlaneRefSpec, RefFrameStoreEntry,
};
// Chroma reference plane (already subsampled — the bundle carries
// the chroma-plane samples directly).
let ref_w: usize = 32;
let ref_h: usize = 32;
let stride = ref_w;
let mut refp = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp[r * stride + c] = ((((r * 13) ^ (c * 19)) + 7) & 0xFF) as u16;
}
}
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let mode_info = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
// A 4×4 chroma block at plane (4, 4); above neighbour distinct MV.
let (cw, ch) = (4usize, 4usize);
let above_bundle = PredictInterRef {
ref_plane: &refp[..],
ref_stride: stride,
ref_upscaled_width: ref_w as u32,
ref_width: ref_w as u32,
ref_height: ref_h as u32,
mv: [8, 0],
};
let above_nb = ObmcNeighbour {
bundle: above_bundle,
step4: 2,
interp_filter_x: crate::inter_pred::EIGHTTAP,
interp_filter_y: crate::inter_pred::EIGHTTAP,
axis_pos4: 2,
};
// plane_residual_size_ge_block_8x8 = false on this small chroma
// block → above-pass gated off; left-pass with one neighbour.
let left_nb = above_nb;
let obmc = ObmcParams {
mi_row: 2,
mi_col: 2,
mi_cols: 16,
mi_rows: 16,
mi_width_log2: 1,
mi_height_log2: 1,
avail_u: true,
avail_l: true,
plane_residual_size_ge_block_8x8: false,
above_neighbours: &[above_nb],
left_neighbours: &[left_nb],
};
// Per-block chroma oracle into a 16×16 plane at (4, 4).
let st = 16usize;
let mut oracle = vec![0u16; st * 16];
reconstruct_inter_block_obmc(
mode_info,
&obmc,
&ref_frame_idx,
&store,
1,
4,
4,
cw,
ch,
8,
1,
1,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oracle,
st,
)
.unwrap();
let mut oracle_blk = vec![0u16; cw * ch];
for i in 0..ch {
for j in 0..cw {
oracle_blk[i * cw + j] = oracle[(4 + i) * st + (4 + j)];
}
}
// 4×4 mi walker (8×8 chroma half-extent). The chroma plane buffer
// is sized MiCols × MI_SIZE >> 1 = 8 wide.
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let ref_spec = PlaneRefSpec {
plane: 1,
subsampling_x: 1,
subsampling_y: 1,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_block_obmc_into_curr_frame(
mode_info,
&obmc,
&ref_frame_idx,
&ref_spec,
4,
4,
cw,
ch,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker OBMC chroma reconstruction");
let (rows, cols) = walker.curr_frame_dims(1).unwrap();
assert_eq!((rows, cols), (8, 8), "4:2:0 chroma plane is half-extent");
let plane = walker.curr_frame(1).unwrap();
let mut got = vec![0u16; cw * ch];
for i in 0..ch {
for j in 0..cw {
got[i * cw + j] = plane[(4 + i) * cols as usize + (4 + j)] as u16;
}
}
assert_eq!(
got, oracle_blk,
"chroma OBMC bridge must match the per-block driver on the subsampled plane"
);
}
/// r305 §7.11.3.1 compound end-to-end: the walker bridge
/// [`PartitionWalker::reconstruct_inter_block_compound_into_curr_frame`]
/// must drive the two-reference `COMPOUND_AVERAGE` step-14 combine
/// (`CurrFrame[plane][y+i][x+j] = Clip1(Round2(preds[0] + preds[1],
/// 1))`) into the walker's own tracked `CurrFrame[plane]` buffer
/// byte-for-byte identically to the per-block
/// [`crate::inter_pred::reconstruct_inter_block_compound`] driver — i.e.
/// the walker now reconstructs one compound inter block's pixels inline.
/// The plane buffer is allocated by the bridge (compound overwrites; no
/// intra-half prerequisite).
#[test]
fn r305_walker_reconstructs_compound_average_inter_block_pixels() {
use crate::inter_pred::{
reconstruct_inter_block_compound, CompoundInterModeInfo, CompoundOrderHintContext,
EIGHTTAP,
};
use crate::{PlaneRefSpec, RefFrameStoreEntry, COMPOUND_AVERAGE};
// Two §7.11.3.3 reference frames (one plane each, 16×16) with
// distinct content so the two predictions genuinely differ.
let ref_w: usize = 16;
let ref_h: usize = 16;
let stride = ref_w;
let mut refp0 = vec![0u16; ref_h * stride];
let mut refp1 = vec![0u16; ref_h * stride];
for r in 0..ref_h {
for c in 0..ref_w {
refp0[r * stride + c] = ((r * 9 + c * 5 + 1) & 0xFF) as u16;
refp1[r * stride + c] = ((r * 3 + c * 11 + 7) & 0xFF) as u16;
}
}
let e0 = RefFrameStoreEntry {
plane: &refp0[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
let e1 = RefFrameStoreEntry {
plane: &refp1[..],
stride,
upscaled_width: ref_w as u32,
width: ref_w as u32,
height: ref_h as u32,
};
// refp0 at slot 0, refp1 at slot 3; LAST→0, ALTREF→3.
let store = [e0, e0, e0, e1];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 3];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let altref = crate::uncompressed_header_tail::ALTREF_FRAME as u8;
let mode_info = CompoundInterModeInfo {
ref_frame_0: last,
ref_frame_1: altref,
mv0: [0, 4],
mv1: [8, 0],
compound_type: COMPOUND_AVERAGE,
wedge: None,
mask_type: 0,
};
let order_hints = CompoundOrderHintContext {
order_hint_bits: 7,
current_order_hint: 0,
order_hint_ref0: 0,
order_hint_ref1: 0,
};
let curr_w = 8usize;
let curr_h = 8usize;
// Per-block oracle into a fresh 8×8 buffer at origin (0, 0) with
// the SAME stride the walker buffer will have (8 columns).
let mut oracle = vec![0u16; curr_w * curr_h];
reconstruct_inter_block_compound(
mode_info,
order_hints,
&ref_frame_idx,
&store,
0,
0,
0,
curr_w,
curr_h,
8,
0,
0,
ref_w as u32,
ref_h as u32,
EIGHTTAP,
EIGHTTAP,
None,
/* warp */ None,
&mut oracle,
curr_w,
)
.unwrap();
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let mut walker = PartitionWalker::new(2, 2, geom).unwrap();
// Fresh walker: curr_frame[0] is unallocated. The compound bridge
// must allocate it (no intra-half prerequisite).
assert!(walker.curr_frame(0).is_none());
let ref_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: ref_w as u32,
frame_height: ref_h as u32,
};
walker
.reconstruct_inter_block_compound_into_curr_frame(
mode_info,
order_hints,
&ref_frame_idx,
&ref_spec,
0,
0,
curr_w,
curr_h,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker compound reconstruction");
let (rows, cols) = walker.curr_frame_dims(0).unwrap();
assert_eq!((rows, cols), (curr_h as u32, curr_w as u32));
let got: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(
got, oracle,
"the walker bridge must reconstruct compound pixels identically to the per-block driver"
);
assert!(got.iter().any(|&s| s != 0), "compound MC must write pixels");
}
/// r305 compound bridge guards: an out-of-range plane, a negative
/// origin, and an `INTRA_FRAME` `RefFrame[1]` (rejected by
/// [`crate::inter_pred::reconstruct_inter_block_compound`]) are all
/// caller bugs surfaced as [`crate::Error::PartitionWalkOutOfRange`].
#[test]
fn r305_walker_compound_bridge_guards() {
use crate::inter_pred::{CompoundInterModeInfo, CompoundOrderHintContext, EIGHTTAP};
use crate::{PlaneRefSpec, RefFrameStoreEntry, COMPOUND_AVERAGE};
let refp = vec![0u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 2];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let altref = crate::uncompressed_header_tail::ALTREF_FRAME as u8;
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let order_hints = CompoundOrderHintContext {
order_hint_bits: 7,
current_order_hint: 0,
order_hint_ref0: 0,
order_hint_ref1: 0,
};
let good_mode = CompoundInterModeInfo {
ref_frame_0: last,
ref_frame_1: altref,
mv0: [0, 0],
mv1: [0, 0],
compound_type: COMPOUND_AVERAGE,
wedge: None,
mask_type: 0,
};
// Out-of-range plane.
let mut w = PartitionWalker::new(2, 2, geom).unwrap();
let bad_plane = PlaneRefSpec {
plane: 3,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
assert!(matches!(
w.reconstruct_inter_block_compound_into_curr_frame(
good_mode,
order_hints,
&ref_frame_idx,
&bad_plane,
0,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
let good_spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
// Negative origin (rejected by reconstruct_inter_block_compound).
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
assert!(matches!(
w2.reconstruct_inter_block_compound_into_curr_frame(
good_mode,
order_hints,
&ref_frame_idx,
&good_spec,
-1,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// INTRA_FRAME RefFrame[1] (0): not a compound inter reference.
let mut w3 = PartitionWalker::new(2, 2, geom).unwrap();
let intra_ref1 = CompoundInterModeInfo {
ref_frame_0: last,
ref_frame_1: 0,
mv0: [0, 0],
mv1: [0, 0],
compound_type: COMPOUND_AVERAGE,
wedge: None,
mask_type: 0,
};
assert!(matches!(
w3.reconstruct_inter_block_compound_into_curr_frame(
intra_ref1,
order_hints,
&ref_frame_idx,
&good_spec,
0,
0,
8,
8,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r306 multi-plane compound bridge: a `COMPOUND_DIFFWTD` block driven
/// through
/// [`PartitionWalker::reconstruct_inter_block_compound_multiplane_into_curr_frame`]
/// must reconstruct all three planes (Y, Cb, Cr) byte-identically to
/// the per-block [`crate::inter_pred::reconstruct_inter_block_compound`]
/// driver invoked once per plane with **one shared luma-grid
/// §7.11.3.12 `Mask` buffer** (the `plane == 0` call fills it; the
/// chroma calls read it downsampled, per av1-spec p.258 lines
/// 14392-14393). DIFFWTD is the arm that genuinely depends on the
/// shared mask, so it is the discriminating case.
#[test]
fn r306_walker_reconstructs_diffwtd_compound_block_multiplane() {
use crate::inter_pred::{
reconstruct_inter_block_compound, CompoundInterModeInfo, CompoundOrderHintContext,
EIGHTTAP,
};
use crate::{
block_height, block_width, PlaneRefSpec, RefFrameStoreEntry, COMPOUND_DIFFWTD,
};
// 4:2:0 reference planes: luma 16×16, chroma 8×8. Two distinct
// references per plane so the DIFFWTD mask is non-degenerate.
let build_plane = |w: usize, h: usize, a: usize, b: usize, c: usize| -> Vec<u16> {
let mut p = vec![0u16; w * h];
for r in 0..h {
for col in 0..w {
p[r * w + col] = ((r * a + col * b + c) & 0xFF) as u16;
}
}
p
};
let ly_w = 16usize;
let ly_h = 16usize;
let lc_w = 8usize;
let lc_h = 8usize;
// Luma refs.
let ry0 = build_plane(ly_w, ly_h, 9, 5, 1);
let ry1 = build_plane(ly_w, ly_h, 3, 11, 7);
// Cb refs.
let rcb0 = build_plane(lc_w, lc_h, 7, 2, 3);
let rcb1 = build_plane(lc_w, lc_h, 2, 13, 5);
// Cr refs.
let rcr0 = build_plane(lc_w, lc_h, 4, 6, 9);
let rcr1 = build_plane(lc_w, lc_h, 11, 1, 2);
fn mk<'a>(p: &'a [u16], w: usize, h: usize) -> RefFrameStoreEntry<'a> {
RefFrameStoreEntry {
plane: p,
stride: w,
upscaled_width: w as u32,
width: w as u32,
height: h as u32,
}
}
// Per-plane store: ref0 at slot 0, ref1 at slot 3 (LAST→0,
// ALTREF→3 via ref_frame_idx below).
let ey0 = mk(&ry0, ly_w, ly_h);
let ey1 = mk(&ry1, ly_w, ly_h);
let ecb0 = mk(&rcb0, lc_w, lc_h);
let ecb1 = mk(&rcb1, lc_w, lc_h);
let ecr0 = mk(&rcr0, lc_w, lc_h);
let ecr1 = mk(&rcr1, lc_w, lc_h);
let store_y = [ey0, ey0, ey0, ey1];
let store_cb = [ecb0, ecb0, ecb0, ecb1];
let store_cr = [ecr0, ecr0, ecr0, ecr1];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 3];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let altref = crate::uncompressed_header_tail::ALTREF_FRAME as u8;
let mode_info = CompoundInterModeInfo {
ref_frame_0: last,
ref_frame_1: altref,
mv0: [0, 4],
mv1: [8, 0],
compound_type: COMPOUND_DIFFWTD,
wedge: None,
mask_type: 0,
};
let order_hints = CompoundOrderHintContext {
order_hint_bits: 7,
current_order_hint: 0,
order_hint_ref0: 0,
order_hint_ref1: 0,
};
// BLOCK_8X8: luma 8×8, chroma 4×4 (4:2:0). Block at the
// top-left (mi_col = mi_row = 0) so per-plane base_x/base_y = 0.
let mi_size = BLOCK_8X8;
let lw = block_width(mi_size); // 8
let lh = block_height(mi_size); // 8
let cw = lw >> 1; // 4
let ch = lh >> 1; // 4
// ---- Oracle: per-block driver, three planes, ONE shared
// luma-grid mask (lw × lh). plane 0 fills it; chroma read it. ----
let mut shared_mask = vec![0u8; lw * lh];
let mut oy = vec![0u16; lw * lh];
let mut ocb = vec![0u16; cw * ch];
let mut ocr = vec![0u16; cw * ch];
reconstruct_inter_block_compound(
mode_info,
order_hints,
&ref_frame_idx,
&store_y,
0,
0,
0,
lw,
lh,
8,
0,
0,
ly_w as u32,
ly_h as u32,
EIGHTTAP,
EIGHTTAP,
Some(shared_mask.as_mut_slice()),
/* warp */ None,
&mut oy,
lw,
)
.unwrap();
reconstruct_inter_block_compound(
mode_info,
order_hints,
&ref_frame_idx,
&store_cb,
1,
0,
0,
cw,
ch,
8,
1,
1,
lc_w as u32,
lc_h as u32,
EIGHTTAP,
EIGHTTAP,
Some(shared_mask.as_mut_slice()),
/* warp */ None,
&mut ocb,
cw,
)
.unwrap();
reconstruct_inter_block_compound(
mode_info,
order_hints,
&ref_frame_idx,
&store_cr,
2,
0,
0,
cw,
ch,
8,
1,
1,
lc_w as u32,
lc_h as u32,
EIGHTTAP,
EIGHTTAP,
Some(shared_mask.as_mut_slice()),
/* warp */ None,
&mut ocr,
cw,
)
.unwrap();
// ---- Walker multi-plane bridge ----
// 2×2 mi grid → luma 8×8, chroma 4×4 buffers.
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let mut walker = PartitionWalker::new(2, 2, geom).unwrap();
assert!(walker.curr_frame(0).is_none());
let spec_y = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store_y,
frame_width: ly_w as u32,
frame_height: ly_h as u32,
};
let spec_cb = PlaneRefSpec {
plane: 1,
subsampling_x: 1,
subsampling_y: 1,
frame_store: &store_cb,
frame_width: lc_w as u32,
frame_height: lc_h as u32,
};
let spec_cr = PlaneRefSpec {
plane: 2,
subsampling_x: 1,
subsampling_y: 1,
frame_store: &store_cr,
frame_width: lc_w as u32,
frame_height: lc_h as u32,
};
let specs = [spec_y, spec_cb, spec_cr];
walker
.reconstruct_inter_block_compound_multiplane_into_curr_frame(
mode_info,
order_hints,
&ref_frame_idx,
&specs,
0,
0,
mi_size,
3,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker multi-plane compound reconstruction");
let got_y: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
let got_cb: Vec<u16> = walker
.curr_frame(1)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
let got_cr: Vec<u16> = walker
.curr_frame(2)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(got_y, oy, "Y plane must match the shared-mask oracle");
assert_eq!(got_cb, ocb, "Cb plane must match the shared-mask oracle");
assert_eq!(got_cr, ocr, "Cr plane must match the shared-mask oracle");
// Discriminating control: re-deriving a fresh per-plane mask for
// Cb (the single-plane-bridge behaviour) generally yields a
// DIFFERENT chroma plane — proving the shared luma mask is the
// load-bearing detail. (DIFFWTD's mask is derived from the
// chroma-resolution preds when computed per-plane, which differs
// from the downsampled luma-grid mask the spec mandates.)
let mut fresh_mask = vec![0u8; cw * ch];
let mut cb_wrong = vec![0u16; cw * ch];
reconstruct_inter_block_compound(
mode_info,
order_hints,
&ref_frame_idx,
&store_cb,
0, // plane 0 → forces a fresh full-extent mask derivation
0,
0,
cw,
ch,
8,
0,
0,
lc_w as u32,
lc_h as u32,
EIGHTTAP,
EIGHTTAP,
Some(fresh_mask.as_mut_slice()),
/* warp */ None,
&mut cb_wrong,
cw,
)
.unwrap();
assert_ne!(
cb_wrong, ocb,
"a fresh per-plane DIFFWTD mask must differ from the shared luma-grid mask"
);
}
/// r306 multi-plane compound bridge guards: an invalid `num_planes`,
/// an out-of-range `mi_size`, a too-short `plane_specs` slice, and an
/// `INTRA_FRAME` `RefFrame[1]` (rejected per-plane by the driver) are
/// all surfaced as [`crate::Error::PartitionWalkOutOfRange`].
#[test]
fn r306_walker_compound_multiplane_bridge_guards() {
use crate::inter_pred::{CompoundInterModeInfo, CompoundOrderHintContext, EIGHTTAP};
use crate::{PlaneRefSpec, RefFrameStoreEntry, COMPOUND_AVERAGE};
let refp = vec![0u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 3];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let altref = crate::uncompressed_header_tail::ALTREF_FRAME as u8;
let order_hints = CompoundOrderHintContext {
order_hint_bits: 7,
current_order_hint: 0,
order_hint_ref0: 0,
order_hint_ref1: 0,
};
let good_mode = CompoundInterModeInfo {
ref_frame_0: last,
ref_frame_1: altref,
mv0: [0, 0],
mv1: [0, 0],
compound_type: COMPOUND_AVERAGE,
wedge: None,
mask_type: 0,
};
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
let specs3 = [
PlaneRefSpec { plane: 0, ..spec },
PlaneRefSpec {
plane: 1,
subsampling_x: 1,
subsampling_y: 1,
..spec
},
PlaneRefSpec {
plane: 2,
subsampling_x: 1,
subsampling_y: 1,
..spec
},
];
// Invalid num_planes (2).
let mut w = PartitionWalker::new(2, 2, geom).unwrap();
assert!(matches!(
w.reconstruct_inter_block_compound_multiplane_into_curr_frame(
good_mode,
order_hints,
&ref_frame_idx,
&specs3,
0,
0,
BLOCK_8X8,
2,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Out-of-range mi_size.
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
assert!(matches!(
w2.reconstruct_inter_block_compound_multiplane_into_curr_frame(
good_mode,
order_hints,
&ref_frame_idx,
&specs3,
0,
0,
BLOCK_SIZES,
3,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// plane_specs too short for num_planes (1 spec, num_planes 3).
let mut w3 = PartitionWalker::new(2, 2, geom).unwrap();
let one_spec = [PlaneRefSpec { plane: 0, ..spec }];
assert!(matches!(
w3.reconstruct_inter_block_compound_multiplane_into_curr_frame(
good_mode,
order_hints,
&ref_frame_idx,
&one_spec,
0,
0,
BLOCK_8X8,
3,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// INTRA_FRAME RefFrame[1] (driver rejects on the first plane).
let mut w4 = PartitionWalker::new(2, 2, geom).unwrap();
let intra_ref1 = CompoundInterModeInfo {
ref_frame_1: 0,
..good_mode
};
assert!(matches!(
w4.reconstruct_inter_block_compound_multiplane_into_curr_frame(
intra_ref1,
order_hints,
&ref_frame_idx,
&specs3,
0,
0,
BLOCK_8X8,
3,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// r307 multi-plane single-ref bridge: a plain single-reference block
/// driven through
/// [`PartitionWalker::reconstruct_inter_block_multiplane_into_curr_frame`]
/// must reconstruct all three planes (Y, Cb, Cr) byte-identically to
/// the per-block [`crate::reconstruct_inter_block`] driver invoked
/// once per plane with the §5.11.34 `predict()` per-plane geometry
/// (`subX/subY`, `baseX/baseY`, `predW/predH`).
#[test]
fn r307_walker_reconstructs_single_ref_block_multiplane() {
use crate::inter_pred::EIGHTTAP;
use crate::{block_height, block_width, InterModeInfo, PlaneRefSpec, RefFrameStoreEntry};
// 4:2:0 reference planes: luma 16×16, chroma 8×8.
let build_plane = |w: usize, h: usize, a: usize, b: usize, c: usize| -> Vec<u16> {
let mut p = vec![0u16; w * h];
for r in 0..h {
for col in 0..w {
p[r * w + col] = ((r * a + col * b + c) & 0xFF) as u16;
}
}
p
};
let ly_w = 16usize;
let ly_h = 16usize;
let lc_w = 8usize;
let lc_h = 8usize;
let ry = build_plane(ly_w, ly_h, 9, 5, 1);
let rcb = build_plane(lc_w, lc_h, 7, 2, 3);
let rcr = build_plane(lc_w, lc_h, 4, 6, 9);
fn mk(p: &[u16], w: usize, h: usize) -> RefFrameStoreEntry<'_> {
RefFrameStoreEntry {
plane: p,
stride: w,
upscaled_width: w as u32,
width: w as u32,
height: h as u32,
}
}
let store_y = [mk(&ry, ly_w, ly_h); 1].repeat(4);
let store_cb = [mk(&rcb, lc_w, lc_h); 1].repeat(4);
let store_cr = [mk(&rcr, lc_w, lc_h); 1].repeat(4);
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 3];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let mode_info = InterModeInfo {
ref_frame: last,
mv: [0, 4],
};
// BLOCK_8X8: luma 8×8, chroma 4×4 (4:2:0). Block at top-left.
let mi_size = BLOCK_8X8;
let lw = block_width(mi_size); // 8
let lh = block_height(mi_size); // 8
let cw = lw >> 1; // 4
let ch = lh >> 1; // 4
// ---- Oracle: per-block driver, three planes. ----
let mut oy = vec![0u16; lw * lh];
let mut ocb = vec![0u16; cw * ch];
let mut ocr = vec![0u16; cw * ch];
crate::reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store_y,
0,
0,
0,
lw,
lh,
8,
0,
0,
ly_w as u32,
ly_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut oy,
lw,
)
.unwrap();
crate::reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store_cb,
1,
0,
0,
cw,
ch,
8,
1,
1,
lc_w as u32,
lc_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut ocb,
cw,
)
.unwrap();
crate::reconstruct_inter_block(
mode_info,
&ref_frame_idx,
&store_cr,
2,
0,
0,
cw,
ch,
8,
1,
1,
lc_w as u32,
lc_h as u32,
EIGHTTAP,
EIGHTTAP,
&mut ocr,
cw,
)
.unwrap();
// ---- Walker multi-plane bridge ----
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let mut walker = PartitionWalker::new(2, 2, geom).unwrap();
assert!(walker.curr_frame(0).is_none());
let spec_y = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store_y,
frame_width: ly_w as u32,
frame_height: ly_h as u32,
};
let spec_cb = PlaneRefSpec {
plane: 1,
subsampling_x: 1,
subsampling_y: 1,
frame_store: &store_cb,
frame_width: lc_w as u32,
frame_height: lc_h as u32,
};
let spec_cr = PlaneRefSpec {
plane: 2,
subsampling_x: 1,
subsampling_y: 1,
frame_store: &store_cr,
frame_width: lc_w as u32,
frame_height: lc_h as u32,
};
let specs = [spec_y, spec_cb, spec_cr];
walker
.reconstruct_inter_block_multiplane_into_curr_frame(
mode_info,
&ref_frame_idx,
&specs,
0,
0,
mi_size,
3,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("walker multi-plane single-ref reconstruction");
let got_y: Vec<u16> = walker
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
let got_cb: Vec<u16> = walker
.curr_frame(1)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
let got_cr: Vec<u16> = walker
.curr_frame(2)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(got_y, oy, "Y plane must match the per-block oracle");
assert_eq!(got_cb, ocb, "Cb plane must match the per-block oracle");
assert_eq!(got_cr, ocr, "Cr plane must match the per-block oracle");
// num_planes == 1 reconstructs only luma; chroma untouched.
let mut wluma = PartitionWalker::new(2, 2, geom).unwrap();
wluma
.reconstruct_inter_block_multiplane_into_curr_frame(
mode_info,
&ref_frame_idx,
&specs,
0,
0,
mi_size,
1,
8,
EIGHTTAP,
EIGHTTAP,
)
.expect("luma-only multi-plane single-ref reconstruction");
let luma_only: Vec<u16> = wluma
.curr_frame(0)
.unwrap()
.iter()
.map(|&s| s as u16)
.collect();
assert_eq!(luma_only, oy, "luma-only Y plane must match the oracle");
assert!(
wluma.curr_frame(1).is_none(),
"num_planes == 1 must leave the chroma planes untouched"
);
}
/// r307 multi-plane single-ref bridge guards: an invalid `num_planes`,
/// an out-of-range `mi_size`, a too-short `plane_specs` slice, and an
/// `INTRA_FRAME` `RefFrame[0]` (rejected per-plane by the driver) are
/// all surfaced as [`crate::Error::PartitionWalkOutOfRange`].
#[test]
fn r307_walker_single_ref_multiplane_bridge_guards() {
use crate::inter_pred::EIGHTTAP;
use crate::{InterModeInfo, PlaneRefSpec, RefFrameStoreEntry};
let refp = vec![0u16; 16 * 16];
let entry = RefFrameStoreEntry {
plane: &refp[..],
stride: 16,
upscaled_width: 16,
width: 16,
height: 16,
};
let store = [entry, entry, entry, entry];
let ref_frame_idx: [u8; 7] = [0, 1, 2, 3, 0, 1, 3];
let last = crate::uncompressed_header_tail::LAST_FRAME as u8;
let good_mode = InterModeInfo {
ref_frame: last,
mv: [0, 0],
};
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 2,
mi_col_start: 0,
mi_col_end: 2,
};
let spec = PlaneRefSpec {
plane: 0,
subsampling_x: 0,
subsampling_y: 0,
frame_store: &store,
frame_width: 16,
frame_height: 16,
};
let specs3 = [
PlaneRefSpec { plane: 0, ..spec },
PlaneRefSpec {
plane: 1,
subsampling_x: 1,
subsampling_y: 1,
..spec
},
PlaneRefSpec {
plane: 2,
subsampling_x: 1,
subsampling_y: 1,
..spec
},
];
// Invalid num_planes (2).
let mut w = PartitionWalker::new(2, 2, geom).unwrap();
assert!(matches!(
w.reconstruct_inter_block_multiplane_into_curr_frame(
good_mode,
&ref_frame_idx,
&specs3,
0,
0,
BLOCK_8X8,
2,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// Out-of-range mi_size.
let mut w2 = PartitionWalker::new(2, 2, geom).unwrap();
assert!(matches!(
w2.reconstruct_inter_block_multiplane_into_curr_frame(
good_mode,
&ref_frame_idx,
&specs3,
0,
0,
BLOCK_SIZES,
3,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// plane_specs too short for num_planes (1 spec, num_planes 3).
let mut w3 = PartitionWalker::new(2, 2, geom).unwrap();
let one_spec = [PlaneRefSpec { plane: 0, ..spec }];
assert!(matches!(
w3.reconstruct_inter_block_multiplane_into_curr_frame(
good_mode,
&ref_frame_idx,
&one_spec,
0,
0,
BLOCK_8X8,
3,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// INTRA_FRAME RefFrame[0] (driver rejects on the first plane).
let mut w4 = PartitionWalker::new(2, 2, geom).unwrap();
let intra_ref0 = InterModeInfo {
ref_frame: 0,
..good_mode
};
assert!(matches!(
w4.reconstruct_inter_block_multiplane_into_curr_frame(
intra_ref0,
&ref_frame_idx,
&specs3,
0,
0,
BLOCK_8X8,
3,
8,
EIGHTTAP,
EIGHTTAP,
),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
// -----------------------------------------------------------------
// §7.11.4 palette prediction process (av1-spec p.286).
// -----------------------------------------------------------------
/// §7.11.4: a single 4×4 transform block at `(x, y) = (0, 0)` of a
/// palette block maps each colour index straight through the
/// palette colour table.
#[test]
fn predict_palette_4x4_top_left_maps_indices_through_palette() {
// 4×4 index map (stride 4): a diagonal index pattern.
let map = [
0u8, 1, 2, 3, //
1, 2, 3, 0, //
2, 3, 0, 1, //
3, 0, 1, 2,
];
let palette = [100u16, 110, 120, 130];
let mut pred = [0u16; 16];
predict_palette(TX_4X4, 0, 0, &map, 4, &palette, &mut pred).unwrap();
for i in 0..4 {
for j in 0..4 {
let idx = map[i * 4 + j] as usize;
assert_eq!(
pred[i * 4 + j],
palette[idx],
"row {i} col {j} = palette[map[{i}][{j}]]"
);
}
}
}
/// §7.11.4: the `(x, y)` 4×4-unit offset windows into the map at
/// `(y*4, x*4)`. A 4×4 transform block at `(x, y) = (1, 1)` reads
/// the bottom-right 4×4 quadrant of an 8×8 map.
#[test]
fn predict_palette_offset_windows_into_map_at_y4_x4() {
// 8×8 map (stride 8): value = row*10 + col, all < palette.len().
let mut map = [0u8; 64];
for r in 0..8usize {
for c in 0..8usize {
map[r * 8 + c] = (r + c) as u8; // 0..=14, fits palette
}
}
// palette[k] = 1000 + k for k in 0..15.
let palette: Vec<u16> = (0..15u16).map(|k| 1000 + k).collect();
let mut pred = [0u16; 16];
// (x, y) = (1, 1) -> window starts at row 4, col 4.
predict_palette(TX_4X4, 1, 1, &map, 8, &palette, &mut pred).unwrap();
for i in 0..4 {
for j in 0..4 {
let map_v = map[(4 + i) * 8 + (4 + j)] as usize;
assert_eq!(
pred[i * 4 + j],
palette[map_v],
"windowed (i={i}, j={j}) reads map[{}][{}]",
4 + i,
4 + j
);
}
}
}
/// §7.11.4: rectangular `TX_4X8` (w=4, h=8) honours `w`/`h` from the
/// `Tx_Width` / `Tx_Height` tables and row-major `pred` stride `w`.
#[test]
fn predict_palette_rectangular_4x8_uses_tx_tables() {
// Map at least 8 rows × 4 cols (stride 4).
let mut map = [0u8; 32];
for r in 0..8usize {
for c in 0..4usize {
map[r * 4 + c] = ((r + c) % 3) as u8;
}
}
let palette = [7u16, 8, 9];
let mut pred = [0u16; 32];
predict_palette(TX_4X8, 0, 0, &map, 4, &palette, &mut pred).unwrap();
// w=4, h=8.
for i in 0..8 {
for j in 0..4 {
let idx = map[i * 4 + j] as usize;
assert_eq!(pred[i * 4 + j], palette[idx], "row {i} col {j}");
}
}
}
/// §7.11.4: caller-bug argument guards all return
/// `PartitionWalkOutOfRange`.
#[test]
fn predict_palette_rejects_bad_arguments() {
let map = [0u8; 16];
let palette = [5u16, 6];
let mut pred = [0u16; 16];
// txsz out of range.
assert!(matches!(
predict_palette(TX_SIZES_ALL, 0, 0, &map, 4, &palette, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// empty palette.
assert!(matches!(
predict_palette(TX_4X4, 0, 0, &map, 4, &[], &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// pred too short.
let mut short_pred = [0u16; 15];
assert!(matches!(
predict_palette(TX_4X4, 0, 0, &map, 4, &palette, &mut short_pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// stride < w.
assert!(matches!(
predict_palette(TX_4X4, 0, 0, &map, 3, &palette, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// map window exceeds map.len() (offset (1,1) needs an 8×8 map
// but only a 4×4 is supplied at stride 4).
assert!(matches!(
predict_palette(TX_4X4, 1, 1, &map, 4, &palette, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
// index >= palette.len(): map value 3 with a 2-colour palette.
let bad_map = [0u8, 1, 0, 3, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1];
assert!(matches!(
predict_palette(TX_4X4, 0, 0, &bad_map, 4, &palette, &mut pred),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
/// §7.11.4 walker bridge: a single 4×4 palette block (TX_4X4, one
/// transform block) on luma reconstructs `CurrFrame[0][i][j] =
/// palette[ColorMapY[i][j]]` into the walker's own buffer.
#[test]
fn reconstruct_palette_block_single_tu_luma() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// §5.11.46 palette stamped at the block anchor (0, 0): 3 colours.
walker
.stamp_palette_for_test(0, 0, 0, &[11, 22, 33])
.unwrap();
// §5.11.49 4×4 colour-index map, indices into the 3-colour
// palette.
let map = DecodedPaletteMap {
data: vec![0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0],
stride: 4,
block_w: 4,
block_h: 4,
onscreen_w: 4,
onscreen_h: 4,
};
walker
.reconstruct_palette_block_into_curr_frame(0, &map, 0, 0, TX_4X4, 0, 0, 3)
.expect("single-TU palette reconstruction");
let cf = walker.curr_frame(0).unwrap();
let cols = walker.curr_frame_dims(0).unwrap().1 as usize;
// Frame is 4 mi × MI_SIZE = 16 samples wide; the block occupies
// the top-left 4×4 only.
let pal = [11i32, 22, 33];
for i in 0..4usize {
for j in 0..4usize {
let idx = map.data[i * 4 + j] as usize;
assert_eq!(cf[i * cols + j], pal[idx], "({i},{j}) must be palette[map]");
}
}
// Samples outside the 4×4 block stay zero.
assert_eq!(cf[5], 0, "sample outside the block is untouched");
}
/// §7.11.4 walker bridge: an 8×8 palette block tiled by four TX_4X4
/// transform blocks reconstructs each transform block at its
/// `(startX, startY) = (4*x, 4*y)` offset, with each TU windowing
/// into its own 4×4 region of the 8×8 ColorMap.
#[test]
fn reconstruct_palette_block_multi_tu_tiling() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
walker
.stamp_palette_for_test(0, 0, 0, &[7, 70, 140])
.unwrap();
// 8×8 map (stride 8): increasing indices mod 3.
let mut data = vec![0u8; 64];
for (k, slot) in data.iter_mut().enumerate() {
*slot = (k % 3) as u8;
}
let map = DecodedPaletteMap {
data: data.clone(),
stride: 8,
block_w: 8,
block_h: 8,
onscreen_w: 8,
onscreen_h: 8,
};
walker
.reconstruct_palette_block_into_curr_frame(0, &map, 0, 0, TX_4X4, 0, 0, 3)
.expect("multi-TU palette reconstruction");
let cf = walker.curr_frame(0).unwrap();
let cols = walker.curr_frame_dims(0).unwrap().1 as usize;
let pal = [7i32, 70, 140];
// Every sample of the 8×8 block must equal palette[map[i][j]]:
// the four TX_4X4 tiles together cover the whole 8×8 map.
for i in 0..8usize {
for j in 0..8usize {
let idx = map.data[i * map.stride + j] as usize;
assert_eq!(
cf[i * cols + j],
pal[idx],
"({i},{j}) palette sample after 2×2 TU tiling"
);
}
}
}
/// §7.11.4 walker bridge: a 4:2:0 chroma plane (sub_x = sub_y = 1)
/// reads ColorMapUV + its own palette_colors_v and writes at the
/// subsampled origin `(MiCol >> 1) * MI_SIZE`.
#[test]
fn reconstruct_palette_block_chroma_subsampled() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
// V plane (plane 2) palette at (0, 0).
walker.stamp_palette_for_test(2, 0, 0, &[3, 9]).unwrap();
let map = DecodedPaletteMap {
data: vec![0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1],
stride: 4,
block_w: 4,
block_h: 4,
onscreen_w: 4,
onscreen_h: 4,
};
walker
.reconstruct_palette_block_into_curr_frame(2, &map, 0, 0, TX_4X4, 1, 1, 2)
.expect("chroma palette reconstruction");
let cf = walker.curr_frame(2).unwrap();
let (rows, cols) = walker.curr_frame_dims(2).unwrap();
// 4:2:0 ⇒ chroma plane is (4 mi * 4) >> 1 = 8 samples per axis.
assert_eq!((rows, cols), (8, 8));
let pal = [3i32, 9];
for i in 0..4usize {
for j in 0..4usize {
let idx = map.data[i * 4 + j] as usize;
assert_eq!(cf[i * cols as usize + j], pal[idx]);
}
}
}
/// §7.11.4 walker bridge guards: plane >= 3, tx_sz out of range,
/// palette_size outside 1..=PALETTE_COLORS, and a malformed map
/// (stride < block_w) all surface PartitionWalkOutOfRange.
#[test]
fn reconstruct_palette_block_guards() {
let geom = TileGeometry {
mi_row_start: 0,
mi_row_end: 4,
mi_col_start: 0,
mi_col_end: 4,
};
let mut walker = PartitionWalker::new(4, 4, geom).unwrap();
let map = DecodedPaletteMap {
data: vec![0u8; 16],
stride: 4,
block_w: 4,
block_h: 4,
onscreen_w: 4,
onscreen_h: 4,
};
// plane >= 3.
assert!(matches!(
walker.reconstruct_palette_block_into_curr_frame(3, &map, 0, 0, TX_4X4, 0, 0, 2),
Err(crate::Error::PartitionWalkOutOfRange)
));
// tx_sz out of range.
assert!(matches!(
walker.reconstruct_palette_block_into_curr_frame(0, &map, 0, 0, TX_SIZES_ALL, 0, 0, 2),
Err(crate::Error::PartitionWalkOutOfRange)
));
// palette_size == 0.
assert!(matches!(
walker.reconstruct_palette_block_into_curr_frame(0, &map, 0, 0, TX_4X4, 0, 0, 0),
Err(crate::Error::PartitionWalkOutOfRange)
));
// palette_size > PALETTE_COLORS.
assert!(matches!(
walker.reconstruct_palette_block_into_curr_frame(
0,
&map,
0,
0,
TX_4X4,
0,
0,
PALETTE_COLORS + 1
),
Err(crate::Error::PartitionWalkOutOfRange)
));
// malformed map: stride < block_w.
let bad_map = DecodedPaletteMap {
data: vec![0u8; 16],
stride: 3,
block_w: 4,
block_h: 4,
onscreen_w: 4,
onscreen_h: 4,
};
assert!(matches!(
walker.reconstruct_palette_block_into_curr_frame(0, &bad_map, 0, 0, TX_4X4, 0, 0, 2),
Err(crate::Error::PartitionWalkOutOfRange)
));
}
}