use crate::cdf::{
block_height, block_width, cfl_allowed_for_uv_mode, compute_tx_type, find_tx_size,
get_plane_residual_size, get_tx_size, inter_tx_type_set, intra_mode_ctx, intra_tx_type_set,
is_inter_ctx, palette_tokens_args, partition_ctx, partition_subsize, segment_id_ctx,
size_group, skip_ctx, skip_mode_ctx, tx_size_sqr_index, EncoderBlockSyntaxStamp,
FrameInterOrderHints, InterIntraReadout, InterpolationFilterReadout, MotionFieldMvs,
PalettePlane, PartitionWalker, TileCdfContext, TileGeometry, BLOCK_4X4, BLOCK_64X64, BLOCK_8X8,
BLOCK_INVALID, BLOCK_SIZES, DCT_DCT, DC_PRED, EIGHTTAP, FRAME_LF_COUNT, GM_TYPE_IDENTITY,
GM_TYPE_TRANSLATION, H_ADST, H_DCT, H_FLIPADST, MAX_SEGMENTS, MAX_TX_SIZE_RECT,
MAX_VARTX_DEPTH, MI_HEIGHT_LOG2, MI_SIZE, MI_SIZE_LOG2, MI_WIDTH_LOG2, MODE_GLOBALMV,
MODE_GLOBAL_GLOBALMV, MODE_NEARESTMV, MODE_NEAREST_NEARESTMV, MODE_NEARMV, MODE_NEWMV,
MODE_NEW_NEWMV, MOTION_MODE_SIMPLE, NUM_4X4_BLOCKS_HIGH, NUM_4X4_BLOCKS_WIDE, PALETTE_COLORS,
PARTITION_HORZ, PARTITION_HORZ_4, PARTITION_HORZ_A, PARTITION_HORZ_B, PARTITION_NONE,
PARTITION_SPLIT, PARTITION_VERT, PARTITION_VERT_4, PARTITION_VERT_A, PARTITION_VERT_B,
SPLIT_TX_SIZE, SWITCHABLE, TX_4X4, TX_CLASS_2D, TX_CLASS_HORIZ, TX_CLASS_VERT, TX_HEIGHT,
TX_SIZES_ALL, TX_SIZE_SQR_UP, TX_WIDTH, UV_CFL_PRED, V_ADST, V_DCT, V_FLIPADST,
WARPEDMODEL_PREC_BITS,
};
use crate::encoder::block_mode_info::{
assign_mv_pred_mv, write_cdef, write_cfl_alphas, write_delta_lf, write_delta_qindex,
write_inter_block_mode_info, write_inter_frame_mode_info_prefix,
write_intra_block_mode_info_with_palette, write_intra_frame_else_arm,
write_intra_frame_intrabc_arm, write_intra_frame_y_mode, write_intra_segment_id,
write_intra_uv_mode, write_palette_tokens_plane, write_skip, write_y_mode,
InterBlockModeInfoTail, InterFrameDeltaSiteInputs, IntrabcArmInputs,
};
use crate::encoder::coefficients::write_coefficients;
use crate::encoder::partition::write_partition;
use crate::encoder::symbol_writer::SymbolWriter;
use crate::encoder::transform_tree::write_block_tx_size;
use crate::Error;
#[derive(Debug, Clone)]
pub struct PlaneCoefficients {
pub plane: u8,
pub is_inter: u8,
pub tx_size: usize,
pub tx_class: usize,
pub txb_skip_ctx: usize,
pub dc_sign_ctx: usize,
pub scan: Vec<u16>,
pub quant: Vec<i32>,
}
#[derive(Debug, Clone)]
pub struct EncodeBlock {
pub skip: u8,
pub segment_id: u8,
pub segment_pred: u8,
pub y_mode: u8,
pub uv_mode: Option<u8>,
pub cfl_alpha_u: Option<i8>,
pub cfl_alpha_v: Option<i8>,
pub coefficients: Vec<PlaneCoefficients>,
}
#[derive(Debug, Clone)]
pub enum EncodeNode {
Leaf(EncodeBlock),
Split([Box<EncodeNode>; 4]),
}
impl EncodeNode {
#[must_use]
pub fn dummy_oob() -> Self {
EncodeNode::Leaf(EncodeBlock {
skip: 0,
segment_id: 0,
segment_pred: 0,
y_mode: 0,
uv_mode: None,
cfl_alpha_u: None,
cfl_alpha_v: None,
coefficients: Vec::new(),
})
}
}
#[derive(Debug)]
pub struct PartitionTreeWriter {
mi_rows: u32,
mi_cols: u32,
geometry: TileGeometry,
mi_sizes: Vec<usize>,
segmentation_enabled: bool,
last_active_seg_id: u8,
lossless: bool,
subsampling_x: bool,
subsampling_y: bool,
}
impl PartitionTreeWriter {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
mi_rows: u32,
mi_cols: u32,
geometry: TileGeometry,
segmentation_enabled: bool,
last_active_seg_id: u8,
lossless: bool,
subsampling_x: bool,
subsampling_y: bool,
) -> Option<Self> {
if mi_rows == 0 || mi_cols == 0 {
return None;
}
if geometry.mi_row_end > mi_rows || geometry.mi_col_end > mi_cols {
return None;
}
if geometry.mi_row_start >= geometry.mi_row_end
|| geometry.mi_col_start >= geometry.mi_col_end
{
return None;
}
Some(Self {
mi_rows,
mi_cols,
geometry,
mi_sizes: vec![BLOCK_INVALID; (mi_rows * mi_cols) as usize],
segmentation_enabled,
last_active_seg_id,
lossless,
subsampling_x,
subsampling_y,
})
}
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)
}
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]
}
fn stamp_mi_sizes(&mut self, r: u32, c: u32, sub_size: usize) {
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;
}
}
}
}
pub fn write_partition_tree(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionTreeWriter,
node: &EncodeNode,
r: u32,
c: u32,
b_size: usize,
) -> Result<(), Error> {
if r >= state.mi_rows || c >= state.mi_cols {
return Ok(());
}
if b_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
let num4x4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let half_block4x4 = num4x4 >> 1;
let has_rows = (r + half_block4x4) < state.mi_rows;
let has_cols = (c + half_block4x4) < state.mi_cols;
let partition = match node {
EncodeNode::Leaf(_) => {
if b_size >= BLOCK_8X8 && !has_rows && !has_cols {
return Err(Error::PartitionWalkOutOfRange);
}
PARTITION_NONE
}
EncodeNode::Split(_) => {
if b_size < BLOCK_8X8 {
return Err(Error::PartitionWalkOutOfRange);
}
PARTITION_SPLIT
}
};
let pctx = if b_size >= BLOCK_8X8 {
let bsl = MI_WIDTH_LOG2[b_size] as u32;
state.partition_ctx_for(r, c, bsl)
} else {
0
};
write_partition(writer, cdfs, partition, b_size, has_rows, has_cols, pctx)?;
let sub_size = partition_subsize(partition, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
match (node, partition) {
(EncodeNode::Leaf(block), PARTITION_NONE) => {
state.stamp_mi_sizes(r, c, sub_size);
write_encode_block_leaf(writer, cdfs, state, block, r, c, sub_size)?;
}
(EncodeNode::Split(children), PARTITION_SPLIT) => {
let [nw, ne, sw, se] = children;
write_partition_tree(writer, cdfs, state, nw, r, c, sub_size)?;
write_partition_tree(writer, cdfs, state, ne, r, c + half_block4x4, sub_size)?;
write_partition_tree(writer, cdfs, state, sw, r + half_block4x4, c, sub_size)?;
write_partition_tree(
writer,
cdfs,
state,
se,
r + half_block4x4,
c + half_block4x4,
sub_size,
)?;
}
_ => return Err(Error::PartitionWalkOutOfRange),
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_encode_block_leaf(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionTreeWriter,
block: &EncodeBlock,
_r: u32,
_c: u32,
sub_size: usize,
) -> Result<(), Error> {
let skip_ctx_val = skip_ctx(0, 0);
write_skip(writer, cdfs, block.skip, skip_ctx_val, false)?;
write_intra_segment_id(
writer,
cdfs,
block.segment_id,
block.skip,
block.segment_pred,
0,
state.segmentation_enabled,
state.last_active_seg_id,
)?;
let size_group_ctx = size_group(sub_size);
write_y_mode(writer, cdfs, block.y_mode, size_group_ctx)?;
let _ = write_intra_frame_y_mode;
let _ = intra_mode_ctx;
let _ = DC_PRED;
if let Some(uv_mode) = block.uv_mode {
let cfl_allowed = cfl_allowed_for_uv_mode(
state.lossless,
sub_size,
state.subsampling_x,
state.subsampling_y,
);
write_intra_uv_mode(writer, cdfs, block.y_mode, uv_mode, cfl_allowed)?;
if uv_mode as usize == UV_CFL_PRED {
let alpha_u = block.cfl_alpha_u.ok_or(Error::PartitionWalkOutOfRange)?;
let alpha_v = block.cfl_alpha_v.ok_or(Error::PartitionWalkOutOfRange)?;
write_cfl_alphas(writer, cdfs, alpha_u, alpha_v)?;
}
}
for plane_in in &block.coefficients {
write_coefficients(
writer,
cdfs,
plane_in.plane,
plane_in.is_inter,
plane_in.tx_size,
plane_in.tx_class,
plane_in.txb_skip_ctx,
plane_in.dc_sign_ctx,
&plane_in.scan,
&plane_in.quant,
)?;
}
Ok(())
}
#[derive(Debug, Clone, Default)]
pub struct SyntaxPalette {
pub size_y: u8,
pub colors_y: [u16; PALETTE_COLORS],
pub color_map_y: Vec<u8>,
pub size_uv: u8,
pub colors_u: [u16; PALETTE_COLORS],
pub colors_v: [u16; PALETTE_COLORS],
pub delta_encode_v: bool,
pub color_map_uv: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TuQuant {
len: u32,
nz: Vec<(u16, i32)>,
}
impl TuQuant {
#[must_use]
pub fn len(&self) -> usize {
self.len as usize
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn is_all_zero(&self) -> bool {
self.nz.is_empty()
}
#[must_use]
pub fn nonzero_count(&self) -> usize {
self.nz.len()
}
#[must_use]
pub fn nonzeros(&self) -> &[(u16, i32)] {
&self.nz
}
#[must_use]
pub fn dense(&self) -> Vec<i32> {
let mut v = vec![0i32; self.len as usize];
for &(i, q) in &self.nz {
v[i as usize] = q;
}
v
}
}
impl From<Vec<i32>> for TuQuant {
fn from(v: Vec<i32>) -> Self {
Self::from(&v[..])
}
}
impl From<&[i32]> for TuQuant {
fn from(v: &[i32]) -> Self {
debug_assert!(v.len() <= u16::MAX as usize + 1);
Self {
len: v.len() as u32,
nz: v
.iter()
.enumerate()
.filter(|(_, &q)| q != 0)
.map(|(i, &q)| (i as u16, q))
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TuQuantList(pub Vec<TuQuant>);
impl TuQuantList {
#[must_use]
pub fn as_slice(&self) -> &[TuQuant] {
&self.0
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn get(&self, i: usize) -> Option<&TuQuant> {
self.0.get(i)
}
pub fn iter(&self) -> core::slice::Iter<'_, TuQuant> {
self.0.iter()
}
pub fn clear(&mut self) {
self.0.clear();
}
}
impl From<Vec<Vec<i32>>> for TuQuantList {
fn from(v: Vec<Vec<i32>>) -> Self {
Self(v.into_iter().map(TuQuant::from).collect())
}
}
impl<'a> IntoIterator for &'a TuQuantList {
type Item = &'a TuQuant;
type IntoIter = core::slice::Iter<'a, TuQuant>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[derive(Debug, Clone)]
pub struct SyntaxBlock {
pub skip: u8,
pub segment_id: u8,
pub cdef_idx: i8,
pub reduced_delta_q_index: i32,
pub reduced_delta_lf: [i32; FRAME_LF_COUNT],
pub intrabc_mv: Option<[i32; 2]>,
pub y_mode: u8,
pub uv_mode: Option<u8>,
pub angle_delta_y: i8,
pub angle_delta_uv: i8,
pub cfl_alpha_u: Option<i8>,
pub cfl_alpha_v: Option<i8>,
pub use_filter_intra: u8,
pub filter_intra_mode: Option<u8>,
pub palette: SyntaxPalette,
pub residual_quant: TuQuantList,
pub tx_size: Option<u8>,
pub residual_tx_type: Vec<u8>,
pub var_tx_trees: Vec<VarTxSyntaxTree>,
pub inter: Option<SyntaxInterBlock>,
}
#[derive(Debug, Clone)]
pub struct SyntaxInterBlock {
pub ref_frame: [i8; 2],
pub y_mode: u8,
pub mv: [[i32; 2]; 2],
pub ref_mv_idx: u32,
pub interp_filter: [u8; 2],
pub skip_mode: u8,
pub compound_type: u8,
pub wedge_index: u8,
pub wedge_sign: u8,
pub mask_type: u8,
pub interintra_mode: Option<u8>,
pub wedge_interintra: u8,
pub interintra_wedge_index: u8,
pub motion_mode: u8,
}
#[derive(Debug, Clone)]
pub struct SyntaxInterFrameParams {
pub skip_mode_present: bool,
pub skip_mode_frame: [i8; 2],
pub reference_select: bool,
pub gm_type: [i32; 8],
pub gm_params: [[i32; 6]; 8],
pub ref_frame_sign_bias: [i32; 8],
pub allow_high_precision_mv: bool,
pub force_integer_mv: bool,
pub use_ref_frame_mvs: bool,
pub is_motion_mode_switchable: bool,
pub segmentation_update_map: bool,
pub segmentation_temporal_update: bool,
pub prev_segment_ids: Option<std::sync::Arc<Vec<i32>>>,
pub allow_warped_motion: bool,
pub is_scaled_per_ref: [bool; 7],
pub enable_interintra_compound: bool,
pub enable_masked_compound: bool,
pub enable_jnt_comp: bool,
pub order_hints: FrameInterOrderHints,
pub interpolation_filter: u8,
pub enable_dual_filter: bool,
pub motion_field_mvs: MotionFieldMvs,
}
impl SyntaxInterFrameParams {
#[must_use]
pub fn single_ref_baseline(mi_rows: u32, mi_cols: u32, force_integer_mv: bool) -> Self {
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;
}
SyntaxInterFrameParams {
skip_mode_present: false,
skip_mode_frame: [0, 0],
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,
use_ref_frame_mvs: false,
is_motion_mode_switchable: false,
segmentation_update_map: false,
segmentation_temporal_update: false,
prev_segment_ids: None,
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,
enable_dual_filter: false,
motion_field_mvs: MotionFieldMvs::new_invalid(mi_rows, mi_cols),
}
}
}
#[derive(Debug, Clone)]
pub enum VarTxSyntaxTree {
Leaf,
Split(Vec<VarTxSyntaxTree>),
}
impl SyntaxBlock {
#[must_use]
pub fn skip_leaf(y_mode: u8, uv_mode: Option<u8>) -> Self {
SyntaxBlock {
skip: 1,
segment_id: 0,
cdef_idx: 0,
reduced_delta_q_index: 0,
reduced_delta_lf: [0; FRAME_LF_COUNT],
intrabc_mv: None,
y_mode,
uv_mode,
angle_delta_y: 0,
angle_delta_uv: 0,
cfl_alpha_u: None,
cfl_alpha_v: None,
use_filter_intra: 0,
filter_intra_mode: None,
palette: SyntaxPalette::default(),
residual_quant: TuQuantList::default(),
tx_size: None,
residual_tx_type: Vec::new(),
var_tx_trees: Vec::new(),
inter: None,
}
}
}
#[derive(Debug, Clone)]
pub enum SyntaxNode {
Leaf(Box<SyntaxBlock>),
Split([Box<SyntaxNode>; 4]),
Horz([Box<SyntaxBlock>; 2]),
Vert([Box<SyntaxBlock>; 2]),
HorzA([Box<SyntaxBlock>; 3]),
HorzB([Box<SyntaxBlock>; 3]),
VertA([Box<SyntaxBlock>; 3]),
VertB([Box<SyntaxBlock>; 3]),
Horz4([Box<SyntaxBlock>; 4]),
Vert4([Box<SyntaxBlock>; 4]),
}
impl SyntaxNode {
#[must_use]
pub fn dummy_oob() -> Self {
SyntaxNode::Leaf(Box::new(SyntaxBlock::skip_leaf(DC_PRED as u8, None)))
}
#[must_use]
pub fn asymmetric_blocks(&self) -> &[Box<SyntaxBlock>] {
match self {
SyntaxNode::Leaf(_) | SyntaxNode::Split(_) => &[],
SyntaxNode::Horz(b) | SyntaxNode::Vert(b) => b,
SyntaxNode::HorzA(b)
| SyntaxNode::HorzB(b)
| SyntaxNode::VertA(b)
| SyntaxNode::VertB(b) => b,
SyntaxNode::Horz4(b) | SyntaxNode::Vert4(b) => b,
}
}
pub fn stamp_cdef_idx(&mut self, idx: i8) {
match self {
SyntaxNode::Leaf(b) => b.cdef_idx = idx,
SyntaxNode::Split(q) => {
for n in q.iter_mut() {
n.stamp_cdef_idx(idx);
}
}
SyntaxNode::Horz(b) | SyntaxNode::Vert(b) => {
for blk in b.iter_mut() {
blk.cdef_idx = idx;
}
}
SyntaxNode::HorzA(b)
| SyntaxNode::HorzB(b)
| SyntaxNode::VertA(b)
| SyntaxNode::VertB(b) => {
for blk in b.iter_mut() {
blk.cdef_idx = idx;
}
}
SyntaxNode::Horz4(b) | SyntaxNode::Vert4(b) => {
for blk in b.iter_mut() {
blk.cdef_idx = idx;
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct SyntaxFrameParams {
pub subsampling_x: u8,
pub subsampling_y: u8,
pub num_planes: u8,
pub seg_id_pre_skip: bool,
pub segmentation_enabled: bool,
pub seg_ref_frame: [Option<i8>; MAX_SEGMENTS],
pub seg_skip: [bool; MAX_SEGMENTS],
pub seg_globalmv: [bool; MAX_SEGMENTS],
pub last_active_seg_id: u8,
pub lossless_array: [bool; MAX_SEGMENTS],
pub coded_lossless: bool,
pub enable_cdef: bool,
pub allow_intrabc: bool,
pub cdef_bits: u32,
pub read_deltas: bool,
pub use_128x128_superblock: bool,
pub delta_q_res: u8,
pub delta_lf_present: bool,
pub delta_lf_multi: bool,
pub mono_chrome: bool,
pub delta_lf_res: u8,
pub allow_screen_content_tools: bool,
pub enable_filter_intra: bool,
pub bit_depth: u8,
pub tx_mode_select: bool,
pub quant: crate::cdf::QuantizerParams,
pub reduced_tx_set: bool,
pub inter: Option<SyntaxInterFrameParams>,
}
impl SyntaxFrameParams {
#[must_use]
pub fn intra_8bit_baseline() -> Self {
SyntaxFrameParams {
subsampling_x: 0,
subsampling_y: 0,
num_planes: 3,
seg_id_pre_skip: false,
segmentation_enabled: false,
seg_ref_frame: [None; MAX_SEGMENTS],
seg_skip: [false; MAX_SEGMENTS],
seg_globalmv: [false; MAX_SEGMENTS],
last_active_seg_id: 0,
lossless_array: [false; MAX_SEGMENTS],
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,
allow_screen_content_tools: false,
enable_filter_intra: false,
bit_depth: 8,
tx_mode_select: false,
quant: crate::cdf::QuantizerParams::neutral(0, 8),
reduced_tx_set: false,
inter: None,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct WriterScopeSnapshot {
mirror: crate::cdf::PriceScopeSnapshot,
write_deltas_pending: bool,
}
#[derive(Debug, Clone)]
pub struct PartitionSyntaxWriter {
mi_rows: u32,
mi_cols: u32,
geometry: TileGeometry,
mirror: PartitionWalker,
write_deltas_pending: bool,
}
impl PartitionSyntaxWriter {
#[must_use]
pub fn new(mi_rows: u32, mi_cols: u32, geometry: TileGeometry) -> Option<Self> {
if geometry.mi_row_end > mi_rows || geometry.mi_col_end > mi_cols {
return None;
}
let mirror = PartitionWalker::new(mi_rows, mi_cols, geometry)?;
Some(Self {
mi_rows,
mi_cols,
geometry,
mirror,
write_deltas_pending: true,
})
}
pub fn deltas_pending(&self) -> bool {
self.write_deltas_pending
}
pub(crate) fn import_tile_from(
&mut self,
src: &Self,
geo: TileGeometry,
subsampling_x: u8,
subsampling_y: u8,
) {
self.mirror.import_rect_from(
&src.mirror,
0,
0,
geo.mi_row_start,
geo.mi_col_start,
geo.mi_row_end - geo.mi_row_start,
geo.mi_col_end - geo.mi_col_start,
subsampling_x,
subsampling_y,
);
}
#[doc(hidden)]
pub fn differing_fields(&self, other: &Self) -> Vec<&'static str> {
let mut out = self.mirror.differing_fields(&other.mirror);
if self.geometry != other.geometry {
out.push("writer.geometry");
}
if self.write_deltas_pending != other.write_deltas_pending {
out.push("writer.write_deltas_pending");
}
out
}
pub fn begin_tile(&mut self, geometry: TileGeometry) -> bool {
if geometry.mi_row_end > self.mi_rows
|| geometry.mi_col_end > self.mi_cols
|| geometry.mi_row_start >= geometry.mi_row_end
|| geometry.mi_col_start >= geometry.mi_col_end
{
return false;
}
self.geometry = geometry;
self.mirror.begin_tile(geometry);
self.write_deltas_pending = true;
true
}
pub fn arm_read_deltas(&mut self) {
self.write_deltas_pending = true;
}
pub(crate) fn snapshot_price_scope(
&self,
mi_row: u32,
mi_col: u32,
w4: u32,
h4: u32,
params: &SyntaxFrameParams,
) -> WriterScopeSnapshot {
WriterScopeSnapshot {
mirror: self.mirror.snapshot_price_scope(
mi_row,
mi_col,
w4,
h4,
params.subsampling_x,
params.subsampling_y,
),
write_deltas_pending: self.write_deltas_pending,
}
}
pub(crate) fn restore_price_scope(&mut self, snap: &WriterScopeSnapshot) {
self.mirror.restore_price_scope(&snap.mirror);
self.write_deltas_pending = snap.write_deltas_pending;
}
#[must_use]
pub fn mirror(&self) -> &PartitionWalker {
&self.mirror
}
fn partition_ctx_for(&self, r: u32, c: u32, bsl: u32) -> usize {
let sizes = self.mirror.mi_sizes();
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 = avail_u && {
let nb = sizes[((r - 1) * self.mi_cols + c) as usize];
nb < BLOCK_SIZES && (MI_WIDTH_LOG2[nb] as u32) < bsl
};
let left = avail_l && {
let nb = sizes[(r * self.mi_cols + c - 1) as usize];
nb < BLOCK_SIZES && (MI_HEIGHT_LOG2[nb] as u32) < bsl
};
partition_ctx(above, left)
}
pub(crate) fn segment_pred_ctx(&self, mi_row: u32, mi_col: u32) -> (u8, usize) {
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 at = |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.mirror.segment_ids()[(r * self.mi_cols + c) as usize]
};
let prev_ul = if avail_u && avail_l {
at(mi_row as i32 - 1, mi_col as i32 - 1)
} else {
-1
};
let prev_u = if avail_u {
at(mi_row as i32 - 1, mi_col as i32)
} else {
-1
};
let prev_l = if avail_l {
at(mi_row as i32, mi_col as i32 - 1)
} else {
-1
};
#[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
};
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));
(pred as u8, ctx)
}
}
#[allow(clippy::too_many_arguments)]
pub fn write_partition_tree_syntax(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
node: &SyntaxNode,
r: u32,
c: u32,
b_size: usize,
params: &SyntaxFrameParams,
) -> Result<(), Error> {
if r >= state.mi_rows || c >= state.mi_cols {
return Ok(());
}
if b_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
let num4x4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let half_block4x4 = num4x4 >> 1;
let has_rows = (r + half_block4x4) < state.mi_rows;
let has_cols = (c + half_block4x4) < state.mi_cols;
let partition = match node {
SyntaxNode::Leaf(_) => {
if b_size >= BLOCK_8X8 && !has_rows && !has_cols {
return Err(Error::PartitionWalkOutOfRange);
}
PARTITION_NONE
}
SyntaxNode::Split(_) => {
if b_size < BLOCK_8X8 {
return Err(Error::PartitionWalkOutOfRange);
}
PARTITION_SPLIT
}
SyntaxNode::Horz(_) => {
if b_size < BLOCK_8X8 || !has_cols {
return Err(Error::PartitionWalkOutOfRange);
}
PARTITION_HORZ
}
SyntaxNode::Vert(_) => {
if b_size < BLOCK_8X8 || !has_rows {
return Err(Error::PartitionWalkOutOfRange);
}
PARTITION_VERT
}
SyntaxNode::HorzA(_)
| SyntaxNode::HorzB(_)
| SyntaxNode::VertA(_)
| SyntaxNode::VertB(_)
| SyntaxNode::Horz4(_)
| SyntaxNode::Vert4(_) => {
if b_size <= BLOCK_8X8 || !has_rows || !has_cols {
return Err(Error::PartitionWalkOutOfRange);
}
match node {
SyntaxNode::HorzA(_) => PARTITION_HORZ_A,
SyntaxNode::HorzB(_) => PARTITION_HORZ_B,
SyntaxNode::VertA(_) => PARTITION_VERT_A,
SyntaxNode::VertB(_) => PARTITION_VERT_B,
SyntaxNode::Horz4(_) => PARTITION_HORZ_4,
_ => PARTITION_VERT_4,
}
}
};
write_partition_symbol(writer, cdfs, state, partition, r, c, b_size)?;
let sub_size = partition_subsize(partition, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
match (node, partition) {
(SyntaxNode::Leaf(block), PARTITION_NONE) => {
write_block_syntax(writer, cdfs, state, block, r, c, sub_size, params)?;
}
(SyntaxNode::Horz(blocks), PARTITION_HORZ) => {
let [top, bottom] = blocks;
write_block_syntax(writer, cdfs, state, top, r, c, sub_size, params)?;
if has_rows {
write_block_syntax(
writer,
cdfs,
state,
bottom,
r + half_block4x4,
c,
sub_size,
params,
)?;
}
}
(SyntaxNode::Vert(blocks), PARTITION_VERT) => {
let [left, right] = blocks;
write_block_syntax(writer, cdfs, state, left, r, c, sub_size, params)?;
if has_cols {
write_block_syntax(
writer,
cdfs,
state,
right,
r,
c + half_block4x4,
sub_size,
params,
)?;
}
}
(SyntaxNode::HorzA(blocks), PARTITION_HORZ_A) => {
let split_size =
partition_subsize(PARTITION_SPLIT, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
let [a, b, cbl] = blocks;
write_block_syntax(writer, cdfs, state, a, r, c, split_size, params)?;
write_block_syntax(
writer,
cdfs,
state,
b,
r,
c + half_block4x4,
split_size,
params,
)?;
write_block_syntax(
writer,
cdfs,
state,
cbl,
r + half_block4x4,
c,
sub_size,
params,
)?;
}
(SyntaxNode::HorzB(blocks), PARTITION_HORZ_B) => {
let split_size =
partition_subsize(PARTITION_SPLIT, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
let [a, b, cbl] = blocks;
write_block_syntax(writer, cdfs, state, a, r, c, sub_size, params)?;
write_block_syntax(
writer,
cdfs,
state,
b,
r + half_block4x4,
c,
split_size,
params,
)?;
write_block_syntax(
writer,
cdfs,
state,
cbl,
r + half_block4x4,
c + half_block4x4,
split_size,
params,
)?;
}
(SyntaxNode::VertA(blocks), PARTITION_VERT_A) => {
let split_size =
partition_subsize(PARTITION_SPLIT, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
let [a, b, cbl] = blocks;
write_block_syntax(writer, cdfs, state, a, r, c, split_size, params)?;
write_block_syntax(
writer,
cdfs,
state,
b,
r + half_block4x4,
c,
split_size,
params,
)?;
write_block_syntax(
writer,
cdfs,
state,
cbl,
r,
c + half_block4x4,
sub_size,
params,
)?;
}
(SyntaxNode::VertB(blocks), PARTITION_VERT_B) => {
let split_size =
partition_subsize(PARTITION_SPLIT, b_size).ok_or(Error::PartitionWalkOutOfRange)?;
let [a, b, cbl] = blocks;
write_block_syntax(writer, cdfs, state, a, r, c, sub_size, params)?;
write_block_syntax(
writer,
cdfs,
state,
b,
r,
c + half_block4x4,
split_size,
params,
)?;
write_block_syntax(
writer,
cdfs,
state,
cbl,
r + half_block4x4,
c + half_block4x4,
split_size,
params,
)?;
}
(SyntaxNode::Horz4(blocks), PARTITION_HORZ_4) => {
let quarter = half_block4x4 >> 1;
for (i, blk) in blocks.iter().enumerate() {
let rr = r + quarter * (i as u32);
if i == 3 && rr >= state.mi_rows {
break;
}
write_block_syntax(writer, cdfs, state, blk, rr, c, sub_size, params)?;
}
}
(SyntaxNode::Vert4(blocks), PARTITION_VERT_4) => {
let quarter = half_block4x4 >> 1;
for (i, blk) in blocks.iter().enumerate() {
let cc = c + quarter * (i as u32);
if i == 3 && cc >= state.mi_cols {
break;
}
write_block_syntax(writer, cdfs, state, blk, r, cc, sub_size, params)?;
}
}
(SyntaxNode::Split(children), PARTITION_SPLIT) => {
let [nw, ne, sw, se] = children;
write_partition_tree_syntax(writer, cdfs, state, nw, r, c, sub_size, params)?;
write_partition_tree_syntax(
writer,
cdfs,
state,
ne,
r,
c + half_block4x4,
sub_size,
params,
)?;
write_partition_tree_syntax(
writer,
cdfs,
state,
sw,
r + half_block4x4,
c,
sub_size,
params,
)?;
write_partition_tree_syntax(
writer,
cdfs,
state,
se,
r + half_block4x4,
c + half_block4x4,
sub_size,
params,
)?;
}
_ => return Err(Error::PartitionWalkOutOfRange),
}
Ok(())
}
pub(crate) fn write_partition_symbol(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &PartitionSyntaxWriter,
partition: usize,
r: u32,
c: u32,
b_size: usize,
) -> Result<(), Error> {
let num4x4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let half_block4x4 = num4x4 >> 1;
let has_rows = (r + half_block4x4) < state.mi_rows;
let has_cols = (c + half_block4x4) < state.mi_cols;
let pctx = if b_size >= BLOCK_8X8 {
let bsl = MI_WIDTH_LOG2[b_size] as u32;
state.partition_ctx_for(r, c, bsl)
} else {
0
};
write_partition(writer, cdfs, partition, b_size, has_rows, has_cols, pctx)
}
#[allow(clippy::too_many_arguments)]
pub fn write_block_syntax(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
block: &SyntaxBlock,
mi_row: u32,
mi_col: u32,
sub_size: usize,
params: &SyntaxFrameParams,
) -> Result<(), Error> {
if sub_size >= BLOCK_SIZES {
return Err(Error::PartitionWalkOutOfRange);
}
if mi_row >= state.mi_rows || mi_col >= state.mi_cols {
return Err(Error::PartitionWalkOutOfRange);
}
if !matches!(params.bit_depth, 8 | 10 | 12) {
return Err(Error::PartitionWalkOutOfRange);
}
if block.skip > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if block.skip == 1 && !block.residual_quant.is_empty() {
return Err(Error::PartitionWalkOutOfRange);
}
if (block.segment_id as usize) >= MAX_SEGMENTS {
return Err(Error::PartitionWalkOutOfRange);
}
if params.inter.is_some() {
return write_block_syntax_inter_frame(
writer, cdfs, state, block, mi_row, mi_col, sub_size, params,
);
}
if block.inter.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
let chroma_y_edge_case = bh4 == 1 && params.subsampling_y != 0 && (mi_row & 1) == 0;
let chroma_x_edge_case = bw4 == 1 && params.subsampling_x != 0 && (mi_col & 1) == 0;
let has_chroma = if chroma_y_edge_case || chroma_x_edge_case {
false
} else {
params.num_planes > 1
};
let avail_u = state.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = state.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let (seg_pred, seg_ctx) = state.segment_pred_ctx(mi_row, mi_col);
if params.seg_id_pre_skip {
write_intra_segment_id(
writer,
cdfs,
block.segment_id,
0,
seg_pred,
seg_ctx,
params.segmentation_enabled,
params.last_active_seg_id,
)?;
}
let above_skip = if avail_u {
state.mirror.skips()[((mi_row - 1) * state.mi_cols + mi_col) as usize]
} else {
0
};
let left_skip = if avail_l {
state.mirror.skips()[(mi_row * state.mi_cols + mi_col - 1) as usize]
} else {
0
};
write_skip(
writer,
cdfs,
block.skip,
skip_ctx(above_skip, left_skip),
params.seg_id_pre_skip
&& params.segmentation_enabled
&& params.seg_skip[block.segment_id as usize],
)?;
if !params.seg_id_pre_skip {
write_intra_segment_id(
writer,
cdfs,
block.segment_id,
block.skip,
seg_pred,
seg_ctx,
params.segmentation_enabled,
params.last_active_seg_id,
)?;
}
let cdef_size4 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as u32;
let cdef_mask: u32 = !(cdef_size4 - 1);
let anchor_cell = ((mi_row & cdef_mask) * state.mi_cols + (mi_col & cdef_mask)) as usize;
let anchor_prior = state.mirror.cdef_idx()[anchor_cell];
let anchor_already_stamped = anchor_prior != -1;
let cdef_value = if anchor_already_stamped {
anchor_prior
} else {
block.cdef_idx
};
let cdef_committed = write_cdef(
writer,
cdef_value,
anchor_prior,
params.cdef_bits,
block.skip,
params.coded_lossless,
params.enable_cdef,
params.allow_intrabc,
anchor_already_stamped,
)?;
let block_write_deltas = params.read_deltas && state.write_deltas_pending;
write_delta_qindex(
writer,
cdfs,
sub_size,
block.reduced_delta_q_index,
block.skip,
block_write_deltas,
params.use_128x128_superblock,
params.delta_q_res,
)?;
write_delta_lf(
writer,
cdfs,
sub_size,
&block.reduced_delta_lf,
block.skip,
block_write_deltas,
params.delta_lf_present,
params.delta_lf_multi,
params.mono_chrome,
params.use_128x128_superblock,
params.delta_lf_res,
)?;
state.write_deltas_pending = false;
let lossless = params.lossless_array[block.segment_id as usize];
let tx_size_blk = if lossless {
TX_4X4
} else {
MAX_TX_SIZE_RECT[sub_size]
};
if let Some(mv) = block.intrabc_mv {
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(state.mi_rows, state.mi_cols);
let stack = state.mirror.find_mv_stack(
mi_row,
mi_col,
sub_size,
[0, -1],
false,
false,
[GM_TYPE_IDENTITY; 8],
gm_params,
[0; 8],
false,
true,
&mfmv,
)?;
let inputs = IntrabcArmInputs {
mv,
mv_stack: &stack,
use_128x128_superblock: params.use_128x128_superblock,
mi_row,
mi_row_start: state.geometry.mi_row_start,
};
let info =
write_intra_frame_intrabc_arm(writer, cdfs, params.allow_intrabc, Some(&inputs))?
.ok_or(Error::PartitionWalkOutOfRange)?;
state
.mirror
.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row,
mi_col,
sub_size,
skip: block.skip,
skip_mode: 0,
segment_id: block.segment_id,
is_inter: 1,
y_mode: info.y_mode,
ref_frame: [0, -1],
mv: info.mv,
mv2: [0, 0],
interp_filter: info.interp_filter,
motion_mode: crate::cdf::MOTION_MODE_SIMPLE,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: cdef_committed,
tx_size: tx_size_blk as u8,
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 tx_size_committed = write_block_tx_size_syntax(
writer, cdfs, state, block, mi_row, mi_col, sub_size, lossless,
true, params,
)?;
if block.skip == 0 {
write_residual(
writer,
cdfs,
state,
block,
mi_row,
mi_col,
sub_size,
params,
has_chroma,
lossless,
tx_size_committed,
true,
)?;
} else {
state
.mirror
.stamp_tx_type(mi_col, mi_row, bw4, bh4, DCT_DCT as u8);
state.mirror.reset_txb_block_context(
mi_row,
mi_col,
bw4,
bh4,
has_chroma,
params.subsampling_x,
params.subsampling_y,
);
}
return Ok(());
}
write_intra_frame_intrabc_arm(writer, cdfs, params.allow_intrabc, None)?;
let above_mode = if avail_u {
state.mirror.y_modes()[((mi_row - 1) * state.mi_cols + mi_col) as usize] as usize
} else {
DC_PRED
};
let left_mode = if avail_l {
state.mirror.y_modes()[(mi_row * state.mi_cols + mi_col - 1) as usize] as usize
} else {
DC_PRED
};
let abovemode_ctx = intra_mode_ctx(above_mode);
let leftmode_ctx = intra_mode_ctx(left_mode);
let above_palette_y = avail_u
&& mi_row > 0
&& state.mirror.palette_sizes()[((mi_row - 1) * state.mi_cols + mi_col) as usize] > 0;
let left_palette_y = avail_l
&& mi_col > 0
&& state.mirror.palette_sizes()[(mi_row * state.mi_cols + mi_col - 1) as usize] > 0;
let cfl_allowed = cfl_allowed_for_uv_mode(
lossless,
sub_size,
params.subsampling_x != 0,
params.subsampling_y != 0,
);
let pal = &block.palette;
let has_palette_y = u8::from(pal.size_y > 0);
let has_palette_uv = u8::from(pal.size_uv > 0);
write_intra_frame_else_arm(
writer,
cdfs,
sub_size,
block.y_mode,
block.uv_mode,
block.angle_delta_y,
block.angle_delta_uv,
cfl_allowed,
has_chroma,
params.allow_screen_content_tools,
params.enable_filter_intra,
block.use_filter_intra,
block.filter_intra_mode,
above_palette_y,
left_palette_y,
params.bit_depth,
has_palette_y,
pal.size_y as usize,
&pal.colors_y,
has_palette_uv,
pal.size_uv as usize,
&pal.colors_u,
&pal.colors_v,
pal.delta_encode_v,
block.cfl_alpha_u,
block.cfl_alpha_v,
&state.mirror,
mi_row,
mi_col,
abovemode_ctx,
leftmode_ctx,
)?;
if pal.size_y > 0 {
let args = palette_tokens_args(
sub_size,
mi_row as usize,
mi_col as usize,
state.mi_rows as usize,
state.mi_cols as usize,
PalettePlane::Y,
0,
0,
)
.ok_or(Error::PartitionWalkOutOfRange)?;
write_palette_tokens_plane(
writer,
cdfs,
PalettePlane::Y,
pal.size_y as usize,
args.block_w,
args.block_h,
args.onscreen_w,
args.onscreen_h,
&pal.color_map_y,
args.block_w,
)?;
}
if pal.size_uv > 0 {
let args = palette_tokens_args(
sub_size,
mi_row as usize,
mi_col as usize,
state.mi_rows as usize,
state.mi_cols as usize,
PalettePlane::Uv,
params.subsampling_x as usize,
params.subsampling_y as usize,
)
.ok_or(Error::PartitionWalkOutOfRange)?;
write_palette_tokens_plane(
writer,
cdfs,
PalettePlane::Uv,
pal.size_uv as usize,
args.block_w,
args.block_h,
args.onscreen_w,
args.onscreen_h,
&pal.color_map_uv,
args.block_w,
)?;
}
state
.mirror
.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row,
mi_col,
sub_size,
skip: block.skip,
skip_mode: 0,
segment_id: block.segment_id,
is_inter: 0,
y_mode: block.y_mode,
ref_frame: [0, -1],
mv: [0, 0],
mv2: [0, 0],
interp_filter: [0, 0],
motion_mode: crate::cdf::MOTION_MODE_SIMPLE,
palette_size_y: pal.size_y,
palette_colors_y: &pal.colors_y,
palette_size_uv: pal.size_uv,
palette_colors_u: &pal.colors_u,
palette_colors_v: &pal.colors_v,
cdef: cdef_committed,
tx_size: tx_size_blk as u8,
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 tx_size_committed = write_block_tx_size_syntax(
writer, cdfs, state, block, mi_row, mi_col, sub_size, lossless,
false, params,
)?;
if block.skip == 0 {
write_residual(
writer,
cdfs,
state,
block,
mi_row,
mi_col,
sub_size,
params,
has_chroma,
lossless,
tx_size_committed,
false,
)?;
} else {
state
.mirror
.stamp_tx_type(mi_col, mi_row, bw4, bh4, DCT_DCT as u8);
state.mirror.reset_txb_block_context(
mi_row,
mi_col,
bw4,
bh4,
has_chroma,
params.subsampling_x,
params.subsampling_y,
);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_block_syntax_inter_frame(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
block: &SyntaxBlock,
mi_row: u32,
mi_col: u32,
sub_size: usize,
params: &SyntaxFrameParams,
) -> Result<(), Error> {
let ip = params
.inter
.as_ref()
.ok_or(Error::PartitionWalkOutOfRange)?;
if block.intrabc_mv.is_some() || params.allow_intrabc {
return Err(Error::PartitionWalkOutOfRange);
}
if params.segmentation_enabled && !ip.segmentation_update_map {
return Err(Error::PartitionWalkOutOfRange);
}
let skip_mode: u8 = block.inter.as_ref().map_or(0, |ib| ib.skip_mode);
if skip_mode > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
if skip_mode == 1 {
let ib = block.inter.as_ref().expect("skip_mode implies inter");
if !ip.skip_mode_present
|| block_width(sub_size) < 8
|| block_height(sub_size) < 8
|| block.skip != 1
|| ib.ref_frame != ip.skip_mode_frame
|| ib.y_mode != MODE_NEAREST_NEARESTMV
|| ib.ref_mv_idx != 0
{
return Err(Error::PartitionWalkOutOfRange);
}
}
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
let chroma_y_edge_case = bh4 == 1 && params.subsampling_y != 0 && (mi_row & 1) == 0;
let chroma_x_edge_case = bw4 == 1 && params.subsampling_x != 0 && (mi_col & 1) == 0;
let has_chroma = if chroma_y_edge_case || chroma_x_edge_case {
false
} else {
params.num_planes > 1
};
let avail_u = state.geometry.is_inside(mi_row as i32 - 1, mi_col as i32);
let avail_l = state.geometry.is_inside(mi_row as i32, mi_col as i32 - 1);
let (seg_pred, seg_ctx) = state.segment_pred_ctx(mi_row, mi_col);
let predicted_segment_id: u8 = if params.segmentation_enabled {
match ip.prev_segment_ids.as_deref() {
Some(prev) => {
let v = crate::cdf::get_segment_id(
prev,
state.mi_rows,
state.mi_cols,
state.mi_rows,
state.mi_cols,
mi_row,
mi_col,
sub_size,
)
.ok_or(Error::PartitionWalkOutOfRange)?;
if v < 0 || v > i32::from(params.last_active_seg_id) {
return Err(Error::PartitionWalkOutOfRange);
}
v as u8
}
None => 0,
}
} else {
0
};
let seg_pred_ctx_v = state.mirror.seg_pred_ctx(mi_row, mi_col);
let seg_id_predicted_v: u8 = u8::from(
params.segmentation_enabled
&& ip.segmentation_update_map
&& ip.segmentation_temporal_update
&& block.segment_id == predicted_segment_id,
);
let above_skip = if avail_u {
state.mirror.skips()[((mi_row - 1) * state.mi_cols + mi_col) as usize]
} else {
0
};
let left_skip = if avail_l {
state.mirror.skips()[(mi_row * state.mi_cols + mi_col - 1) as usize]
} else {
0
};
let skip_ctx_v = skip_ctx(above_skip, left_skip);
let above_skip_mode = if avail_u {
state.mirror.skip_modes()[((mi_row - 1) * state.mi_cols + mi_col) as usize]
} else {
0
};
let left_skip_mode = if avail_l {
state.mirror.skip_modes()[(mi_row * state.mi_cols + mi_col - 1) as usize]
} else {
0
};
let skip_mode_ctx_v = skip_mode_ctx(above_skip_mode, left_skip_mode);
let above_intra_opt: Option<bool> = if avail_u {
Some(state.mirror.is_inters()[((mi_row - 1) * state.mi_cols + mi_col) as usize] == 0)
} else {
None
};
let left_intra_opt: Option<bool> = if avail_l {
Some(state.mirror.is_inters()[(mi_row * state.mi_cols + mi_col - 1) as usize] == 0)
} else {
None
};
let is_inter_ctx_v = is_inter_ctx(above_intra_opt, left_intra_opt);
let cdef_size4 = NUM_4X4_BLOCKS_WIDE[BLOCK_64X64] as u32;
let cdef_mask: u32 = !(cdef_size4 - 1);
let anchor_cell = ((mi_row & cdef_mask) * state.mi_cols + (mi_col & cdef_mask)) as usize;
let anchor_prior = state.mirror.cdef_idx()[anchor_cell];
let anchor_already_stamped = anchor_prior != -1;
let cdef_value = if anchor_already_stamped {
anchor_prior
} else {
block.cdef_idx
};
let block_write_deltas = params.read_deltas && state.write_deltas_pending;
let delta_inputs = InterFrameDeltaSiteInputs {
cdef_idx: cdef_value,
cdef_idx_prior_stamp: anchor_prior,
cdef_bits: params.cdef_bits,
coded_lossless: params.coded_lossless,
enable_cdef: params.enable_cdef,
allow_intrabc: params.allow_intrabc,
anchor_already_stamped,
reduced_delta_q_index: block.reduced_delta_q_index,
reduced_delta_lf: block.reduced_delta_lf,
read_deltas: block_write_deltas,
use_128x128_superblock: params.use_128x128_superblock,
delta_q_res: params.delta_q_res,
delta_lf_present: params.delta_lf_present,
delta_lf_multi: params.delta_lf_multi,
mono_chrome: params.mono_chrome,
delta_lf_res: params.delta_lf_res,
};
let is_inter_flag = u8::from(block.inter.is_some());
let seg_ref_data: i8 = params.seg_ref_frame[block.segment_id as usize].unwrap_or(0);
let seg_ref_active: bool =
params.segmentation_enabled && params.seg_ref_frame[block.segment_id as usize].is_some();
let seg_skip_feat: bool =
params.segmentation_enabled && params.seg_skip[block.segment_id as usize];
let seg_gmv_active: bool =
params.segmentation_enabled && params.seg_globalmv[block.segment_id as usize];
let prefix = write_inter_frame_mode_info_prefix(
writer,
cdfs,
sub_size,
block.segment_id,
skip_mode,
block.skip,
is_inter_flag,
seg_pred,
seg_id_predicted_v,
skip_mode_ctx_v,
skip_ctx_v,
seg_ctx,
seg_pred_ctx_v,
is_inter_ctx_v,
seg_ref_active || seg_skip_feat || seg_gmv_active,
params.seg_id_pre_skip && seg_skip_feat,
seg_ref_active,
seg_ref_data != 0,
seg_gmv_active,
params.segmentation_enabled,
ip.segmentation_update_map,
ip.segmentation_temporal_update,
params.seg_id_pre_skip,
predicted_segment_id,
params.last_active_seg_id,
ip.skip_mode_present,
¶ms.lossless_array,
&delta_inputs,
)?;
state.write_deltas_pending = false;
if params.segmentation_enabled && ip.segmentation_update_map {
if !params.seg_id_pre_skip && block.skip != 0 {
state
.mirror
.stamp_seg_pred_context(mi_row, mi_col, sub_size, 0);
} else if ip.segmentation_temporal_update {
state
.mirror
.stamp_seg_pred_context(mi_row, mi_col, sub_size, seg_id_predicted_v);
}
}
let cdef_committed = prefix.cdef_idx;
let lossless = prefix.lossless;
let tx_pre = if lossless {
TX_4X4
} else {
MAX_TX_SIZE_RECT[sub_size]
};
if let Some(ib) = block.inter.as_ref() {
let is_compound = ib.ref_frame[1] > 0;
if !(1..=7).contains(&ib.ref_frame[0])
|| !(ib.ref_frame[1] == -1 || (1..=7).contains(&ib.ref_frame[1]))
{
return Err(Error::PartitionWalkOutOfRange);
}
let mode_ok = if is_compound {
(MODE_NEAREST_NEARESTMV..=MODE_NEW_NEWMV).contains(&ib.y_mode)
} else {
matches!(
ib.y_mode,
MODE_NEARESTMV | MODE_NEARMV | MODE_GLOBALMV | MODE_NEWMV
)
};
if !mode_ok {
return Err(Error::PartitionWalkOutOfRange);
}
let list_count: usize = if is_compound { 2 } else { 1 };
for i in 0..list_count {
if ib.mv[i][0].unsigned_abs() >= (1 << 14) || ib.mv[i][1].unsigned_abs() >= (1 << 14) {
return Err(Error::PartitionWalkOutOfRange);
}
}
if ip.interpolation_filter > SWITCHABLE {
return Err(Error::PartitionWalkOutOfRange);
}
let rf = |r: u32, c: u32, slot: usize| -> i32 {
i32::from(state.mirror.ref_frames()[((r * state.mi_cols + c) as usize) * 2 + slot])
};
let left_ref_frame: [i32; 2] = if avail_l {
[rf(mi_row, mi_col - 1, 0), rf(mi_row, mi_col - 1, 1)]
} else {
[0, -1]
};
let above_ref_frame: [i32; 2] = if avail_u {
[rf(mi_row - 1, mi_col, 0), rf(mi_row - 1, mi_col, 1)]
} else {
[0, -1]
};
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;
let stack = state.mirror.find_mv_stack(
mi_row,
mi_col,
sub_size,
[i32::from(ib.ref_frame[0]), i32::from(ib.ref_frame[1])],
is_compound,
ip.use_ref_frame_mvs,
ip.gm_type,
ip.gm_params,
ip.ref_frame_sign_bias,
ip.allow_high_precision_mv,
ip.force_integer_mv,
&ip.motion_field_mvs,
)?;
for i in 0..list_count {
if crate::cdf::get_mode(ib.y_mode, i) != MODE_NEWMV {
let d = assign_mv_pred_mv(&stack, ib.y_mode, i as u8, ib.ref_mv_idx)?;
if d != ib.mv[i] {
return Err(Error::PartitionWalkOutOfRange);
}
}
}
let mut tail = InterBlockModeInfoTail::bit_silent();
tail.interpolation_filter = ip.interpolation_filter;
tail.enable_dual_filter = ip.enable_dual_filter;
tail.gm_type = ip.gm_type;
tail.is_motion_mode_switchable = ip.is_motion_mode_switchable;
tail.allow_warped_motion = ip.allow_warped_motion;
tail.is_scaled_per_ref = ip.is_scaled_per_ref;
tail.motion_mode = ib.motion_mode;
tail.has_overlappable = state
.mirror
.has_overlappable_candidates(mi_row, mi_col, sub_size);
tail.num_samples = state.mirror.find_warp_samples(
mi_row,
mi_col,
sub_size,
i32::from(ib.ref_frame[0]),
ib.mv,
);
tail.enable_interintra_compound = ip.enable_interintra_compound;
tail.enable_masked_compound = ip.enable_masked_compound;
tail.enable_jnt_comp = ip.enable_jnt_comp;
let interintra_on = if let Some(ii_mode) = ib.interintra_mode {
let gate_open = skip_mode == 0
&& ip.enable_interintra_compound
&& ib.ref_frame[1] == -1
&& (crate::cdf::BLOCK_8X8..=crate::cdf::BLOCK_32X32).contains(&sub_size);
if !gate_open
|| (ii_mode as usize) >= crate::cdf::INTERINTRA_MODES
|| ib.wedge_interintra > 1
{
return Err(Error::PartitionWalkOutOfRange);
}
if ib.wedge_interintra == 1 {
if crate::cdf::wedge_bits(sub_size) == 0
|| (ib.interintra_wedge_index as usize) >= 16
{
return Err(Error::PartitionWalkOutOfRange);
}
} else if ib.interintra_wedge_index != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
tail.interintra = InterIntraReadout {
interintra: 1,
interintra_mode: Some(ii_mode),
wedge_interintra: Some(ib.wedge_interintra),
wedge_index: (ib.wedge_interintra == 1).then_some(ib.interintra_wedge_index),
};
true
} else {
if ib.wedge_interintra != 0 || ib.interintra_wedge_index != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
false
};
{
use crate::inter_pred::{
COMPOUND_AVERAGE, COMPOUND_DIFFWTD, COMPOUND_DISTANCE, COMPOUND_WEDGE,
};
let ct = ib.compound_type;
if interintra_on {
let derived = if ib.wedge_interintra == 1 {
COMPOUND_WEDGE
} else {
crate::inter_pred::COMPOUND_INTRA
};
if ct != derived || ib.wedge_index != 0 || ib.wedge_sign != 0 || ib.mask_type != 0 {
return Err(Error::PartitionWalkOutOfRange);
}
tail.compound_type = crate::cdf::CompoundTypeReadout {
comp_group_idx: 0,
compound_idx: 1,
compound_type: ct,
wedge_index: None,
wedge_sign: None,
mask_type: None,
};
}
let masked = !interintra_on && (ct == COMPOUND_WEDGE || ct == COMPOUND_DIFFWTD);
let side_ok = interintra_on
|| match ct {
COMPOUND_WEDGE => {
ib.mask_type == 0 && (ib.wedge_index as usize) < 16 && ib.wedge_sign <= 1
}
COMPOUND_DIFFWTD => {
ib.wedge_index == 0 && ib.wedge_sign == 0 && ib.mask_type <= 1
}
COMPOUND_AVERAGE | COMPOUND_DISTANCE => {
ib.wedge_index == 0 && ib.wedge_sign == 0 && ib.mask_type == 0
}
_ => false,
};
if !side_ok {
return Err(Error::PartitionWalkOutOfRange);
}
if is_compound && skip_mode == 0 {
if masked && !ip.enable_masked_compound {
return Err(Error::PartitionWalkOutOfRange);
}
if ct == COMPOUND_DISTANCE && !ip.enable_jnt_comp {
return Err(Error::PartitionWalkOutOfRange);
}
if ct == COMPOUND_WEDGE && crate::cdf::wedge_bits(sub_size) == 0 {
return Err(Error::PartitionWalkOutOfRange);
}
tail.compound_type = crate::cdf::CompoundTypeReadout {
comp_group_idx: u8::from(masked),
compound_idx: u8::from(ct != COMPOUND_DISTANCE),
compound_type: ct,
wedge_index: (ct == COMPOUND_WEDGE).then_some(ib.wedge_index),
wedge_sign: (ct == COMPOUND_WEDGE).then_some(ib.wedge_sign),
mask_type: (ct == COMPOUND_DIFFWTD).then_some(ib.mask_type),
};
let oh = &ip.order_hints;
let fwd = crate::inter_pred::get_relative_dist(
oh.order_hints_by_ref[ib.ref_frame[0] as usize],
oh.current_order_hint,
oh.order_hint_bits,
)
.abs();
let bck = crate::inter_pred::get_relative_dist(
oh.order_hints_by_ref[ib.ref_frame[1] as usize],
oh.current_order_hint,
oh.order_hint_bits,
)
.abs();
tail.dist_equal = fwd == bck;
} else if !interintra_on && ct != COMPOUND_AVERAGE {
return Err(Error::PartitionWalkOutOfRange);
}
if avail_u {
let cell = (((mi_row - 1) * state.mi_cols) + mi_col) as usize;
tail.above_comp_group_idx = state.mirror.comp_group_idxs()[cell];
tail.above_compound_idx = state.mirror.compound_idxs()[cell];
}
if avail_l {
let cell = ((mi_row * state.mi_cols) + mi_col - 1) as usize;
tail.left_comp_group_idx = state.mirror.comp_group_idxs()[cell];
tail.left_compound_idx = state.mirror.compound_idxs()[cell];
}
}
tail.interp_filter = if ip.interpolation_filter != SWITCHABLE {
if ib.interp_filter != [ip.interpolation_filter; 2] {
return Err(Error::PartitionWalkOutOfRange);
}
InterpolationFilterReadout {
interp_filter: [ip.interpolation_filter; 2],
read_from_bitstream: [false, false],
}
} else {
let large = core::cmp::min(
NUM_4X4_BLOCKS_WIDE[sub_size] * MI_SIZE,
NUM_4X4_BLOCKS_HIGH[sub_size] * MI_SIZE,
) >= 8;
let needs = if skip_mode != 0 || ib.motion_mode == crate::cdf::MOTION_MODE_WARPED_CAUSAL
{
false
} else if large && ib.y_mode == MODE_GLOBALMV {
ip.gm_type[ib.ref_frame[0] as usize] == GM_TYPE_TRANSLATION
} else if large && ib.y_mode == MODE_GLOBAL_GLOBALMV {
ip.gm_type[ib.ref_frame[0] as usize] == GM_TYPE_TRANSLATION
|| ip.gm_type[ib.ref_frame[1] as usize] == GM_TYPE_TRANSLATION
} else {
true
};
let mut read = [false, false];
if needs {
read[0] = true;
if ip.enable_dual_filter {
read[1] = true;
}
}
InterpolationFilterReadout {
interp_filter: ib.interp_filter,
read_from_bitstream: read,
}
};
if avail_u {
let cell = (((mi_row - 1) * state.mi_cols + mi_col) as usize) * 2;
let f = state.mirror.interp_filters();
tail.above_interp_filters = [f[cell], f[cell + 1]];
}
if avail_l {
let cell = ((mi_row * state.mi_cols + mi_col - 1) as usize) * 2;
let f = state.mirror.interp_filters();
tail.left_interp_filters = [f[cell], f[cell + 1]];
}
let committed_filters = tail.interp_filter.interp_filter;
write_inter_block_mode_info(
writer,
cdfs,
[i32::from(ib.ref_frame[0]), i32::from(ib.ref_frame[1])],
ib.y_mode,
ib.mv,
ib.ref_mv_idx,
&stack,
sub_size,
skip_mode,
ip.skip_mode_frame,
seg_ref_active,
seg_ref_data,
seg_skip_feat,
seg_gmv_active,
ip.reference_select,
avail_u,
avail_l,
above_single,
left_single,
above_intra,
left_intra,
above_ref_frame,
left_ref_frame,
ip.force_integer_mv,
ip.allow_high_precision_mv,
&tail,
)?;
state
.mirror
.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row,
mi_col,
sub_size,
skip: block.skip,
skip_mode,
segment_id: block.segment_id,
is_inter: 1,
y_mode: ib.y_mode,
ref_frame: if interintra_on {
[
ib.ref_frame[0],
crate::uncompressed_header_tail::INTRA_FRAME as i8,
]
} else {
ib.ref_frame
},
mv: ib.mv[0],
mv2: ib.mv[1],
interp_filter: committed_filters,
motion_mode: ib.motion_mode,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: cdef_committed,
tx_size: tx_pre as u8,
comp_group_idx: u8::from(
is_compound
&& skip_mode == 0
&& matches!(
ib.compound_type,
crate::inter_pred::COMPOUND_WEDGE | crate::inter_pred::COMPOUND_DIFFWTD
),
),
compound_idx: u8::from(
!(is_compound
&& skip_mode == 0
&& ib.compound_type == crate::inter_pred::COMPOUND_DISTANCE),
),
compound_type: if interintra_on || (is_compound && skip_mode == 0) {
ib.compound_type
} else {
crate::inter_pred::COMPOUND_AVERAGE
},
wedge_index: ib.wedge_index,
wedge_sign: ib.wedge_sign,
mask_type: ib.mask_type,
interintra_mode: ib.interintra_mode.unwrap_or(0),
wedge_interintra: if interintra_on {
ib.wedge_interintra
} else {
0
},
interintra_wedge_index: ib.interintra_wedge_index,
});
let tx_size_committed = write_block_tx_size_syntax(
writer, cdfs, state, block, mi_row, mi_col, sub_size, lossless,
true, params,
)?;
if block.skip == 0 {
write_residual(
writer,
cdfs,
state,
block,
mi_row,
mi_col,
sub_size,
params,
has_chroma,
lossless,
tx_size_committed,
true,
)?;
} else {
state
.mirror
.stamp_tx_type(mi_col, mi_row, bw4, bh4, DCT_DCT as u8);
state.mirror.reset_txb_block_context(
mi_row,
mi_col,
bw4,
bh4,
has_chroma,
params.subsampling_x,
params.subsampling_y,
);
}
return Ok(());
}
let above_palette_y = avail_u
&& mi_row > 0
&& state.mirror.palette_sizes()[((mi_row - 1) * state.mi_cols + mi_col) as usize] > 0;
let left_palette_y = avail_l
&& mi_col > 0
&& state.mirror.palette_sizes()[(mi_row * state.mi_cols + mi_col - 1) as usize] > 0;
let cfl_allowed = cfl_allowed_for_uv_mode(
lossless,
sub_size,
params.subsampling_x != 0,
params.subsampling_y != 0,
);
let pal = &block.palette;
let has_palette_y = u8::from(pal.size_y > 0);
let has_palette_uv = u8::from(pal.size_uv > 0);
write_intra_block_mode_info_with_palette(
writer,
cdfs,
sub_size,
block.y_mode,
block.uv_mode,
block.angle_delta_y,
block.angle_delta_uv,
cfl_allowed,
has_chroma,
params.allow_screen_content_tools,
params.enable_filter_intra,
block.use_filter_intra,
block.filter_intra_mode,
above_palette_y,
left_palette_y,
params.bit_depth,
has_palette_y,
pal.size_y as usize,
&pal.colors_y,
has_palette_uv,
pal.size_uv as usize,
&pal.colors_u,
&pal.colors_v,
pal.delta_encode_v,
block.cfl_alpha_u,
block.cfl_alpha_v,
&state.mirror,
mi_row,
mi_col,
)?;
if pal.size_y > 0 {
let args = palette_tokens_args(
sub_size,
mi_row as usize,
mi_col as usize,
state.mi_rows as usize,
state.mi_cols as usize,
PalettePlane::Y,
0,
0,
)
.ok_or(Error::PartitionWalkOutOfRange)?;
write_palette_tokens_plane(
writer,
cdfs,
PalettePlane::Y,
pal.size_y as usize,
args.block_w,
args.block_h,
args.onscreen_w,
args.onscreen_h,
&pal.color_map_y,
args.block_w,
)?;
}
if pal.size_uv > 0 {
let args = palette_tokens_args(
sub_size,
mi_row as usize,
mi_col as usize,
state.mi_rows as usize,
state.mi_cols as usize,
PalettePlane::Uv,
params.subsampling_x as usize,
params.subsampling_y as usize,
)
.ok_or(Error::PartitionWalkOutOfRange)?;
write_palette_tokens_plane(
writer,
cdfs,
PalettePlane::Uv,
pal.size_uv as usize,
args.block_w,
args.block_h,
args.onscreen_w,
args.onscreen_h,
&pal.color_map_uv,
args.block_w,
)?;
}
state
.mirror
.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row,
mi_col,
sub_size,
skip: block.skip,
skip_mode: 0,
segment_id: block.segment_id,
is_inter: 0,
y_mode: block.y_mode,
ref_frame: [0, -1],
mv: [0, 0],
mv2: [0, 0],
interp_filter: [0, 0],
motion_mode: MOTION_MODE_SIMPLE,
palette_size_y: pal.size_y,
palette_colors_y: &pal.colors_y,
palette_size_uv: pal.size_uv,
palette_colors_u: &pal.colors_u,
palette_colors_v: &pal.colors_v,
cdef: cdef_committed,
tx_size: tx_pre as u8,
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 tx_size_committed = write_block_tx_size_syntax(
writer, cdfs, state, block, mi_row, mi_col, sub_size, lossless,
false, params,
)?;
if block.skip == 0 {
write_residual(
writer,
cdfs,
state,
block,
mi_row,
mi_col,
sub_size,
params,
has_chroma,
lossless,
tx_size_committed,
false,
)?;
} else {
state
.mirror
.stamp_tx_type(mi_col, mi_row, bw4, bh4, DCT_DCT as u8);
state.mirror.reset_txb_block_context(
mi_row,
mi_col,
bw4,
bh4,
has_chroma,
params.subsampling_x,
params.subsampling_y,
);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_block_tx_size_syntax(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
block: &SyntaxBlock,
mi_row: u32,
mi_col: u32,
sub_size: usize,
lossless: bool,
is_inter: bool,
params: &SyntaxFrameParams,
) -> Result<usize, Error> {
let bw4 = NUM_4X4_BLOCKS_WIDE[sub_size] as u32;
let bh4 = NUM_4X4_BLOCKS_HIGH[sub_size] as u32;
let skip = block.skip != 0;
if params.tx_mode_select && sub_size > BLOCK_4X4 && is_inter && !skip && !lossless {
if block.tx_size.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
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;
let mut tree_idx = 0usize;
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 {
let tree = block
.var_tx_trees
.get(tree_idx)
.ok_or(Error::PartitionWalkOutOfRange)?;
tree_idx += 1;
last_tx_size = write_var_tx_size_syntax(
writer, cdfs, state, tree, mi_row, mi_col, sub_size, row, col, max_tx_sz,
0,
)?;
col += tx_w4;
}
row += tx_h4;
}
if tree_idx != block.var_tx_trees.len() {
return Err(Error::PartitionWalkOutOfRange);
}
state
.mirror
.stamp_tx_sizes_footprint(mi_row, mi_col, sub_size, last_tx_size);
return Ok(last_tx_size as usize);
}
if !block.var_tx_trees.is_empty() {
return Err(Error::PartitionWalkOutOfRange);
}
let allow_select = !skip || !is_inter;
let default_tx = if lossless {
TX_4X4
} else {
MAX_TX_SIZE_RECT[sub_size]
};
let tx_size = block.tx_size.map_or(default_tx, |t| t as usize);
let ctx = state.mirror.tx_depth_block_ctx(mi_row, mi_col, sub_size);
write_block_tx_size(
writer,
cdfs,
tx_size,
sub_size,
lossless,
allow_select,
params.tx_mode_select,
ctx,
)?;
state
.mirror
.stamp_block_tx_size_grids(mi_row, mi_col, sub_size, tx_size as u8);
Ok(tx_size)
}
#[allow(clippy::too_many_arguments)]
fn write_var_tx_size_syntax(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
tree: &VarTxSyntaxTree,
mi_row_b: u32,
mi_col_b: u32,
sub_size: usize,
row: u32,
col: u32,
tx_sz: usize,
depth: u32,
) -> Result<u8, Error> {
if row >= state.mi_rows || col >= state.mi_cols {
return match tree {
VarTxSyntaxTree::Leaf => Ok(tx_sz as u8),
VarTxSyntaxTree::Split(_) => Err(Error::PartitionWalkOutOfRange),
};
}
let terminal = tx_sz == TX_4X4 || depth == MAX_VARTX_DEPTH;
match tree {
VarTxSyntaxTree::Leaf => {
if !terminal {
let ctx = state
.mirror
.txfm_split_node_ctx(mi_row_b, mi_col_b, sub_size, row, col, tx_sz)?;
let cdf = cdfs.txfm_split_cdf(ctx);
writer.write_symbol(0, cdf)?;
}
state.mirror.stamp_var_tx_leaf(row, col, tx_sz);
Ok(tx_sz as u8)
}
VarTxSyntaxTree::Split(children) => {
if terminal {
return Err(Error::PartitionWalkOutOfRange);
}
let ctx = state
.mirror
.txfm_split_node_ctx(mi_row_b, mi_col_b, sub_size, row, col, tx_sz)?;
let cdf = cdfs.txfm_split_cdf(ctx);
writer.write_symbol(1, cdf)?;
let sub_tx_sz = SPLIT_TX_SIZE[tx_sz];
let w4 = (TX_WIDTH[tx_sz] / MI_SIZE) as u32;
let h4 = (TX_HEIGHT[tx_sz] / MI_SIZE) as u32;
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"
);
let expected = ((h4 / step_h) * (w4 / step_w)) as usize;
if children.len() != expected {
return Err(Error::PartitionWalkOutOfRange);
}
let mut last_tx_sz = sub_tx_sz as u8;
let mut k = 0usize;
let mut i = 0u32;
while i < h4 {
let mut j = 0u32;
while j < w4 {
last_tx_sz = write_var_tx_size_syntax(
writer,
cdfs,
state,
&children[k],
mi_row_b,
mi_col_b,
sub_size,
row + i,
col + j,
sub_tx_sz,
depth + 1,
)?;
k += 1;
j += step_w;
}
i += step_h;
}
Ok(last_tx_sz)
}
}
}
#[allow(clippy::too_many_arguments)]
fn write_residual(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
block: &SyntaxBlock,
mi_row: u32,
mi_col: u32,
sub_size: usize,
params: &SyntaxFrameParams,
has_chroma: bool,
lossless: bool,
tx_size_blk: usize,
is_inter: bool,
) -> Result<(), Error> {
if params.subsampling_x > 1 || params.subsampling_y > 1 {
return Err(Error::PartitionWalkOutOfRange);
}
let block_w = NUM_4X4_BLOCKS_WIDE[sub_size] * 4;
let block_h = NUM_4X4_BLOCKS_HIGH[sub_size] * 4;
let width_chunks = core::cmp::max(1, block_w >> 6);
let height_chunks = core::cmp::max(1, block_h >> 6);
let mi_size_chunk = if width_chunks > 1 || height_chunks > 1 {
BLOCK_64X64
} else {
sub_size
};
let num_planes: u8 = if has_chroma { 3 } else { 1 };
let mut tu_idx = 0usize;
let mut luma_tx_idx = 0usize;
for chunk_y in 0..height_chunks {
for chunk_x in 0..width_chunks {
for plane in 0..num_planes {
let tx_sz = if lossless {
TX_4X4
} else {
get_tx_size(
plane,
tx_size_blk,
sub_size,
params.subsampling_x,
params.subsampling_y,
)
.ok_or(Error::PartitionWalkOutOfRange)?
};
let step_x = TX_WIDTH[tx_sz] >> 2;
let step_y = TX_HEIGHT[tx_sz] >> 2;
let plane_sz = match get_plane_residual_size(
mi_size_chunk,
plane,
params.subsampling_x,
params.subsampling_y,
) {
Some(s) => s,
None => {
if plane == 0 {
return Err(Error::PartitionWalkOutOfRange);
}
continue;
}
};
let num4x4_w = NUM_4X4_BLOCKS_WIDE[plane_sz];
let num4x4_h = NUM_4X4_BLOCKS_HIGH[plane_sz];
let sub_x = if plane > 0 { params.subsampling_x } else { 0 };
let sub_y = if plane > 0 { params.subsampling_y } else { 0 };
if is_inter && !lossless && plane == 0 {
let mi_row_chunk = mi_row + ((chunk_y as u32) << 4);
let mi_col_chunk = mi_col + ((chunk_x as u32) << 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);
write_transform_tree(
writer,
cdfs,
state,
block,
params,
base_x,
base_y,
(num4x4_w * 4) as u32,
(num4x4_h * 4) as u32,
mi_row,
mi_col,
sub_size,
lossless,
&mut tu_idx,
&mut luma_tx_idx,
)?;
continue;
}
let base_x_block = (mi_col >> sub_x) * (MI_SIZE as u32);
let base_y_block = (mi_row >> sub_y) * (MI_SIZE as u32);
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);
write_transform_block(
writer,
cdfs,
state,
block,
params,
plane,
base_x_block,
base_y_block,
tx_sz,
x_arg,
y_arg,
sub_x,
sub_y,
mi_row,
mi_col,
sub_size,
lossless,
is_inter,
&mut tu_idx,
&mut luma_tx_idx,
)?;
x += step_x;
}
y += step_y;
}
}
}
}
if tu_idx != block.residual_quant.len() {
return Err(Error::PartitionWalkOutOfRange);
}
if !block.residual_tx_type.is_empty() && luma_tx_idx != block.residual_tx_type.len() {
return Err(Error::PartitionWalkOutOfRange);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn write_transform_block(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
block: &SyntaxBlock,
params: &SyntaxFrameParams,
plane: u8,
base_x: u32,
base_y: u32,
tx_sz: usize,
x: u32,
y: u32,
sub_x: u8,
sub_y: u8,
mi_row: u32,
mi_col: u32,
mi_size: usize,
lossless: bool,
is_inter: bool,
tu_idx: &mut usize,
luma_tx_idx: &mut usize,
) -> Result<(), Error> {
let start_x = base_x + 4 * x;
let start_y = base_y + 4 * y;
let max_x = (state.mi_cols * (MI_SIZE as u32)) >> sub_x;
let max_y = (state.mi_rows * (MI_SIZE as u32)) >> sub_y;
if start_x >= max_x || start_y >= max_y {
return Ok(());
}
let tx_w = TX_WIDTH[tx_sz];
let tx_h = TX_HEIGHT[tx_sz];
let x4 = start_x >> 2;
let y4 = start_y >> 2;
let tx_sz_sqr = tx_size_sqr_index(tx_sz);
let tx_set = if is_inter {
inter_tx_type_set(
tx_sz_sqr as u32,
TX_SIZE_SQR_UP[tx_sz] as u32,
params.reduced_tx_set,
)
} else {
intra_tx_type_set(
tx_sz_sqr as u32,
TX_SIZE_SQR_UP[tx_sz] as u32,
params.reduced_tx_set,
)
};
let quant_tu = block
.residual_quant
.get(*tu_idx)
.ok_or(Error::PartitionWalkOutOfRange)?;
*tu_idx += 1;
if quant_tu.len() != tx_w * tx_h {
return Err(Error::PartitionWalkOutOfRange);
}
let all_zero = quant_tu.is_all_zero();
let quant_dense = quant_tu.dense();
let quant: &[i32] = &quant_dense;
let txb_skip_ctx = state
.mirror
.txb_skip_ctx(plane, tx_sz, mi_size, x4, y4, sub_x, sub_y)
.ok_or(Error::PartitionWalkOutOfRange)?;
let dc_sign_ctx = state.mirror.dc_sign_ctx(plane, tx_sz, x4, y4, sub_x, sub_y);
{
let tx_sz_sqr_min = {
let side = core::cmp::min(tx_w, tx_h);
(side.trailing_zeros() as usize) - 2
};
let tx_sz_ctx = (tx_sz_sqr_min + TX_SIZE_SQR_UP[tx_sz] + 1) >> 1;
crate::encoder::coefficients::write_txb_skip(
writer,
cdfs,
u8::from(all_zero),
tx_sz_ctx,
txb_skip_ctx,
)?;
}
if all_zero {
if plane == 0 {
let committed = block
.residual_tx_type
.get(*luma_tx_idx)
.copied()
.unwrap_or(DCT_DCT as u8);
*luma_tx_idx += 1;
if committed != DCT_DCT as u8 {
return Err(Error::PartitionWalkOutOfRange);
}
state.mirror.stamp_tx_type(
x4,
y4,
(tx_w >> 2) as u32,
(tx_h >> 2) as u32,
DCT_DCT as u8,
);
}
state
.mirror
.stamp_txb_level_context(plane, tx_sz, x4, y4, 0, 0);
return Ok(());
}
if plane == 0 {
let q_for_guard = if params.quant.segmentation_enabled {
crate::cdf::get_qindex(¶ms.quant, true, block.segment_id)
} else {
params.quant.base_q_idx as i32
};
let codes_symbol = tx_set > 0 && q_for_guard > 0;
let committed = block
.residual_tx_type
.get(*luma_tx_idx)
.copied()
.unwrap_or(DCT_DCT as u8);
*luma_tx_idx += 1;
let tx_type: u8 = if codes_symbol {
let intra_dir = crate::cdf::intra_dir(
block.use_filter_intra == 1,
block.y_mode as usize,
block.filter_intra_mode.unwrap_or(0) as usize,
)
.unwrap_or(0);
let symbol = crate::cdf::tx_type_to_symbol(tx_set, is_inter, committed as usize)
.ok_or(Error::PartitionWalkOutOfRange)?;
if is_inter {
let cdf = cdfs
.inter_tx_type_cdf(tx_set, tx_sz_sqr as u32)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(symbol, cdf)?;
} else {
let cdf = cdfs
.intra_tx_type_cdf(tx_set, tx_sz_sqr as u32, intra_dir)
.ok_or(Error::PartitionWalkOutOfRange)?;
writer.write_symbol(symbol, cdf)?;
}
committed
} else {
if committed != DCT_DCT as u8 {
return Err(Error::PartitionWalkOutOfRange);
}
DCT_DCT as u8
};
state
.mirror
.stamp_tx_type(x4, y4, (tx_w >> 2) as u32, (tx_h >> 2) as u32, tx_type);
}
let plane_tx_type = {
let tx_types_grid = state.mirror.tx_types();
let mi_rows = state.mi_rows;
let mi_cols = state.mi_cols;
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,
block.uv_mode.unwrap_or(0) 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
}
},
)
};
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,
};
let scan = crate::scan::get_scan(tx_sz, plane_tx_type);
let wout = crate::encoder::coefficients::write_coefficients_gate_open(
writer,
cdfs,
plane,
u8::from(is_inter),
tx_sz,
tx_class,
dc_sign_ctx,
scan,
quant,
)?;
state
.mirror
.stamp_txb_level_context(plane, tx_sz, x4, y4, wout.cul_level, wout.dc_category);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn write_single_transform_block(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
block: &SyntaxBlock,
params: &SyntaxFrameParams,
plane: u8,
base_x: u32,
base_y: u32,
tx_sz: usize,
x: u32,
y: u32,
mi_row: u32,
mi_col: u32,
mi_size: usize,
is_inter: bool,
) -> Result<(), Error> {
let mut tu_idx = 0usize;
let mut luma_tx_idx = 0usize;
write_transform_block(
writer,
cdfs,
state,
block,
params,
plane,
base_x,
base_y,
tx_sz,
x,
y,
0,
0,
mi_row,
mi_col,
mi_size,
false,
is_inter,
&mut tu_idx,
&mut luma_tx_idx,
)
}
#[allow(clippy::too_many_arguments)]
fn write_transform_tree(
writer: &mut SymbolWriter,
cdfs: &mut TileCdfContext,
state: &mut PartitionSyntaxWriter,
block: &SyntaxBlock,
params: &SyntaxFrameParams,
start_x: u32,
start_y: u32,
w: u32,
h: u32,
mi_row: u32,
mi_col: u32,
mi_size: usize,
lossless: bool,
tu_idx: &mut usize,
luma_tx_idx: &mut usize,
) -> Result<(), Error> {
let max_x = state.mi_cols * (MI_SIZE as u32);
let max_y = state.mi_rows * (MI_SIZE as u32);
if start_x >= max_x || start_y >= max_y {
return Ok(());
}
let row = (start_y >> (MI_SIZE_LOG2 as u32)) as usize;
let col = (start_x >> (MI_SIZE_LOG2 as u32)) as usize;
let luma_tx_sz = state.mirror.inter_tx_sizes()[row * state.mi_cols as usize + col] as usize;
if luma_tx_sz >= TX_SIZES_ALL {
return Err(Error::ResidualTransformTreeUnsupported);
}
let luma_w = TX_WIDTH[luma_tx_sz] as u32;
let luma_h = TX_HEIGHT[luma_tx_sz] as u32;
if w <= luma_w && h <= luma_h {
let tx_sz =
find_tx_size(w as usize, h as usize).ok_or(Error::ResidualTransformTreeUnsupported)?;
return write_transform_block(
writer,
cdfs,
state,
block,
params,
0,
start_x,
start_y,
tx_sz,
0,
0,
0,
0,
mi_row,
mi_col,
mi_size,
lossless,
true,
tu_idx,
luma_tx_idx,
);
}
let halves: &[(u32, u32, u32, u32)] = if w > h {
&[(0, 0, 1, 0), (1, 0, 1, 0)]
} else if w < h {
&[(0, 0, 0, 1), (0, 1, 0, 1)]
} else {
&[(0, 0, 1, 1), (1, 0, 1, 1), (0, 1, 1, 1), (1, 1, 1, 1)]
};
for &(ox, oy, hw, hh) in halves {
let sub_w = if hw != 0 { w / 2 } else { w };
let sub_h = if hh != 0 { h / 2 } else { h };
write_transform_tree(
writer,
cdfs,
state,
block,
params,
start_x + ox * (w / 2),
start_y + oy * (h / 2),
sub_w,
sub_h,
mi_row,
mi_col,
mi_size,
lossless,
tu_idx,
luma_tx_idx,
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cdf::{
get_br_ctx, get_coeff_base_ctx, get_coeff_base_eob_ctx, CoefficientsReadout,
PartitionWalker, TileGeometry as G, BLOCK_16X16, BLOCK_16X32, BLOCK_32X16, BLOCK_32X32,
BLOCK_64X64, BLOCK_8X8, DC_PRED, MAX_SEGMENTS, TX_16X16, TX_4X4, TX_8X8, TX_CLASS_2D,
V_PRED,
};
use crate::scan::get_default_scan;
use crate::symbol_decoder::SymbolDecoder;
fn single_tile(mi_rows: u32, mi_cols: u32) -> G {
G {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
}
}
fn fresh_state(mi_rows: u32, mi_cols: u32) -> PartitionTreeWriter {
PartitionTreeWriter::new(
mi_rows,
mi_cols,
single_tile(mi_rows, mi_cols),
false,
0,
false,
false,
false,
)
.expect("state construction")
}
fn make_skip_leaf() -> EncodeBlock {
EncodeBlock {
skip: 1,
segment_id: 0,
segment_pred: 0,
y_mode: DC_PRED as u8,
uv_mode: Some(DC_PRED as u8),
cfl_alpha_u: None,
cfl_alpha_v: None,
coefficients: Vec::new(),
}
}
fn make_zero_coeff_leaf() -> EncodeBlock {
let scan: Vec<u16> = get_default_scan(TX_4X4).to_vec();
let quant = vec![0i32; 16];
let coefficients = vec![
PlaneCoefficients {
plane: 0,
is_inter: 0,
tx_size: TX_4X4,
tx_class: TX_CLASS_2D,
txb_skip_ctx: 0,
dc_sign_ctx: 0,
scan: scan.clone(),
quant: quant.clone(),
},
PlaneCoefficients {
plane: 1,
is_inter: 0,
tx_size: TX_4X4,
tx_class: TX_CLASS_2D,
txb_skip_ctx: 0,
dc_sign_ctx: 0,
scan: scan.clone(),
quant: quant.clone(),
},
PlaneCoefficients {
plane: 2,
is_inter: 0,
tx_size: TX_4X4,
tx_class: TX_CLASS_2D,
txb_skip_ctx: 0,
dc_sign_ctx: 0,
scan,
quant,
},
];
EncodeBlock {
skip: 0,
segment_id: 0,
segment_pred: 0,
y_mode: DC_PRED as u8,
uv_mode: Some(DC_PRED as u8),
cfl_alpha_u: None,
cfl_alpha_v: None,
coefficients,
}
}
#[test]
fn new_rejects_zero_extent() {
assert!(
PartitionTreeWriter::new(0, 8, single_tile(0, 8), false, 0, false, false, false)
.is_none()
);
assert!(
PartitionTreeWriter::new(8, 0, single_tile(8, 0), false, 0, false, false, false)
.is_none()
);
}
#[test]
fn new_rejects_oversize_geometry() {
let bad = G {
mi_row_start: 0,
mi_row_end: 16, mi_col_start: 0,
mi_col_end: 8,
};
assert!(PartitionTreeWriter::new(8, 8, bad, false, 0, false, false, false).is_none());
}
#[test]
fn new_rejects_inverted_geometry() {
let bad = G {
mi_row_start: 4,
mi_row_end: 4, mi_col_start: 0,
mi_col_end: 8,
};
assert!(PartitionTreeWriter::new(8, 8, bad, false, 0, false, false, false).is_none());
}
#[test]
fn rejects_split_at_forced_none_b_size() {
let mut writer = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = fresh_state(8, 8);
let node = EncodeNode::Split([
Box::new(EncodeNode::Leaf(make_skip_leaf())),
Box::new(EncodeNode::Leaf(make_skip_leaf())),
Box::new(EncodeNode::Leaf(make_skip_leaf())),
Box::new(EncodeNode::Leaf(make_skip_leaf())),
]);
let err = write_partition_tree(
&mut writer,
&mut cdfs,
&mut state,
&node,
0,
0,
crate::cdf::BLOCK_4X4,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn rejects_leaf_at_forced_split_corner() {
let mut writer = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = fresh_state(4, 4);
let node = EncodeNode::Leaf(make_skip_leaf());
let err =
write_partition_tree(&mut writer, &mut cdfs, &mut state, &node, 0, 0, BLOCK_32X32)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn rejects_out_of_range_b_size() {
let mut writer = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = fresh_state(8, 8);
let node = EncodeNode::Leaf(make_skip_leaf());
let err =
write_partition_tree(&mut writer, &mut cdfs, &mut state, &node, 0, 0, BLOCK_SIZES)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn out_of_frame_quadrant_short_circuits() {
let mut writer = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = fresh_state(8, 8);
let node = EncodeNode::dummy_oob();
write_partition_tree(&mut writer, &mut cdfs, &mut state, &node, 8, 0, BLOCK_16X16).unwrap();
write_partition_tree(&mut writer, &mut cdfs, &mut state, &node, 0, 8, BLOCK_16X16).unwrap();
}
#[allow(clippy::too_many_arguments)]
fn mirror_decode_partition_tree(
dec: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
node: &EncodeNode,
state: &mut PartitionTreeWriter,
r: u32,
c: u32,
b_size: usize,
leaves: &mut Vec<(u32, u32, usize)>,
) {
if r >= state.mi_rows || c >= state.mi_cols {
return;
}
let num4x4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let half_block4x4 = num4x4 >> 1;
let has_rows = (r + half_block4x4) < state.mi_rows;
let has_cols = (c + half_block4x4) < state.mi_cols;
let partition = if b_size < BLOCK_8X8 {
PARTITION_NONE
} else if !has_rows && !has_cols {
PARTITION_SPLIT
} else {
let bsl = MI_WIDTH_LOG2[b_size] as u32;
let pctx = state.partition_ctx_for(r, c, bsl);
if has_rows && has_cols {
let cdf = cdfs.partition_cdf(bsl, pctx).unwrap();
dec.read_symbol(cdf).unwrap() as usize
} else if has_cols {
let cdf_row = cdfs.partition_cdf(bsl, pctx).unwrap();
let mut bin = crate::cdf::split_or_horz_cdf(cdf_row, b_size).unwrap();
let s = dec.read_symbol(&mut bin).unwrap();
if s == 0 {
crate::cdf::PARTITION_HORZ
} else {
PARTITION_SPLIT
}
} else {
let cdf_row = cdfs.partition_cdf(bsl, pctx).unwrap();
let mut bin = crate::cdf::split_or_vert_cdf(cdf_row, b_size).unwrap();
let s = dec.read_symbol(&mut bin).unwrap();
if s == 0 {
crate::cdf::PARTITION_VERT
} else {
PARTITION_SPLIT
}
}
};
let sub_size = partition_subsize(partition, b_size).unwrap();
match (node, partition) {
(EncodeNode::Leaf(block), PARTITION_NONE) => {
state.stamp_mi_sizes(r, c, sub_size);
leaves.push((r, c, sub_size));
let skip_cdf = cdfs.skip_cdf(skip_ctx(0, 0));
let _ = dec.read_symbol(skip_cdf).unwrap();
let y_cdf = cdfs.y_mode_cdf(size_group(sub_size)).unwrap();
let _ = dec.read_symbol(y_cdf).unwrap();
if block.uv_mode.is_some() {
let cfl = cfl_allowed_for_uv_mode(false, sub_size, false, false);
let uv_cdf = cdfs.uv_mode_cdf(cfl, block.y_mode as usize).unwrap();
let _ = dec.read_symbol(uv_cdf).unwrap();
}
assert!(
block.coefficients.is_empty(),
"mirror_decode helper handles skip-leaves only; coefficient leaves must use a tailored walker"
);
}
(EncodeNode::Split(children), PARTITION_SPLIT) => {
let [nw, ne, sw, se] = children;
mirror_decode_partition_tree(dec, cdfs, nw, state, r, c, sub_size, leaves);
mirror_decode_partition_tree(
dec,
cdfs,
ne,
state,
r,
c + half_block4x4,
sub_size,
leaves,
);
mirror_decode_partition_tree(
dec,
cdfs,
sw,
state,
r + half_block4x4,
c,
sub_size,
leaves,
);
mirror_decode_partition_tree(
dec,
cdfs,
se,
state,
r + half_block4x4,
c + half_block4x4,
sub_size,
leaves,
);
}
_ => panic!("(node, partition) pairing mismatch"),
}
}
fn round_trip_partition_shape(
node: &EncodeNode,
mi_rows: u32,
mi_cols: u32,
b_size: usize,
) -> Vec<(u32, u32, usize)> {
let mut writer = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut enc_state = fresh_state(mi_rows, mi_cols);
write_partition_tree(
&mut writer,
&mut enc_cdfs,
&mut enc_state,
node,
0,
0,
b_size,
)
.unwrap();
let bytes = writer.finish();
let bytes = if bytes.is_empty() { vec![0u8] } else { bytes };
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec_state = fresh_state(mi_rows, mi_cols);
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let mut leaves = Vec::new();
mirror_decode_partition_tree(
&mut dec,
&mut dec_cdfs,
node,
&mut dec_state,
0,
0,
b_size,
&mut leaves,
);
assert_eq!(enc_cdfs.partition_w8, dec_cdfs.partition_w8);
assert_eq!(enc_cdfs.partition_w16, dec_cdfs.partition_w16);
leaves
}
#[test]
fn round_trip_single_none_leaf_recovers_one_block() {
let node = EncodeNode::Leaf(make_skip_leaf());
let leaves = round_trip_partition_shape(&node, 4, 4, BLOCK_16X16);
assert_eq!(leaves, vec![(0, 0, BLOCK_16X16)]);
}
#[test]
fn round_trip_forced_none_b_size_lt_8x8() {
let node = EncodeNode::Leaf(make_skip_leaf());
let leaves = round_trip_partition_shape(&node, 4, 4, crate::cdf::BLOCK_4X4);
assert_eq!(leaves, vec![(0, 0, crate::cdf::BLOCK_4X4)]);
}
#[test]
fn round_trip_one_level_split_recovers_four_leaves() {
let leaf = || Box::new(EncodeNode::Leaf(make_skip_leaf()));
let node = EncodeNode::Split([leaf(), leaf(), leaf(), leaf()]);
let leaves = round_trip_partition_shape(&node, 4, 4, BLOCK_16X16);
assert_eq!(
leaves,
vec![
(0, 0, BLOCK_8X8),
(0, 2, BLOCK_8X8),
(2, 0, BLOCK_8X8),
(2, 2, BLOCK_8X8),
]
);
}
#[test]
fn round_trip_forced_split_corner() {
let leaf = || Box::new(EncodeNode::Leaf(make_skip_leaf()));
let oob = || Box::new(EncodeNode::dummy_oob());
let node = EncodeNode::Split([leaf(), oob(), oob(), oob()]);
let leaves = round_trip_partition_shape(&node, 4, 4, BLOCK_32X32);
assert_eq!(leaves, vec![(0, 0, BLOCK_16X16)]);
}
#[test]
fn round_trip_two_level_split_64x64() {
let leaf = || Box::new(EncodeNode::Leaf(make_skip_leaf()));
let nw_inner = EncodeNode::Split([leaf(), leaf(), leaf(), leaf()]);
let node = EncodeNode::Split([Box::new(nw_inner), leaf(), leaf(), leaf()]);
let leaves = round_trip_partition_shape(&node, 16, 16, BLOCK_64X64);
assert_eq!(
leaves,
vec![
(0, 0, BLOCK_16X16),
(0, 4, BLOCK_16X16),
(4, 0, BLOCK_16X16),
(4, 4, BLOCK_16X16),
(0, 8, BLOCK_32X32),
(8, 0, BLOCK_32X32),
(8, 8, BLOCK_32X32),
]
);
}
#[allow(clippy::too_many_arguments)]
fn manual_decode_leaf(
dec: &mut SymbolDecoder<'_>,
cdfs: &mut TileCdfContext,
walker: &mut PartitionWalker,
sub_size: usize,
) -> EncodeBlock {
let skip_cdf = cdfs.skip_cdf(skip_ctx(0, 0));
let skip = dec.read_symbol(skip_cdf).unwrap() as u8;
let segment_id = 0u8;
let y_cdf = cdfs.y_mode_cdf(size_group(sub_size)).unwrap();
let y_mode = dec.read_symbol(y_cdf).unwrap() as u8;
let cfl_allowed = cfl_allowed_for_uv_mode(false, sub_size, false, false);
let uv_cdf = cdfs.uv_mode_cdf(cfl_allowed, y_mode as usize).unwrap();
let uv_mode = dec.read_symbol(uv_cdf).unwrap() as u8;
let _ = (walker, get_default_scan as fn(usize) -> &'static [u16]);
EncodeBlock {
skip,
segment_id,
segment_pred: 0,
y_mode,
uv_mode: Some(uv_mode),
cfl_alpha_u: None,
cfl_alpha_v: None,
coefficients: Vec::new(),
}
}
#[test]
fn round_trip_leaf_block_syntax_recovers_skip() {
let node = EncodeNode::Leaf(make_skip_leaf());
let mi_rows = 4;
let mi_cols = 4;
let b_size = BLOCK_16X16;
let mut writer = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut enc_state = fresh_state(mi_rows, mi_cols);
write_partition_tree(
&mut writer,
&mut enc_cdfs,
&mut enc_state,
&node,
0,
0,
b_size,
)
.unwrap();
let bytes = writer.finish();
let mut walker =
PartitionWalker::new(mi_rows, mi_cols, single_tile(mi_rows, mi_cols)).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let bsl = MI_WIDTH_LOG2[b_size] as u32;
let pctx_dec = 0; let part_cdf = dec_cdfs.partition_cdf(bsl, pctx_dec).unwrap();
let partition = dec.read_symbol(part_cdf).unwrap() as usize;
assert_eq!(partition, PARTITION_NONE);
let leaf = manual_decode_leaf(&mut dec, &mut dec_cdfs, &mut walker, b_size);
assert_eq!(leaf.skip, 1);
assert_eq!(leaf.y_mode, DC_PRED as u8);
assert_eq!(leaf.uv_mode, Some(DC_PRED as u8));
}
#[test]
fn round_trip_leaf_with_v_pred_y_mode() {
let mut block = make_skip_leaf();
block.y_mode = V_PRED as u8;
let node = EncodeNode::Leaf(block);
let mi_rows = 4;
let mi_cols = 4;
let b_size = BLOCK_16X16;
let mut writer = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut enc_state = fresh_state(mi_rows, mi_cols);
write_partition_tree(
&mut writer,
&mut enc_cdfs,
&mut enc_state,
&node,
0,
0,
b_size,
)
.unwrap();
let bytes = writer.finish();
let mut walker =
PartitionWalker::new(mi_rows, mi_cols, single_tile(mi_rows, mi_cols)).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let bsl = MI_WIDTH_LOG2[b_size] as u32;
let part_cdf = dec_cdfs.partition_cdf(bsl, 0).unwrap();
let _ = dec.read_symbol(part_cdf).unwrap();
let leaf = manual_decode_leaf(&mut dec, &mut dec_cdfs, &mut walker, b_size);
assert_eq!(leaf.y_mode, V_PRED as u8);
}
#[test]
fn round_trip_leaf_with_coefficients_all_zero() {
let node = EncodeNode::Leaf(make_zero_coeff_leaf());
let mi_rows = 4;
let mi_cols = 4;
let b_size = BLOCK_16X16;
let mut writer = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut enc_state = fresh_state(mi_rows, mi_cols);
write_partition_tree(
&mut writer,
&mut enc_cdfs,
&mut enc_state,
&node,
0,
0,
b_size,
)
.unwrap();
let bytes = writer.finish();
let mut walker =
PartitionWalker::new(mi_rows, mi_cols, single_tile(mi_rows, mi_cols)).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
let bsl = MI_WIDTH_LOG2[b_size] as u32;
let part_cdf = dec_cdfs.partition_cdf(bsl, 0).unwrap();
let _ = dec.read_symbol(part_cdf).unwrap();
let skip_cdf = dec_cdfs.skip_cdf(skip_ctx(0, 0));
let skip = dec.read_symbol(skip_cdf).unwrap() as u8;
assert_eq!(skip, 0);
let y_cdf = dec_cdfs.y_mode_cdf(size_group(b_size)).unwrap();
let y_mode = dec.read_symbol(y_cdf).unwrap() as u8;
assert_eq!(y_mode, DC_PRED as u8);
let cfl = cfl_allowed_for_uv_mode(false, b_size, false, false);
let uv_cdf = dec_cdfs.uv_mode_cdf(cfl, y_mode as usize).unwrap();
let uv_mode = dec.read_symbol(uv_cdf).unwrap() as u8;
assert_eq!(uv_mode, DC_PRED as u8);
for plane in 0u8..3 {
let mut quant_out = vec![0i32; 16];
let readout: CoefficientsReadout = walker
.coefficients(
&mut dec,
&mut dec_cdfs,
plane,
0,
TX_4X4,
TX_CLASS_2D,
0,
0,
get_default_scan(TX_4X4),
&mut quant_out,
)
.unwrap();
assert!(readout.all_zero, "plane {plane} all_zero on round-trip");
assert_eq!(readout.eob, 0);
assert!(quant_out.iter().all(|&q| q == 0));
}
let _ = (
get_coeff_base_ctx,
get_coeff_base_eob_ctx,
get_br_ctx,
MAX_SEGMENTS,
);
}
use crate::cdf::PALETTE_COLORS;
use crate::encoder::partition_tree::{
write_partition_tree_syntax, PartitionSyntaxWriter, SyntaxBlock, SyntaxFrameParams,
SyntaxNode, SyntaxPalette,
};
fn syntax_round_trip(
node: &SyntaxNode,
mi_rows: u32,
mi_cols: u32,
b_size: usize,
params: &SyntaxFrameParams,
sentinel: u8,
) -> (PartitionSyntaxWriter, PartitionWalker) {
let mut writer = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut enc_state =
PartitionSyntaxWriter::new(mi_rows, mi_cols, single_tile(mi_rows, mi_cols))
.expect("syntax writer construction");
write_partition_tree_syntax(
&mut writer,
&mut enc_cdfs,
&mut enc_state,
node,
0,
0,
b_size,
params,
)
.expect("write_partition_tree_syntax");
writer.write_literal(8, u32::from(sentinel)).unwrap();
let bytes = writer.finish();
let mut walker =
PartitionWalker::new(mi_rows, mi_cols, single_tile(mi_rows, mi_cols)).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
walker
.decode_partition_syntax(
&mut dec,
&mut dec_cdfs,
0,
0,
b_size,
true,
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,
¶ms.lossless_array,
params.coded_lossless,
params.enable_cdef,
params.allow_intrabc,
params.cdef_bits,
params.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,
None,
¶ms.quant,
params.reduced_tx_set,
)
.expect("decode_partition_syntax must consume the full tree");
assert_eq!(
dec.read_literal(8).unwrap(),
u32::from(sentinel),
"decoder must be positioned at the sentinel after the tree"
);
assert_eq!(
enc_cdfs, dec_cdfs,
"encoder and decoder CDF contexts must adapt identically"
);
let m = enc_state.mirror();
assert_eq!(m.mi_sizes(), walker.mi_sizes(), "MiSizes parity");
assert_eq!(m.skips(), walker.skips(), "Skips parity");
assert_eq!(m.segment_ids(), walker.segment_ids(), "SegmentIds parity");
assert_eq!(m.y_modes(), walker.y_modes(), "YModes parity");
assert_eq!(m.is_inters(), walker.is_inters(), "IsInters parity");
assert_eq!(m.ref_frames(), walker.ref_frames(), "RefFrames parity");
assert_eq!(m.mvs(), walker.mvs(), "Mvs parity");
assert_eq!(
m.interp_filters(),
walker.interp_filters(),
"InterpFilters parity"
);
assert_eq!(
m.palette_sizes(),
walker.palette_sizes(),
"PaletteSizes parity"
);
assert_eq!(
m.palette_colors(),
walker.palette_colors(),
"PaletteColors parity"
);
assert_eq!(m.cdef_idx(), walker.cdef_idx(), "cdef_idx parity");
assert_eq!(m.tx_sizes(), walker.tx_sizes(), "TxSizes parity");
assert_eq!(
m.inter_tx_sizes(),
walker.inter_tx_sizes(),
"InterTxSizes parity"
);
assert_eq!(m.tx_types(), walker.tx_types(), "TxTypes parity");
assert_eq!(
m.above_level_context(),
walker.above_level_context(),
"AboveLevelContext parity"
);
assert_eq!(
m.above_dc_context(),
walker.above_dc_context(),
"AboveDcContext parity"
);
assert_eq!(
m.left_level_context(),
walker.left_level_context(),
"LeftLevelContext parity"
);
assert_eq!(
m.left_dc_context(),
walker.left_dc_context(),
"LeftDcContext parity"
);
(enc_state, walker)
}
#[test]
fn r282_syntax_round_trip_split_else_arm_neighbour_ctx() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.enable_filter_intra = true;
let mut nw = SyntaxBlock::skip_leaf(V_PRED as u8, Some(V_PRED as u8));
nw.angle_delta_y = 2;
nw.angle_delta_uv = -1;
let mut ne = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
ne.use_filter_intra = 1;
ne.filter_intra_mode = Some(2);
let mut sw = SyntaxBlock::skip_leaf(2, Some(DC_PRED as u8));
sw.angle_delta_y = -2;
let se = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0xA5);
let cols = 4usize;
for (r0, c0, want) in [(0, 0, V_PRED as u8), (0, 2, 0u8), (2, 0, 2u8), (2, 2, 0u8)] {
for dr in 0..2usize {
for dc in 0..2usize {
assert_eq!(
walker.y_modes()[(r0 + dr) * cols + (c0 + dc)],
want,
"YModes stamp at ({}, {})",
r0 + dr,
c0 + dc
);
}
}
}
assert!(
walker.skips().iter().all(|&s| s == 1),
"every leaf committed skip = 1"
);
}
#[test]
fn r282_syntax_round_trip_intrabc_pair_threads_mv_stack() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.allow_intrabc = true;
let mut nw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
nw.intrabc_mv = Some([0, -2560]);
let mut ne = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
ne.intrabc_mv = Some([-8, -2560]);
let sw = SyntaxBlock::skip_leaf(V_PRED as u8, Some(DC_PRED as u8));
let se = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x5A);
let cols = 4usize;
for (r0, c0, want) in [(0usize, 0usize, [0i16, -2560]), (0, 2, [-8, -2560])] {
for dr in 0..2usize {
for dc in 0..2usize {
let cell = (r0 + dr) * cols + (c0 + dc);
assert_eq!(walker.is_inters()[cell], 1, "intrabc IsInters stamp");
assert_eq!(
[
walker.mvs()[(cell * 2) * 2],
walker.mvs()[(cell * 2) * 2 + 1]
],
want,
"decoded Mv[0] at ({}, {})",
r0 + dr,
c0 + dc
);
}
}
}
assert_eq!(walker.is_inters()[2 * cols], 0, "SW else-arm IsInters");
}
#[test]
fn r282_syntax_round_trip_palette_neighbours_share_cache() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.allow_screen_content_tools = true;
let checker: Vec<u8> = (0..64).map(|i| (((i / 8) + (i % 8)) & 1) as u8).collect();
let stripes: Vec<u8> = (0..64).map(|i| u8::from((i % 8) >= 4)).collect();
let bands: Vec<u8> = (0..64).map(|i| (((i / 8) + (i % 8)) % 3) as u8).collect();
let colors = |vals: &[u16]| {
let mut a = [0u16; PALETTE_COLORS];
a[..vals.len()].copy_from_slice(vals);
a
};
let mut nw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
nw.palette = SyntaxPalette {
size_y: 2,
colors_y: colors(&[10, 200]),
color_map_y: checker.clone(),
size_uv: 2,
colors_u: colors(&[20, 30]),
colors_v: colors(&[40, 50]),
delta_encode_v: false,
color_map_uv: stripes.clone(),
};
let mut ne = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
ne.palette = SyntaxPalette {
size_y: 2,
colors_y: colors(&[10, 100]),
color_map_y: stripes.clone(),
..SyntaxPalette::default()
};
let mut sw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
sw.palette = SyntaxPalette {
size_y: 3,
colors_y: colors(&[5, 60, 200]),
color_map_y: bands.clone(),
..SyntaxPalette::default()
};
let se = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0xC3);
let cols = 4usize;
let area = 16usize;
for (r0, c0, size, first_two) in [
(0usize, 0usize, 2u8, [10u16, 200]),
(0, 2, 2, [10, 100]),
(2, 0, 3, [5, 60]),
(2, 2, 0, [0, 0]),
] {
let cell = r0 * cols + c0;
assert_eq!(walker.palette_sizes()[cell], size, "PaletteSizes[0] stamp");
if size > 0 {
assert_eq!(
walker.palette_colors()[cell * PALETTE_COLORS],
first_two[0],
"PaletteColors[0][..][0]"
);
assert_eq!(
walker.palette_colors()[cell * PALETTE_COLORS + 1],
first_two[1],
"PaletteColors[0][..][1]"
);
}
}
assert_eq!(walker.palette_sizes()[area], 2, "PaletteSizes[1] NW");
assert_eq!(walker.palette_sizes()[2 * area], 2, "PaletteSizes[2] NW");
assert_eq!(
walker.palette_colors()[area * PALETTE_COLORS],
20,
"PaletteColors[1][NW][0]"
);
assert_eq!(
walker.palette_colors()[2 * area * PALETTE_COLORS],
40,
"PaletteColors[2][NW][0]"
);
}
#[test]
fn r282_syntax_round_trip_segmentation_and_deltas() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.segmentation_enabled = true;
params.seg_id_pre_skip = true;
params.last_active_seg_id = 3;
params.read_deltas = true;
params.delta_q_res = 2;
params.delta_lf_present = true;
params.delta_lf_res = 1;
let mut nw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
nw.segment_id = 2;
nw.reduced_delta_q_index = 3;
nw.reduced_delta_lf = [1, 0, 0, 0];
let mut ne = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
ne.segment_id = 1;
let mut sw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
sw.segment_id = 0;
let mut se = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
se.segment_id = 3;
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x96);
let cols = 4usize;
for (r0, c0, want) in [(0usize, 0usize, 2i32), (0, 2, 1), (2, 0, 0), (2, 2, 3)] {
assert_eq!(
walker.segment_ids()[r0 * cols + c0],
want,
"SegmentIds stamp at ({r0}, {c0})"
);
}
assert_eq!(walker.current_q_index(), 12, "CurrentQIndex accumulation");
assert_eq!(walker.current_delta_lf()[0], 2, "DeltaLF[0] accumulation");
}
#[test]
fn r283_write_block_syntax_scope_rejects() {
let params = SyntaxFrameParams::intra_8bit_baseline();
let mut writer = SymbolWriter::new(true);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.skip = 0;
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.skip = 0;
block.residual_quant = vec![vec![0i32; 16], vec![0i32; 64], vec![0i32; 64]].into();
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.skip = 0;
block.residual_quant = vec![vec![0i32; 64]; 4].into();
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.residual_quant = vec![vec![0i32; 64]; 3].into();
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.tx_size = Some(TX_4X4 as u8);
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut params_bc = params.clone();
params_bc.allow_intrabc = true;
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
block.skip = 0;
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms_bc,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
fn quant_with(len: usize, entries: &[(usize, i32)]) -> Vec<i32> {
let mut q = vec![0i32; len];
for &(pos, val) in entries {
q[pos] = val;
}
q
}
#[test]
fn r283_syntax_round_trip_residual_single_16x16_leaf() {
let params = SyntaxFrameParams::intra_8bit_baseline();
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.skip = 0;
leaf.residual_quant = vec![
quant_with(256, &[(0, 5), (1, -2), (16, 1)]),
quant_with(256, &[(0, -7)]),
quant_with(256, &[(0, 40), (2, 3)]),
]
.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0xA7);
assert!(
walker.skips().iter().all(|&s| s == 0),
"decoded Skips[] = 0 over the leaf footprint"
);
assert!(
walker
.tx_sizes()
.iter()
.all(|&t| t as usize == crate::cdf::TX_16X16),
"TxSizes[] = TX_16X16 over the 16×16 leaf"
);
}
#[test]
fn r283_syntax_round_trip_residual_split_mixed_skip() {
let params = SyntaxFrameParams::intra_8bit_baseline();
let mut nw = SyntaxBlock::skip_leaf(V_PRED as u8, Some(DC_PRED as u8));
nw.skip = 0;
nw.residual_quant = vec![
quant_with(64, &[(0, 3), (1, 1)]),
quant_with(64, &[(0, -1)]),
quant_with(64, &[(8, 2)]),
]
.into();
let ne = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let mut sw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
sw.skip = 0;
sw.residual_quant = vec![vec![0i32; 64]; 3].into();
let mut se = SyntaxBlock::skip_leaf(2, Some(DC_PRED as u8));
se.skip = 0;
se.residual_quant = vec![quant_with(64, &[(0, -4)]), vec![0i32; 64], vec![0i32; 64]].into();
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x3C);
let cols = 4usize;
for (r0, c0, want) in [(0usize, 0usize, 0u8), (0, 2, 1), (2, 0, 0), (2, 2, 0)] {
assert_eq!(
walker.skips()[r0 * cols + c0],
want,
"Skips stamp at ({r0}, {c0})"
);
}
}
#[test]
fn r283_syntax_round_trip_residual_lossless_tx4x4_fan_out() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.lossless_array = [true; MAX_SEGMENTS];
params.coded_lossless = true;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.skip = 0;
let mut tus = Vec::new();
for plane in 0..3 {
for tu in 0..4 {
tus.push(if tu == 0 {
quant_with(16, &[(0, plane + 1)])
} else {
vec![0i32; 16]
});
}
}
leaf.residual_quant = tus.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 2, 2, BLOCK_8X8, ¶ms, 0xE1);
assert!(
walker.tx_sizes().iter().all(|&t| t as usize == TX_4X4),
"lossless TxSizes[] = TX_4X4"
);
}
#[test]
fn r283_syntax_round_trip_residual_420_chroma_sizing() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.subsampling_x = 1;
params.subsampling_y = 1;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.skip = 0;
leaf.residual_quant = vec![
quant_with(256, &[(0, 2), (1, 1)]),
quant_with(64, &[(0, -3)]),
quant_with(64, &[(0, 6), (8, -1)]),
]
.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let _ = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x4B);
}
#[test]
fn r283_syntax_round_trip_residual_monochrome_luma_only() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.num_planes = 1;
params.mono_chrome = true;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
leaf.skip = 0;
leaf.residual_quant = vec![quant_with(256, &[(0, 9), (3, -2)])].into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let _ = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0xD2);
}
#[test]
fn r283_syntax_round_trip_residual_chroma_mode_to_txfm() {
let params = SyntaxFrameParams::intra_8bit_baseline();
let mut leaf = SyntaxBlock::skip_leaf(V_PRED as u8, Some(V_PRED as u8));
leaf.skip = 0;
leaf.residual_quant = vec![
quant_with(256, &[(0, 1)]),
quant_with(256, &[(0, 2), (16, 1)]),
quant_with(256, &[(0, -2)]),
]
.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let _ = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x78);
}
#[test]
fn r284_syntax_round_trip_txb_level_context_stamps_and_skip_reset() {
let params = SyntaxFrameParams::intra_8bit_baseline();
let mut nw = SyntaxBlock::skip_leaf(V_PRED as u8, Some(DC_PRED as u8));
nw.skip = 0;
nw.residual_quant = vec![
quant_with(64, &[(0, -3), (1, 1)]),
quant_with(64, &[(0, -1)]),
quant_with(64, &[(8, 2)]),
]
.into();
let ne = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let mut sw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
sw.skip = 0;
sw.residual_quant = vec![
quant_with(64, &[(0, -5), (1, 2), (8, 1)]),
vec![0i32; 64],
quant_with(64, &[(0, 4)]),
]
.into();
let mut se = SyntaxBlock::skip_leaf(2, Some(DC_PRED as u8));
se.skip = 0;
se.residual_quant = vec![
quant_with(64, &[(0, 6), (2, 1)]),
vec![0i32; 64],
vec![0i32; 64],
]
.into();
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x5D);
let alc = walker.above_level_context();
let adc = walker.above_dc_context();
let llc = walker.left_level_context();
let ldc = walker.left_dc_context();
assert_eq!(&alc[0..4], &[8, 8, 7, 7], "luma AboveLevelContext");
assert_eq!(&adc[0..4], &[1, 1, 2, 2], "luma AboveDcContext");
assert_eq!(&llc[0..4], &[0, 0, 7, 7], "luma LeftLevelContext");
assert_eq!(&ldc[0..4], &[0, 0, 2, 2], "luma LeftDcContext");
}
#[test]
fn r284_syntax_round_trip_txb_ctx_lossless_420_chroma_bsize_arm() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.subsampling_x = 1;
params.subsampling_y = 1;
params.lossless_array = [true; MAX_SEGMENTS];
params.coded_lossless = true;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.skip = 0;
let mut tus = Vec::new();
for tu in 0..16 {
tus.push(match tu % 3 {
0 => quant_with(16, &[(0, 2)]),
1 => quant_with(16, &[(0, -1), (1, 1)]),
_ => vec![0i32; 16],
});
}
for plane in 0..2 {
for tu in 0..4 {
tus.push(if (tu + plane) % 2 == 0 {
quant_with(16, &[(0, if plane == 0 { -4 } else { 4 })])
} else {
vec![0i32; 16]
});
}
}
leaf.residual_quant = tus.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x9E);
let alc = walker.above_level_context();
let adc = walker.above_dc_context();
assert_eq!(&alc[4..6], &[4, 0], "plane-1 AboveLevelContext");
assert_eq!(&adc[4..6], &[1, 0], "plane-1 AboveDcContext");
assert_eq!(&alc[8..10], &[0, 4], "plane-2 AboveLevelContext");
assert_eq!(&adc[8..10], &[0, 2], "plane-2 AboveDcContext");
}
#[test]
fn r284_syntax_round_trip_intrabc_skip0_transform_tree() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.allow_intrabc = true;
let mut nw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
nw.intrabc_mv = Some([0, -2560]);
nw.skip = 0;
nw.residual_quant = vec![
quant_with(64, &[(0, -3), (1, 2)]),
quant_with(64, &[(0, 4)]),
quant_with(64, &[(2, 1)]),
]
.into();
let mut ne = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
ne.intrabc_mv = Some([-8, -2560]);
let mut sw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
sw.skip = 0;
sw.residual_quant = vec![
quant_with(64, &[(0, 7)]),
vec![0i32; 64],
quant_with(64, &[(0, -2)]),
]
.into();
let se = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0xB6);
assert_eq!(walker.is_inters()[0], 1, "NW IsInters");
assert_eq!(walker.skips()[0], 0, "NW skip = 0");
let alc = walker.above_level_context();
let adc = walker.above_dc_context();
assert_eq!(&alc[0..4], &[7, 7, 0, 0], "luma AboveLevelContext");
assert_eq!(&adc[0..4], &[2, 2, 0, 0], "luma AboveDcContext");
}
#[test]
fn r285_syntax_round_trip_tx_depth_intra_16x16() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.tx_mode_select = true;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.skip = 0;
leaf.tx_size = Some(TX_8X8 as u8);
leaf.residual_quant = vec![
quant_with(64, &[(0, 5), (1, -2)]),
quant_with(64, &[(0, 1)]),
quant_with(64, &[(3, -4)]),
quant_with(64, &[(0, 2), (8, 1)]),
quant_with(256, &[(0, -7)]),
quant_with(256, &[(0, 40), (2, 3)]),
]
.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0xC1);
assert!(
walker.tx_sizes().iter().all(|&t| t as usize == TX_8X8),
"committed TxSize = TX_8X8 over the leaf footprint"
);
assert!(
walker
.inter_tx_sizes()
.iter()
.all(|&t| t as usize == TX_8X8),
"§5.11.16 else-arm InterTxSizes fill follows the committed TxSize"
);
}
#[test]
fn r285_syntax_round_trip_tx_depth_split_neighbour_ctx_and_silent_arm() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.tx_mode_select = true;
params.allow_intrabc = true;
let nw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let mut ne = SyntaxBlock::skip_leaf(V_PRED as u8, Some(DC_PRED as u8));
ne.tx_size = Some(TX_4X4 as u8);
let mut sw = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
sw.tx_size = Some(TX_4X4 as u8);
let mut se = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
se.intrabc_mv = Some([-8, -2560]);
se.tx_size = Some(TX_8X8 as u8);
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0xC2);
for r in 0..4usize {
for c in 0..4usize {
let want = match (r >= 2, c >= 2) {
(false, false) | (true, true) => TX_8X8,
_ => TX_4X4,
};
assert_eq!(
walker.inter_tx_sizes()[r * 4 + c] as usize,
want,
"InterTxSizes[{r}][{c}]"
);
}
}
}
#[test]
fn r285_syntax_round_trip_var_tx_intrabc_32x32() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.tx_mode_select = true;
params.allow_intrabc = true;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.intrabc_mv = Some([0, -2560]);
leaf.skip = 0;
leaf.var_tx_trees = vec![VarTxSyntaxTree::Split(vec![
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Split(vec![
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
]),
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
])];
leaf.residual_quant = vec![
quant_with(256, &[(0, 9), (1, -1)]),
quant_with(64, &[(0, -3)]),
quant_with(64, &[(1, 2)]),
quant_with(64, &[(0, 1)]),
vec![0i32; 64], quant_with(256, &[(0, -5)]),
quant_with(256, &[(2, 4)]),
quant_with(1024, &[(0, 6)]),
quant_with(1024, &[(0, -2), (1, 1)]),
]
.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 8, 8, BLOCK_32X32, ¶ms, 0xC3);
for r in 0..8usize {
for c in 0..8usize {
let want = if r < 4 && c >= 4 { TX_8X8 } else { TX_16X16 };
assert_eq!(
walker.inter_tx_sizes()[r * 8 + c] as usize,
want,
"InterTxSizes[{r}][{c}]"
);
}
}
assert!(
walker.tx_sizes().iter().all(|&t| t as usize == TX_16X16),
"TxSizes[] = last terminal-else txSz over the footprint"
);
assert_eq!(walker.is_inters()[0], 1, "intrabc leaf is inter");
assert_eq!(walker.skips()[0], 0, "skip = 0");
}
#[test]
fn r285_syntax_round_trip_var_tx_frame_edge_clip() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.tx_mode_select = true;
params.allow_intrabc = true;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.intrabc_mv = Some([0, -2560]);
leaf.skip = 0;
leaf.var_tx_trees = vec![VarTxSyntaxTree::Split(vec![
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Split(vec![
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
]),
VarTxSyntaxTree::Leaf,
])];
leaf.residual_quant = vec![
quant_with(256, &[(0, 3)]),
quant_with(256, &[(1, -2)]),
quant_with(64, &[(0, 4)]),
quant_with(64, &[(0, -1)]),
quant_with(256, &[(0, 2)]),
quant_with(1024, &[(0, -6)]),
quant_with(1024, &[(0, 1)]),
]
.into();
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 6, 8, BLOCK_32X32, ¶ms, 0xC5);
for r in 0..6usize {
for c in 0..8usize {
let want = if r >= 4 && c < 4 { TX_8X8 } else { TX_16X16 };
assert_eq!(
walker.inter_tx_sizes()[r * 8 + c] as usize,
want,
"InterTxSizes[{r}][{c}]"
);
}
}
assert!(
walker.tx_sizes().iter().all(|&t| t as usize == TX_16X16),
"TxSizes[] = last terminal-else txSz"
);
}
#[test]
fn r285_syntax_round_trip_tx_mode_select_lossless_silent() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.tx_mode_select = true;
params.lossless_array[0] = true;
let leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 2, 2, BLOCK_8X8, ¶ms, 0xC4);
assert!(
walker.tx_sizes().iter().all(|&t| t as usize == TX_4X4),
"Lossless forces TX_4X4 with no tx_depth symbol"
);
}
#[test]
fn r285_write_block_tx_size_scope_rejects() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.tx_mode_select = true;
params.allow_intrabc = true;
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.tx_size = Some(TX_4X4 as u8);
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_64X64,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.var_tx_trees = vec![VarTxSyntaxTree::Leaf];
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_16X16,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
block.skip = 0;
block.tx_size = Some(TX_16X16 as u8);
block.var_tx_trees = vec![VarTxSyntaxTree::Leaf];
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_16X16,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
block.skip = 0;
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_16X16,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
block.skip = 0;
block.var_tx_trees = vec![VarTxSyntaxTree::Leaf, VarTxSyntaxTree::Leaf];
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_16X16,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(2, 2, single_tile(2, 2)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
block.skip = 0;
block.var_tx_trees = vec![VarTxSyntaxTree::Split(vec![
VarTxSyntaxTree::Split(Vec::new()),
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
])];
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_8X8,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
block.skip = 0;
block.var_tx_trees = vec![VarTxSyntaxTree::Split(vec![
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
VarTxSyntaxTree::Leaf,
])];
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_16X16,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut state = PartitionSyntaxWriter::new(4, 4, single_tile(4, 4)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.intrabc_mv = Some([0, -2560]);
block.var_tx_trees = vec![VarTxSyntaxTree::Leaf];
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_16X16,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
}
#[test]
fn r286_syntax_round_trip_transform_type_intra_8x8_adst() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.quant.base_q_idx = 64;
params.quant.current_q_index = 64;
let mut leaf = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf.skip = 0;
leaf.residual_quant = vec![
quant_with(64, &[(0, 5), (1, -2), (8, 1)]),
quant_with(64, &[(0, -3)]),
quant_with(64, &[(0, 4), (2, 1)]),
]
.into();
leaf.residual_tx_type = vec![crate::cdf::ADST_ADST as u8];
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, walker) = syntax_round_trip(&node, 2, 2, BLOCK_8X8, ¶ms, 0xD5);
assert!(
walker
.tx_types()
.iter()
.all(|&t| t as usize == crate::cdf::ADST_ADST),
"decoded TxTypes[] = ADST_ADST over the 8×8 luma footprint"
);
}
#[test]
fn r286_syntax_round_trip_transform_type_per_tu_fanout() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.tx_mode_select = true;
params.quant.base_q_idx = 110;
params.quant.current_q_index = 110;
let mut leaf = SyntaxBlock::skip_leaf(V_PRED as u8, Some(DC_PRED as u8));
leaf.skip = 0;
leaf.tx_size = Some(TX_8X8 as u8); leaf.residual_quant = vec![
quant_with(64, &[(0, 3), (1, 1)]),
quant_with(64, &[(0, -2)]),
quant_with(64, &[(0, 1), (8, 2)]),
quant_with(64, &[(2, -1)]),
quant_with(256, &[(0, -4)]),
quant_with(256, &[(0, 6)]),
]
.into();
leaf.residual_tx_type = vec![
crate::cdf::ADST_DCT as u8,
crate::cdf::DCT_ADST as u8,
crate::cdf::ADST_ADST as u8,
DCT_DCT as u8,
];
let node = SyntaxNode::Leaf(Box::new(leaf));
let (_enc, _walker) = syntax_round_trip(&node, 4, 4, BLOCK_16X16, ¶ms, 0x9E);
}
#[test]
fn r286_syntax_round_trip_transform_type_dctonly_silent_paths() {
let params0 = SyntaxFrameParams::intra_8bit_baseline();
let mut leaf0 = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf0.skip = 0;
leaf0.residual_quant = vec![quant_with(256, &[(0, 2)]); 3].into();
let node0 = SyntaxNode::Leaf(Box::new(leaf0));
let (_e0, w0) = syntax_round_trip(&node0, 4, 4, BLOCK_16X16, ¶ms0, 0x11);
assert!(
w0.tx_types().iter().all(|&t| t as usize == DCT_DCT),
"base_q_idx = 0 ⇒ TxTypes[] all DCT_DCT"
);
let mut params1 = SyntaxFrameParams::intra_8bit_baseline();
params1.quant.base_q_idx = 200;
params1.quant.current_q_index = 200;
let mut leaf1 = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
leaf1.skip = 0;
leaf1.residual_quant = vec![
quant_with(1024, &[(0, 3)]),
quant_with(1024, &[(0, -1)]),
quant_with(1024, &[(0, 2)]),
]
.into();
leaf1.residual_tx_type = vec![DCT_DCT as u8];
let node1 = SyntaxNode::Leaf(Box::new(leaf1));
let (_e1, w1) = syntax_round_trip(&node1, 8, 8, BLOCK_32X32, ¶ms1, 0x22);
assert!(
w1.tx_types().iter().all(|&t| t as usize == DCT_DCT),
"TX_32X32 ⇒ TX_SET_DCTONLY ⇒ TxTypes[] all DCT_DCT"
);
}
#[test]
fn r286_transform_type_scope_rejects() {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.quant.base_q_idx = 200;
params.quant.current_q_index = 200;
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(8, 8, single_tile(8, 8)).unwrap();
let mut block = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block.skip = 0;
block.residual_quant = vec![
quant_with(1024, &[(0, 3)]),
quant_with(1024, &[(0, -1)]),
quant_with(1024, &[(0, 2)]),
]
.into();
block.residual_tx_type = vec![crate::cdf::ADST_ADST as u8];
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_32X32,
¶ms,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let mut params2 = SyntaxFrameParams::intra_8bit_baseline();
params2.quant.base_q_idx = 64;
params2.quant.current_q_index = 64;
let mut writer2 = SymbolWriter::new(false);
let mut cdfs2 = TileCdfContext::new_from_defaults();
let mut state2 = PartitionSyntaxWriter::new(2, 2, single_tile(2, 2)).unwrap();
let mut block2 = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block2.skip = 0;
block2.residual_quant = vec![quant_with(64, &[(0, 1)]); 3].into();
block2.residual_tx_type = vec![crate::cdf::FLIPADST_DCT as u8];
let err2 = super::write_block_syntax(
&mut writer2,
&mut cdfs2,
&mut state2,
&block2,
0,
0,
BLOCK_8X8,
¶ms2,
)
.unwrap_err();
assert!(matches!(err2, Error::PartitionWalkOutOfRange));
let mut params3 = SyntaxFrameParams::intra_8bit_baseline();
params3.quant.base_q_idx = 64;
params3.quant.current_q_index = 64;
let mut writer3 = SymbolWriter::new(false);
let mut cdfs3 = TileCdfContext::new_from_defaults();
let mut state3 = PartitionSyntaxWriter::new(2, 2, single_tile(2, 2)).unwrap();
let mut block3 = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
block3.skip = 0;
block3.residual_quant = vec![quant_with(64, &[(0, 1)]); 3].into();
block3.residual_tx_type = vec![DCT_DCT as u8, DCT_DCT as u8];
let err3 = super::write_block_syntax(
&mut writer3,
&mut cdfs3,
&mut state3,
&block3,
0,
0,
BLOCK_8X8,
¶ms3,
)
.unwrap_err();
assert!(matches!(err3, Error::PartitionWalkOutOfRange));
}
use crate::cdf::{InterFrameContext, MODE_GLOBALMV, MODE_NEARESTMV, MODE_NEWMV};
use crate::encoder::partition_tree::{SyntaxInterBlock, SyntaxInterFrameParams};
fn inter_ctx_for<'a>(ipp: &'a SyntaxInterFrameParams) -> InterFrameContext<'a> {
let mut c = InterFrameContext::identity_default(&ipp.motion_field_mvs);
c.skip_mode_present = ipp.skip_mode_present;
c.skip_mode_frame = [
i32::from(ipp.skip_mode_frame[0]),
i32::from(ipp.skip_mode_frame[1]),
];
c.reference_select = ipp.reference_select;
c.gm_type = ipp.gm_type;
c.gm_params = ipp.gm_params;
c.ref_frame_sign_bias = ipp.ref_frame_sign_bias;
c.allow_high_precision_mv = ipp.allow_high_precision_mv;
c.force_integer_mv = ipp.force_integer_mv;
c.use_ref_frame_mvs = ipp.use_ref_frame_mvs;
c.is_motion_mode_switchable = ipp.is_motion_mode_switchable;
c.allow_warped_motion = ipp.allow_warped_motion;
c.is_scaled_per_ref = ipp.is_scaled_per_ref;
c.enable_interintra_compound = ipp.enable_interintra_compound;
c.enable_masked_compound = ipp.enable_masked_compound;
c.enable_jnt_comp = ipp.enable_jnt_comp;
c.order_hints = ipp.order_hints;
c.interpolation_filter = ipp.interpolation_filter;
c.enable_dual_filter = ipp.enable_dual_filter;
c
}
fn syntax_round_trip_inter(
node: &SyntaxNode,
mi_rows: u32,
mi_cols: u32,
b_size: usize,
params: &SyntaxFrameParams,
sentinel: u8,
) -> (PartitionSyntaxWriter, PartitionWalker) {
let ipp = params.inter.as_ref().expect("inter params required");
let mut writer = SymbolWriter::new(false);
let mut enc_cdfs = TileCdfContext::new_from_defaults();
let mut enc_state =
PartitionSyntaxWriter::new(mi_rows, mi_cols, single_tile(mi_rows, mi_cols))
.expect("syntax writer construction");
write_partition_tree_syntax(
&mut writer,
&mut enc_cdfs,
&mut enc_state,
node,
0,
0,
b_size,
params,
)
.expect("write_partition_tree_syntax (inter)");
writer.write_literal(8, u32::from(sentinel)).unwrap();
let bytes = writer.finish();
let ictx = inter_ctx_for(ipp);
let mut walker =
PartitionWalker::new(mi_rows, mi_cols, single_tile(mi_rows, mi_cols)).unwrap();
let mut dec_cdfs = TileCdfContext::new_from_defaults();
let mut dec = SymbolDecoder::init_symbol(&bytes, bytes.len(), false).unwrap();
walker
.decode_partition_syntax(
&mut dec,
&mut dec_cdfs,
0,
0,
b_size,
false,
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,
¶ms.lossless_array,
params.coded_lossless,
params.enable_cdef,
params.allow_intrabc,
params.cdef_bits,
params.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,
Some(&ictx),
¶ms.quant,
params.reduced_tx_set,
)
.expect("decode_partition_syntax must consume the full inter tree");
assert_eq!(
dec.read_literal(8).unwrap(),
u32::from(sentinel),
"decoder must be positioned at the sentinel after the inter tree"
);
assert_eq!(
enc_cdfs, dec_cdfs,
"encoder and decoder CDF contexts must adapt identically"
);
let m = enc_state.mirror();
assert_eq!(m.mi_sizes(), walker.mi_sizes(), "MiSizes parity");
assert_eq!(m.skips(), walker.skips(), "Skips parity");
assert_eq!(m.segment_ids(), walker.segment_ids(), "SegmentIds parity");
assert_eq!(m.y_modes(), walker.y_modes(), "YModes parity");
assert_eq!(m.is_inters(), walker.is_inters(), "IsInters parity");
assert_eq!(m.ref_frames(), walker.ref_frames(), "RefFrames parity");
assert_eq!(m.mvs(), walker.mvs(), "Mvs parity");
assert_eq!(
m.interp_filters(),
walker.interp_filters(),
"InterpFilters parity"
);
assert_eq!(
m.motion_modes(),
walker.motion_modes(),
"MotionModes parity"
);
assert_eq!(m.cdef_idx(), walker.cdef_idx(), "cdef_idx parity");
assert_eq!(m.tx_sizes(), walker.tx_sizes(), "TxSizes parity");
assert_eq!(
m.inter_tx_sizes(),
walker.inter_tx_sizes(),
"InterTxSizes parity"
);
assert_eq!(m.tx_types(), walker.tx_types(), "TxTypes parity");
assert_eq!(
m.above_level_context(),
walker.above_level_context(),
"AboveLevelContext parity"
);
assert_eq!(
m.above_dc_context(),
walker.above_dc_context(),
"AboveDcContext parity"
);
assert_eq!(
m.left_level_context(),
walker.left_level_context(),
"LeftLevelContext parity"
);
assert_eq!(
m.left_dc_context(),
walker.left_dc_context(),
"LeftDcContext parity"
);
(enc_state, walker)
}
fn inter_params_420(mi_rows: u32, mi_cols: u32, lossless: bool) -> SyntaxFrameParams {
let mut params = SyntaxFrameParams::intra_8bit_baseline();
params.subsampling_x = 1;
params.subsampling_y = 1;
params.enable_cdef = false;
params.coded_lossless = lossless;
params.lossless_array = [lossless; MAX_SEGMENTS];
if !lossless {
params.quant = crate::cdf::QuantizerParams::neutral(100, 8);
}
params.inter = Some(SyntaxInterFrameParams::single_ref_baseline(
mi_rows, mi_cols, false,
));
params
}
fn inter_skip_leaf(y_mode: u8, mv: [i32; 2]) -> SyntaxBlock {
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, -1],
y_mode,
mv: [mv, [0, 0]],
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: None,
wedge_interintra: 0,
interintra_wedge_index: 0,
motion_mode: MOTION_MODE_SIMPLE,
});
b
}
#[test]
fn r411_inter_syntax_round_trip_newmv_globalmv_intra_mix() {
let params = inter_params_420(16, 16, true);
let nw = inter_skip_leaf(MODE_NEWMV, [10, -14]);
let ne = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let mut sw = SyntaxBlock::skip_leaf(V_PRED as u8, Some(DC_PRED as u8));
sw.angle_delta_y = 1;
let mut se = inter_skip_leaf(MODE_NEWMV, [-8, 24]);
se.skip = 0;
se.residual_quant = vec![vec![0i32; 16]; 64 + 16 + 16].into();
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0xA7);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
assert_eq!(walker.is_inters()[cell(0, 0)], 1);
assert_eq!(
[
walker.mvs()[cell(0, 0) * 4],
walker.mvs()[cell(0, 0) * 4 + 1]
],
[10i16, -14]
);
assert_eq!(walker.is_inters()[cell(0, 8)], 1);
assert_eq!(
[
walker.mvs()[cell(0, 8) * 4],
walker.mvs()[cell(0, 8) * 4 + 1]
],
[0i16, 0]
);
assert_eq!(walker.is_inters()[cell(8, 0)], 0);
assert_eq!(walker.y_modes()[cell(8, 0)], V_PRED as u8);
assert_eq!(walker.ref_frames()[cell(8, 8) * 2], 1);
assert_eq!(walker.ref_frames()[cell(8, 8) * 2 + 1], -1);
}
#[test]
fn r412_inter_syntax_round_trip_switchable_filters() {
let mut params = inter_params_420(16, 16, true);
params
.inter
.as_mut()
.expect("inter params")
.interpolation_filter = SWITCHABLE;
use crate::inter_pred::{EIGHTTAP_SHARP, EIGHTTAP_SMOOTH};
let mut nw = inter_skip_leaf(MODE_NEWMV, [10, -14]);
nw.inter.as_mut().unwrap().interp_filter = [EIGHTTAP_SMOOTH; 2];
let ne = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let mut sw = inter_skip_leaf(MODE_NEWMV, [-8, 24]);
sw.inter.as_mut().unwrap().interp_filter = [EIGHTTAP_SHARP; 2];
let se = inter_skip_leaf(MODE_NEWMV, [2, 2]);
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0x5C);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
let f = walker.interp_filters();
assert_eq!(
[f[cell(0, 0) * 2], f[cell(0, 0) * 2 + 1]],
[EIGHTTAP_SMOOTH; 2],
"NW leaf filter pair"
);
assert_eq!(
[f[cell(0, 8) * 2], f[cell(0, 8) * 2 + 1]],
[EIGHTTAP; 2],
"NE GLOBALMV leaf derives EIGHTTAP with no bits"
);
assert_eq!(
[f[cell(8, 0) * 2], f[cell(8, 0) * 2 + 1]],
[EIGHTTAP_SHARP; 2],
"SW leaf filter pair"
);
assert_eq!(
[f[cell(8, 8) * 2], f[cell(8, 8) * 2 + 1]],
[EIGHTTAP; 2],
"SE leaf filter pair"
);
}
#[test]
fn r412_inter_syntax_round_trip_horz_vert_partitions() {
let params = inter_params_420(16, 16, true);
let top = inter_skip_leaf(MODE_NEWMV, [8, 16]);
let bottom = inter_skip_leaf(MODE_NEWMV, [-8, -16]);
let left = inter_skip_leaf(MODE_NEWMV, [4, -24]);
let right = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let sw = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let se = SyntaxBlock::skip_leaf(DC_PRED as u8, Some(DC_PRED as u8));
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Horz([Box::new(top), Box::new(bottom)])),
Box::new(SyntaxNode::Vert([Box::new(left), Box::new(right)])),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0x9D);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
assert_eq!(walker.mi_sizes()[cell(0, 0)], BLOCK_32X16);
assert_eq!(walker.mi_sizes()[cell(4, 0)], BLOCK_32X16);
assert_eq!(
[
walker.mvs()[cell(0, 0) * 4],
walker.mvs()[cell(0, 0) * 4 + 1]
],
[8i16, 16]
);
assert_eq!(
[
walker.mvs()[cell(4, 0) * 4],
walker.mvs()[cell(4, 0) * 4 + 1]
],
[-8i16, -16]
);
assert_eq!(walker.mi_sizes()[cell(0, 8)], BLOCK_16X32);
assert_eq!(walker.mi_sizes()[cell(0, 12)], BLOCK_16X32);
assert_eq!(
[
walker.mvs()[cell(0, 8) * 4],
walker.mvs()[cell(0, 8) * 4 + 1]
],
[4i16, -24]
);
assert_eq!(
[
walker.mvs()[cell(0, 12) * 4],
walker.mvs()[cell(0, 12) * 4 + 1]
],
[0i16, 0]
);
}
#[test]
fn r412_inter_syntax_round_trip_compound_average() {
let mut params = inter_params_420(16, 16, true);
{
let ipp = params.inter.as_mut().expect("inter params");
ipp.interpolation_filter = SWITCHABLE;
ipp.reference_select = true;
}
let comp_leaf = |y_mode: u8, mv: [[i32; 2]; 2], ref_mv_idx: u32| -> SyntaxBlock {
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, 4],
y_mode,
mv,
ref_mv_idx,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: None,
wedge_interintra: 0,
interintra_wedge_index: 0,
motion_mode: MOTION_MODE_SIMPLE,
});
b
};
let mut nw = comp_leaf(MODE_NEW_NEWMV, [[8, 16], [-4, 24]], 0);
nw.inter.as_mut().unwrap().interp_filter = [crate::inter_pred::EIGHTTAP_SMOOTH; 2];
let ne = comp_leaf(MODE_NEAREST_NEARESTMV, [[8, 16], [-4, 24]], 0);
let sw = comp_leaf(MODE_GLOBAL_GLOBALMV, [[0, 0], [0, 0]], 0);
let se = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0xE4);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
assert_eq!(walker.ref_frames()[cell(0, 0) * 2], 1);
assert_eq!(walker.ref_frames()[cell(0, 0) * 2 + 1], 4);
assert_eq!(
[
walker.mvs()[cell(0, 0) * 4],
walker.mvs()[cell(0, 0) * 4 + 1],
walker.mvs()[cell(0, 0) * 4 + 2],
walker.mvs()[cell(0, 0) * 4 + 3]
],
[8i16, 16, -4, 24]
);
let f = walker.interp_filters();
assert_eq!(
[f[cell(0, 0) * 2], f[cell(0, 0) * 2 + 1]],
[crate::inter_pred::EIGHTTAP_SMOOTH; 2]
);
assert_eq!(
[
walker.mvs()[cell(0, 8) * 4],
walker.mvs()[cell(0, 8) * 4 + 1],
walker.mvs()[cell(0, 8) * 4 + 2],
walker.mvs()[cell(0, 8) * 4 + 3]
],
[8i16, 16, -4, 24]
);
assert_eq!(walker.ref_frames()[cell(8, 0) * 2 + 1], 4);
assert_eq!(
[
walker.mvs()[cell(8, 0) * 4],
walker.mvs()[cell(8, 0) * 4 + 2]
],
[0i16, 0]
);
assert_eq!(f[cell(8, 0) * 2], EIGHTTAP);
assert_eq!(walker.ref_frames()[cell(8, 8) * 2], 1);
assert_eq!(walker.ref_frames()[cell(8, 8) * 2 + 1], -1);
}
#[test]
fn r415_inter_syntax_round_trip_masked_compound() {
use crate::inter_pred::{COMPOUND_AVERAGE, COMPOUND_DIFFWTD, COMPOUND_WEDGE};
let mut params = inter_params_420(16, 16, true);
{
let ipp = params.inter.as_mut().expect("inter params");
ipp.interpolation_filter = SWITCHABLE;
ipp.reference_select = true;
ipp.enable_masked_compound = true;
}
let masked_leaf = |ct: u8, wi: u8, ws: u8, mt: u8, mv: [[i32; 2]; 2]| -> SyntaxBlock {
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, 4],
y_mode: MODE_NEW_NEWMV,
mv,
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: ct,
wedge_index: wi,
wedge_sign: ws,
mask_type: mt,
interintra_mode: None,
wedge_interintra: 0,
interintra_wedge_index: 0,
motion_mode: MOTION_MODE_SIMPLE,
});
b
};
let nw = masked_leaf(COMPOUND_WEDGE, 7, 1, 0, [[8, 16], [-4, 24]]);
let ne = masked_leaf(COMPOUND_DIFFWTD, 0, 0, 1, [[8, 8], [0, 16]]);
let sw = masked_leaf(COMPOUND_AVERAGE, 0, 0, 0, [[0, 8], [8, 0]]);
let se = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0xC7);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
assert_eq!(walker.comp_group_idxs()[cell(0, 0)], 1);
assert_eq!(walker.compound_types()[cell(0, 0)], COMPOUND_WEDGE);
assert_eq!(walker.compound_wedge_indices()[cell(0, 0)], 7);
assert_eq!(walker.compound_wedge_signs()[cell(0, 0)], 1);
assert_eq!(walker.comp_group_idxs()[cell(0, 8)], 1);
assert_eq!(walker.compound_types()[cell(0, 8)], COMPOUND_DIFFWTD);
assert_eq!(walker.compound_mask_types()[cell(0, 8)], 1);
assert_eq!(walker.comp_group_idxs()[cell(8, 0)], 0);
assert_eq!(walker.compound_types()[cell(8, 0)], COMPOUND_AVERAGE);
assert_eq!(walker.comp_group_idxs()[cell(8, 8)], 0);
assert_eq!(walker.compound_idxs()[cell(8, 8)], 1);
}
#[test]
fn r415_inter_syntax_rejects_inconsistent_masked_compound() {
use crate::inter_pred::{COMPOUND_DIFFWTD, COMPOUND_WEDGE};
let build = |enable: bool, ref1: i8, ct: u8, wi: u8, ws: u8, mt: u8| {
let mut params = inter_params_420(16, 16, true);
{
let ipp = params.inter.as_mut().expect("inter params");
ipp.interpolation_filter = SWITCHABLE;
ipp.reference_select = true;
ipp.enable_masked_compound = enable;
}
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, ref1],
y_mode: if ref1 > 0 { MODE_NEW_NEWMV } else { MODE_NEWMV },
mv: [[8, 8], [0, 0]],
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: ct,
wedge_index: wi,
wedge_sign: ws,
mask_type: mt,
interintra_mode: None,
wedge_interintra: 0,
interintra_wedge_index: 0,
motion_mode: MOTION_MODE_SIMPLE,
});
let node = SyntaxNode::Leaf(Box::new(b));
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
write_partition_tree_syntax(
&mut writer,
&mut cdfs,
&mut state,
&node,
0,
0,
BLOCK_32X32,
¶ms,
)
};
assert!(build(false, 4, COMPOUND_WEDGE, 3, 0, 0).is_err());
assert!(build(true, 4, COMPOUND_WEDGE, 3, 0, 1).is_err());
assert!(build(true, -1, COMPOUND_DIFFWTD, 0, 0, 1).is_err());
assert!(build(true, 4, COMPOUND_WEDGE, 3, 0, 0).is_ok());
assert_eq!(crate::cdf::wedge_bits(crate::cdf::BLOCK_64X64), 0);
}
#[test]
fn r416_inter_syntax_round_trip_jnt_comp() {
use crate::inter_pred::{COMPOUND_AVERAGE, COMPOUND_DISTANCE, COMPOUND_WEDGE};
for identity_hints in [false, true] {
let mut params = inter_params_420(16, 16, true);
{
let ipp = params.inter.as_mut().expect("inter params");
ipp.interpolation_filter = SWITCHABLE;
ipp.reference_select = true;
ipp.enable_masked_compound = true;
ipp.enable_jnt_comp = true;
if !identity_hints {
ipp.order_hints = FrameInterOrderHints {
order_hint_bits: 7,
current_order_hint: 2,
order_hints_by_ref: [0, 1, 0, 0, 4, 0, 0, 0],
};
}
}
let comp_leaf = |ct: u8, wi: u8, ws: u8, mv: [[i32; 2]; 2]| -> SyntaxBlock {
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, 4],
y_mode: MODE_NEW_NEWMV,
mv,
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: ct,
wedge_index: wi,
wedge_sign: ws,
mask_type: 0,
interintra_mode: None,
wedge_interintra: 0,
interintra_wedge_index: 0,
motion_mode: MOTION_MODE_SIMPLE,
});
b
};
let nw = comp_leaf(COMPOUND_DISTANCE, 0, 0, [[8, 16], [-4, 24]]);
let ne = comp_leaf(COMPOUND_AVERAGE, 0, 0, [[8, 8], [0, 16]]);
let sw = comp_leaf(COMPOUND_WEDGE, 7, 1, [[0, 8], [8, 0]]);
let se = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0xD3);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
assert_eq!(walker.comp_group_idxs()[cell(0, 0)], 0);
assert_eq!(walker.compound_idxs()[cell(0, 0)], 0);
assert_eq!(walker.compound_types()[cell(0, 0)], COMPOUND_DISTANCE);
assert_eq!(walker.comp_group_idxs()[cell(0, 8)], 0);
assert_eq!(walker.compound_idxs()[cell(0, 8)], 1);
assert_eq!(walker.compound_types()[cell(0, 8)], COMPOUND_AVERAGE);
assert_eq!(walker.comp_group_idxs()[cell(8, 0)], 1);
assert_eq!(walker.compound_idxs()[cell(8, 0)], 1);
assert_eq!(walker.compound_types()[cell(8, 0)], COMPOUND_WEDGE);
assert_eq!(walker.comp_group_idxs()[cell(8, 8)], 0);
assert_eq!(walker.compound_idxs()[cell(8, 8)], 1);
}
}
#[test]
fn r416_inter_syntax_rejects_inconsistent_jnt_comp() {
use crate::inter_pred::COMPOUND_DISTANCE;
let build = |enable_jnt: bool, ref1: i8, wi: u8, ws: u8, mt: u8| {
let mut params = inter_params_420(16, 16, true);
{
let ipp = params.inter.as_mut().expect("inter params");
ipp.interpolation_filter = SWITCHABLE;
ipp.reference_select = true;
ipp.enable_masked_compound = true;
ipp.enable_jnt_comp = enable_jnt;
}
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, ref1],
y_mode: if ref1 > 0 { MODE_NEW_NEWMV } else { MODE_NEWMV },
mv: [[8, 8], [0, 0]],
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: COMPOUND_DISTANCE,
wedge_index: wi,
wedge_sign: ws,
mask_type: mt,
interintra_mode: None,
wedge_interintra: 0,
interintra_wedge_index: 0,
motion_mode: MOTION_MODE_SIMPLE,
});
let node = SyntaxNode::Leaf(Box::new(b));
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
write_partition_tree_syntax(
&mut writer,
&mut cdfs,
&mut state,
&node,
0,
0,
BLOCK_32X32,
¶ms,
)
};
assert!(build(false, 4, 0, 0, 0).is_err());
assert!(build(true, 4, 3, 0, 0).is_err());
assert!(build(true, 4, 0, 1, 0).is_err());
assert!(build(true, 4, 0, 0, 1).is_err());
assert!(build(true, -1, 0, 0, 0).is_err());
assert!(build(true, 4, 0, 0, 0).is_ok());
}
#[test]
fn r417_inter_syntax_round_trip_inter_intra() {
use crate::cdf::{II_DC_PRED, II_SMOOTH_PRED};
let mut params = inter_params_420(16, 16, true);
{
let ipp = params.inter.as_mut().expect("inter params");
ipp.interpolation_filter = SWITCHABLE;
ipp.reference_select = true;
ipp.enable_interintra_compound = true;
}
let ii_leaf = |mode: u8, wedge: Option<u8>, mv: [i32; 2]| -> SyntaxBlock {
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, -1],
y_mode: MODE_NEWMV,
mv: [mv, [0, 0]],
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: if wedge.is_some() {
crate::inter_pred::COMPOUND_WEDGE
} else {
crate::inter_pred::COMPOUND_INTRA
},
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: Some(mode),
wedge_interintra: u8::from(wedge.is_some()),
interintra_wedge_index: wedge.unwrap_or(0),
motion_mode: MOTION_MODE_SIMPLE,
});
b
};
let nw = ii_leaf(II_SMOOTH_PRED, None, [8, 16]);
let ne = ii_leaf(II_DC_PRED, Some(5), [8, 8]);
let sw = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let mut se = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
se.inter = Some(SyntaxInterBlock {
ref_frame: [1, 4],
y_mode: MODE_NEW_NEWMV,
mv: [[0, 8], [8, 0]],
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: crate::inter_pred::COMPOUND_AVERAGE,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: None,
wedge_interintra: 0,
interintra_wedge_index: 0,
motion_mode: MOTION_MODE_SIMPLE,
});
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0xB3);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
assert_eq!(walker.ref_frames()[cell(0, 0) * 2], 1);
assert_eq!(
walker.ref_frames()[cell(0, 0) * 2 + 1],
crate::uncompressed_header_tail::INTRA_FRAME as i8,
"RefFrame[1] = INTRA_FRAME on the interintra arm"
);
assert_eq!(walker.interintra_modes()[cell(0, 0)], II_SMOOTH_PRED);
assert_eq!(walker.wedge_interintras()[cell(0, 0)], 0);
assert_eq!(
walker.compound_types()[cell(0, 0)],
crate::inter_pred::COMPOUND_INTRA
);
assert_eq!(walker.ref_frames()[cell(0, 8) * 2 + 1], 0);
assert_eq!(walker.interintra_modes()[cell(0, 8)], II_DC_PRED);
assert_eq!(walker.wedge_interintras()[cell(0, 8)], 1);
assert_eq!(walker.interintra_wedge_indices()[cell(0, 8)], 5);
assert_eq!(
walker.compound_types()[cell(0, 8)],
crate::inter_pred::COMPOUND_WEDGE
);
assert_eq!(walker.ref_frames()[cell(8, 0) * 2 + 1], -1);
assert_eq!(walker.interintra_modes()[cell(8, 0)], 0);
assert_eq!(walker.wedge_interintras()[cell(8, 0)], 0);
assert_eq!(walker.ref_frames()[cell(8, 8) * 2 + 1], 4);
assert_eq!(walker.interintra_modes()[cell(8, 8)], 0);
}
#[test]
fn r417_inter_syntax_rejects_inconsistent_inter_intra() {
use crate::cdf::II_V_PRED;
let build = |enable: bool, ref1: i8, b_size: usize, wedge_ii: u8, wedge_idx: u8, ct: u8| {
let mut params = inter_params_420(16, 16, true);
{
let ipp = params.inter.as_mut().expect("inter params");
ipp.interpolation_filter = SWITCHABLE;
ipp.reference_select = true;
ipp.enable_interintra_compound = enable;
}
let mut b = SyntaxBlock::skip_leaf(DC_PRED as u8, None);
b.inter = Some(SyntaxInterBlock {
ref_frame: [1, ref1],
y_mode: if ref1 > 0 { MODE_NEW_NEWMV } else { MODE_NEWMV },
mv: [[8, 8], [0, 0]],
ref_mv_idx: 0,
interp_filter: [EIGHTTAP; 2],
skip_mode: 0,
compound_type: ct,
wedge_index: 0,
wedge_sign: 0,
mask_type: 0,
interintra_mode: Some(II_V_PRED),
wedge_interintra: wedge_ii,
interintra_wedge_index: wedge_idx,
motion_mode: MOTION_MODE_SIMPLE,
});
let node = SyntaxNode::Leaf(Box::new(b));
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
write_partition_tree_syntax(
&mut writer,
&mut cdfs,
&mut state,
&node,
0,
0,
b_size,
¶ms,
)
};
use crate::inter_pred::{COMPOUND_AVERAGE, COMPOUND_INTRA, COMPOUND_WEDGE};
assert!(build(false, -1, BLOCK_32X32, 0, 0, COMPOUND_INTRA).is_err());
assert!(build(true, 4, BLOCK_32X32, 0, 0, COMPOUND_INTRA).is_err());
assert!(build(true, -1, BLOCK_64X64, 0, 0, COMPOUND_INTRA).is_err());
assert!(build(true, -1, BLOCK_32X32, 0, 3, COMPOUND_INTRA).is_err());
assert!(build(true, -1, BLOCK_32X32, 0, 0, COMPOUND_AVERAGE).is_err());
assert!(build(true, -1, BLOCK_32X32, 1, 3, COMPOUND_INTRA).is_err());
assert!(build(true, -1, BLOCK_32X32, 0, 0, COMPOUND_INTRA).is_ok());
assert!(build(true, -1, BLOCK_32X32, 1, 3, COMPOUND_WEDGE).is_ok());
}
#[test]
fn r412_inter_syntax_rejects_filter_on_globalmv_identity_leaf() {
let mut params = inter_params_420(16, 16, true);
params
.inter
.as_mut()
.expect("inter params")
.interpolation_filter = SWITCHABLE;
let mut leaf = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
leaf.inter.as_mut().unwrap().interp_filter = [crate::inter_pred::EIGHTTAP_SHARP; 2];
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
let node = SyntaxNode::Leaf(Box::new(leaf));
let err = write_partition_tree_syntax(
&mut writer,
&mut cdfs,
&mut state,
&node,
0,
0,
BLOCK_64X64,
¶ms,
);
assert!(matches!(err, Err(Error::PartitionWalkOutOfRange)));
}
#[test]
fn r411_inter_syntax_round_trip_nearestmv_from_neighbour_stack() {
let params = inter_params_420(16, 16, true);
let ipp = params.inter.as_ref().unwrap();
let nw = inter_skip_leaf(MODE_NEWMV, [16, 8]);
let mut scratch = PartitionWalker::new(16, 16, single_tile(16, 16)).unwrap();
scratch.stamp_encoder_block_syntax(&EncoderBlockSyntaxStamp {
mi_row: 0,
mi_col: 0,
sub_size: BLOCK_32X32,
skip: 1,
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: [ipp.interpolation_filter; 2],
motion_mode: crate::cdf::MOTION_MODE_SIMPLE,
palette_size_y: 0,
palette_colors_y: &[],
palette_size_uv: 0,
palette_colors_u: &[],
palette_colors_v: &[],
cdef: None,
tx_size: TX_4X4 as u8,
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 stack = scratch
.find_mv_stack(
0,
8,
BLOCK_32X32,
[1, -1],
false,
ipp.use_ref_frame_mvs,
ipp.gm_type,
ipp.gm_params,
ipp.ref_frame_sign_bias,
ipp.allow_high_precision_mv,
ipp.force_integer_mv,
&ipp.motion_field_mvs,
)
.unwrap();
assert!(stack.num_mv_found >= 1, "NW must seed NE's stack");
let near_mv = stack.ref_stack_mv[0][0];
let ne = inter_skip_leaf(MODE_NEARESTMV, near_mv);
let sw = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let se = inter_skip_leaf(MODE_NEWMV, [0, 0]); let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
let (_enc, walker) = syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0x5C);
let cell = |r: u32, c: u32| (r * 16 + c) as usize;
assert_eq!(
[
i32::from(walker.mvs()[cell(0, 8) * 4]),
i32::from(walker.mvs()[cell(0, 8) * 4 + 1])
],
near_mv,
"decoded NEARESTMV must equal the committed stack head"
);
}
#[test]
fn r411_inter_syntax_round_trip_lossy_largest_tx_residual() {
let params = inter_params_420(16, 16, false);
let mut nw = inter_skip_leaf(MODE_NEWMV, [4, 6]);
nw.skip = 0;
let mut luma = vec![0i32; 32 * 32];
luma[0] = 5;
luma[1] = -2;
let mut chroma = vec![0i32; 16 * 16];
chroma[0] = 3;
nw.residual_quant = vec![luma, chroma.clone(), chroma].into();
let mut ne = inter_skip_leaf(MODE_NEWMV, [-4, 2]);
ne.skip = 0;
let mut luma2 = vec![0i32; 32 * 32];
luma2[0] = 1;
ne.residual_quant = vec![luma2, vec![0i32; 16 * 16], vec![0i32; 16 * 16]].into();
let sw = inter_skip_leaf(MODE_GLOBALMV, [0, 0]);
let se = inter_skip_leaf(MODE_NEWMV, [8, 8]);
let node = SyntaxNode::Split([
Box::new(SyntaxNode::Leaf(Box::new(nw))),
Box::new(SyntaxNode::Leaf(Box::new(ne))),
Box::new(SyntaxNode::Leaf(Box::new(sw))),
Box::new(SyntaxNode::Leaf(Box::new(se))),
]);
syntax_round_trip_inter(&node, 16, 16, BLOCK_64X64, ¶ms, 0x3E);
}
#[test]
fn r411_inter_syntax_scope_rejects() {
let params_intra = SyntaxFrameParams::intra_8bit_baseline();
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
let mut state = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
let block = inter_skip_leaf(MODE_NEWMV, [0, 0]);
let err = super::write_block_syntax(
&mut writer,
&mut cdfs,
&mut state,
&block,
0,
0,
BLOCK_32X32,
¶ms_intra,
)
.unwrap_err();
assert!(matches!(err, Error::PartitionWalkOutOfRange));
let params = inter_params_420(16, 16, true);
let mut b2 = inter_skip_leaf(MODE_NEWMV, [0, 0]);
b2.inter.as_mut().unwrap().ref_frame = [1, 7];
let mut writer2 = SymbolWriter::new(false);
let mut cdfs2 = TileCdfContext::new_from_defaults();
let mut state2 = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
let err2 = super::write_block_syntax(
&mut writer2,
&mut cdfs2,
&mut state2,
&b2,
0,
0,
BLOCK_32X32,
¶ms,
)
.unwrap_err();
assert!(matches!(err2, Error::PartitionWalkOutOfRange));
let b3 = inter_skip_leaf(MODE_GLOBALMV, [8, 0]);
let mut writer3 = SymbolWriter::new(false);
let mut cdfs3 = TileCdfContext::new_from_defaults();
let mut state3 = PartitionSyntaxWriter::new(16, 16, single_tile(16, 16)).unwrap();
let err3 = super::write_block_syntax(
&mut writer3,
&mut cdfs3,
&mut state3,
&b3,
0,
0,
BLOCK_32X32,
¶ms,
)
.unwrap_err();
assert!(matches!(err3, Error::PartitionWalkOutOfRange));
}
}