use crate::cdf::{
dequantize_step1, get_tx_size, intra_tx_type_set, is_tx_type_in_set, tx_size_sqr_index,
QuantizerParams, TileCdfContext, TileGeometry, ANGLE_STEP, BLOCK_4X4, BLOCK_64X64, BLOCK_8X8,
D67_PRED, DCT_DCT, DC_PRED, H_PRED, INTRA_MODES, MAX_TX_DEPTH, MAX_TX_SIZE_RECT, MODE_TO_ANGLE,
MODE_TO_TXFM, NUM_4X4_BLOCKS_WIDE, PAETH_PRED, SMOOTH_H_PRED, SMOOTH_PRED, SMOOTH_V_PRED,
SPLIT_TX_SIZE, TX_4X4, TX_HEIGHT, TX_SIZE_SQR_UP, TX_WIDTH, UV_CFL_PRED, V_PRED,
};
use crate::cdf::{
predict_intra_dc_pred, predict_intra_directional, predict_intra_h_pred,
predict_intra_paeth_pred, predict_intra_smooth_h_pred, predict_intra_smooth_pred,
predict_intra_smooth_v_pred, predict_intra_v_pred,
};
use crate::encoder::forward_quantize::forward_quantize_rounded;
use crate::encoder::forward_transform_2d::forward_transform_2d;
use crate::encoder::forward_wht::forward_wht_4x4;
use crate::encoder::frame_obu::encode_uncompressed_header;
use crate::encoder::ivf::{IvfWriter, FOURCC_AV01};
use crate::encoder::obu::{build_temporal_unit, ObuFrame};
use crate::encoder::partition_tree::{
write_partition_tree_syntax, PartitionSyntaxWriter, SyntaxBlock, SyntaxFrameParams, SyntaxNode,
};
use crate::encoder::rate_twin::{score256, RateModel, RateTwin, TuCtx, TuFork, TwinScope};
use crate::encoder::sequence_obu::write_sequence_header_obu;
use crate::encoder::symbol_writer::SymbolWriter;
use crate::encoder::tile_group_obu::{write_tile_group_obu, TileGroupObu, TilePayload};
use crate::encoder::yuv_frame::{build_intra_only_seq_yuv, ChromaFormat, YuvFrame};
use crate::encoder::yuv_frame::{
build_intra_only_yuv420_8bit_fh_with_q, sb_grid_origins, Yuv420Frame, SB_SIZE4_64,
};
use crate::frame_header::FrameHeader;
use crate::obu::ObuType;
use crate::sequence_header::SequenceHeader;
use crate::transform::inverse_transform_2d;
use crate::uncompressed_header_tail::FilmGrainParams;
use crate::Error;
#[derive(Debug, Clone)]
pub struct EncodedKeyFrameYuv {
pub ivf_bytes: Vec<u8>,
pub temporal_unit_bytes: Vec<u8>,
pub recon_y: Vec<u16>,
pub recon_u: Vec<u16>,
pub recon_v: Vec<u16>,
pub seq: SequenceHeader,
pub fh: FrameHeader,
}
#[derive(Debug, Clone)]
pub struct EncodedKeyFrame {
pub ivf_bytes: Vec<u8>,
pub temporal_unit_bytes: Vec<u8>,
pub recon_y: Vec<u8>,
pub recon_u: Vec<u8>,
pub recon_v: Vec<u8>,
pub seq: SequenceHeader,
pub fh: FrameHeader,
}
pub const KEY_FRAME_MAX_DIM: u32 = 16384;
pub fn encode_key_frame_yuv420(input: &Yuv420Frame) -> Result<EncodedKeyFrame, Error> {
encode_key_frame_yuv420_with_q(input, 0)
}
pub fn encode_key_frame_yuv420_with_q(
input: &Yuv420Frame,
base_q_idx: u8,
) -> Result<EncodedKeyFrame, Error> {
encode_key_frame_yuv420_with_q_rate_model(input, base_q_idx, RateModel::Twin)
}
#[doc(hidden)]
pub fn encode_key_frame_yuv420_with_q_rate_model(
input: &Yuv420Frame,
base_q_idx: u8,
model: RateModel,
) -> Result<EncodedKeyFrame, Error> {
encode_key_frame_yuv420_with_q_carry(input, base_q_idx, model).map(|(k, _)| k)
}
pub(crate) fn encode_key_frame_yuv420_with_q_carry(
input: &Yuv420Frame,
base_q_idx: u8,
model: RateModel,
) -> Result<(EncodedKeyFrame, crate::encoder::inter_frame::RefSlotCarry), Error> {
encode_key_frame_yuv420_with_q_seg_carry(input, base_q_idx, model, &[], None)
}
pub(crate) fn encode_key_frame_yuv420_with_q_seg_carry(
input: &Yuv420Frame,
base_q_idx: u8,
model: RateModel,
alt_q: &[i16],
exact_mask: Option<&[bool]>,
) -> Result<(EncodedKeyFrame, crate::encoder::inter_frame::RefSlotCarry), Error> {
let wide = YuvFrame::from_yuv420_8bit(input);
let (k, carry) = encode_key_frame_yuv_seg_carry(
&wide, base_q_idx, model, alt_q, exact_mask, true, true, true,
)?;
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok((
EncodedKeyFrame {
ivf_bytes: k.ivf_bytes,
temporal_unit_bytes: k.temporal_unit_bytes,
recon_y: narrow(k.recon_y),
recon_u: narrow(k.recon_u),
recon_v: narrow(k.recon_v),
seq: k.seq,
fh: k.fh,
},
carry,
))
}
pub fn encode_key_frame_yuv(input: &YuvFrame) -> Result<EncodedKeyFrameYuv, Error> {
encode_key_frame_yuv_with_q(input, 0)
}
#[doc(hidden)]
pub fn encode_key_frame_yuv420_with_q_dq(
input: &Yuv420Frame,
base_q_idx: u8,
delta_q: bool,
) -> Result<EncodedKeyFrame, Error> {
let wide = YuvFrame::from_yuv420_8bit(input);
let (k, _) = encode_key_frame_yuv_seg_carry_tiles(
&wide,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
(0, 0),
1,
None,
delta_q,
false,
false,
false,
)?;
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok(EncodedKeyFrame {
ivf_bytes: k.ivf_bytes,
temporal_unit_bytes: k.temporal_unit_bytes,
recon_y: narrow(k.recon_y),
recon_u: narrow(k.recon_u),
recon_v: narrow(k.recon_v),
seq: k.seq,
fh: k.fh,
})
}
#[doc(hidden)]
pub fn encode_key_frame_yuv420_with_q_sr(
input: &Yuv420Frame,
base_q_idx: u8,
superres: bool,
) -> Result<EncodedKeyFrame, Error> {
let wide = YuvFrame::from_yuv420_8bit(input);
let (k, _) = encode_key_frame_yuv_seg_carry_tiles(
&wide,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
(0, 0),
1,
None,
true,
true,
superres,
false,
)?;
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok(EncodedKeyFrame {
ivf_bytes: k.ivf_bytes,
temporal_unit_bytes: k.temporal_unit_bytes,
recon_y: narrow(k.recon_y),
recon_u: narrow(k.recon_u),
recon_v: narrow(k.recon_v),
seq: k.seq,
fh: k.fh,
})
}
#[doc(hidden)]
pub fn encode_key_frame_yuv420_with_q_sr_forced(
input: &Yuv420Frame,
base_q_idx: u8,
denom: u32,
) -> Result<EncodedKeyFrame, Error> {
let wide = YuvFrame::from_yuv420_8bit(input);
let extras = KeyExtras {
delta_q: true,
qm: true,
..KeyExtras::default()
};
let (k, _) = encode_key_frame_superres_arm(
&wide,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
&extras,
denom,
)?;
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok(EncodedKeyFrame {
ivf_bytes: k.ivf_bytes,
temporal_unit_bytes: k.temporal_unit_bytes,
recon_y: narrow(k.recon_y),
recon_u: narrow(k.recon_u),
recon_v: narrow(k.recon_v),
seq: k.seq,
fh: k.fh,
})
}
pub fn encode_key_frame_yuv_with_q(
input: &YuvFrame,
base_q_idx: u8,
) -> Result<EncodedKeyFrameYuv, Error> {
encode_key_frame_yuv_seg_carry(
input,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
)
.map(|(k, _)| k)
}
pub fn encode_key_frame_yuv_with_q_tiles(
input: &YuvFrame,
base_q_idx: u8,
tile_cols_log2: u32,
tile_rows_log2: u32,
) -> Result<EncodedKeyFrameYuv, Error> {
encode_key_frame_yuv_seg_carry_tiles(
input,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
(tile_cols_log2, tile_rows_log2),
1,
None,
true,
true,
true,
false,
)
.map(|(k, _)| k)
}
pub fn encode_key_frame_yuv420_with_q_tiles(
input: &Yuv420Frame,
base_q_idx: u8,
tile_cols_log2: u32,
tile_rows_log2: u32,
) -> Result<EncodedKeyFrame, Error> {
let wide = YuvFrame::from_yuv420_8bit(input);
let k = encode_key_frame_yuv_with_q_tiles(&wide, base_q_idx, tile_cols_log2, tile_rows_log2)?;
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok(EncodedKeyFrame {
ivf_bytes: k.ivf_bytes,
temporal_unit_bytes: k.temporal_unit_bytes,
recon_y: narrow(k.recon_y),
recon_u: narrow(k.recon_u),
recon_v: narrow(k.recon_v),
seq: k.seq,
fh: k.fh,
})
}
pub fn encode_key_frame_yuv_with_q_tile_groups(
input: &YuvFrame,
base_q_idx: u8,
tile_cols_log2: u32,
tile_rows_log2: u32,
tile_groups: u32,
) -> Result<EncodedKeyFrameYuv, Error> {
encode_key_frame_yuv_full(
input,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
&KeyExtras {
tiles: (tile_cols_log2, tile_rows_log2),
tile_groups,
delta_q: true,
qm: true,
..KeyExtras::default()
},
)
.map(|(k, _)| k)
}
pub fn encode_key_frame_yuv420_with_q_tile_groups(
input: &Yuv420Frame,
base_q_idx: u8,
tile_cols_log2: u32,
tile_rows_log2: u32,
tile_groups: u32,
) -> Result<EncodedKeyFrame, Error> {
let wide = YuvFrame::from_yuv420_8bit(input);
let k = encode_key_frame_yuv_with_q_tile_groups(
&wide,
base_q_idx,
tile_cols_log2,
tile_rows_log2,
tile_groups,
)?;
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok(EncodedKeyFrame {
ivf_bytes: k.ivf_bytes,
temporal_unit_bytes: k.temporal_unit_bytes,
recon_y: narrow(k.recon_y),
recon_u: narrow(k.recon_u),
recon_v: narrow(k.recon_v),
seq: k.seq,
fh: k.fh,
})
}
pub fn encode_key_frame_yuv_with_q_tile_layout(
input: &YuvFrame,
base_q_idx: u8,
widths_sb: &[u32],
heights_sb: &[u32],
) -> Result<EncodedKeyFrameYuv, Error> {
encode_key_frame_yuv_full(
input,
base_q_idx,
RateModel::Twin,
&[],
None,
true,
true,
true,
&KeyExtras {
explicit_tiles: Some((widths_sb, heights_sb)),
delta_q: true,
qm: true,
..KeyExtras::default()
},
)
.map(|(k, _)| k)
}
pub fn encode_key_frame_yuv420_with_q_tile_layout(
input: &Yuv420Frame,
base_q_idx: u8,
widths_sb: &[u32],
heights_sb: &[u32],
) -> Result<EncodedKeyFrame, Error> {
let wide = YuvFrame::from_yuv420_8bit(input);
let k = encode_key_frame_yuv_with_q_tile_layout(&wide, base_q_idx, widths_sb, heights_sb)?;
let narrow = |p: Vec<u16>| p.into_iter().map(|s| s as u8).collect::<Vec<u8>>();
Ok(EncodedKeyFrame {
ivf_bytes: k.ivf_bytes,
temporal_unit_bytes: k.temporal_unit_bytes,
recon_y: narrow(k.recon_y),
recon_u: narrow(k.recon_u),
recon_v: narrow(k.recon_v),
seq: k.seq,
fh: k.fh,
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_key_frame_yuv_seg_carry(
input: &YuvFrame,
base_q_idx: u8,
model: RateModel,
alt_q: &[i16],
exact_mask: Option<&[bool]>,
cdef: bool,
cdef_units: bool,
lr: bool,
) -> Result<
(
EncodedKeyFrameYuv,
crate::encoder::inter_frame::RefSlotCarry,
),
Error,
> {
encode_key_frame_yuv_seg_carry_tiles(
input,
base_q_idx,
model,
alt_q,
exact_mask,
cdef,
cdef_units,
lr,
(0, 0),
1,
None,
true,
true,
true,
false,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_key_frame_yuv_seg_carry_tiles(
input: &YuvFrame,
base_q_idx: u8,
model: RateModel,
alt_q: &[i16],
exact_mask: Option<&[bool]>,
cdef: bool,
cdef_units: bool,
lr: bool,
tiles: (u32, u32),
tile_groups: u32,
explicit_tiles: Option<(&[u32], &[u32])>,
delta_q: bool,
qm: bool,
superres: bool,
collect_donor_cdfs: bool,
) -> Result<
(
EncodedKeyFrameYuv,
crate::encoder::inter_frame::RefSlotCarry,
),
Error,
> {
encode_key_frame_yuv_full(
input,
base_q_idx,
model,
alt_q,
exact_mask,
cdef,
cdef_units,
lr,
&KeyExtras {
tiles,
tile_groups,
explicit_tiles,
delta_q,
qm,
superres_elect: superres,
collect_donor_cdfs,
..KeyExtras::default()
},
)
}
pub(crate) struct TileSearchOut {
pub payload: Vec<u8>,
pub cdfs: Box<TileCdfContext>,
pub trees: Vec<crate::encoder::partition_tree::SyntaxNode>,
pub sbs: Vec<(u32, u32)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SearchLimits {
pub tx_depth_steps: usize,
pub prune_tx_types: bool,
pub rect_partitions: bool,
pub min_leaf: usize,
pub skip_leaf_early_exit: bool,
pub sgr_step: usize,
pub screen_content_probe: bool,
pub quant_round_64: i64,
pub lf_effort: u8,
pub threads: usize,
pub lr_min_unit_shift: u8,
pub cdef_fast: Option<(u8, u8)>,
pub intra_mode_prescreen: bool,
pub split_early_term_k64: u32,
pub tx_split_prescreen: bool,
pub tu_proxy_pricing: bool,
pub wiener_rounds: u8,
}
impl Default for SearchLimits {
fn default() -> Self {
Self {
tx_depth_steps: MAX_TX_DEPTH,
prune_tx_types: false,
rect_partitions: true,
min_leaf: BLOCK_4X4,
skip_leaf_early_exit: false,
sgr_step: 1,
screen_content_probe: false,
quant_round_64: crate::encoder::forward_quantize::QUANT_ROUND_HALF,
lf_effort: 0,
threads: 1,
lr_min_unit_shift: 0,
cdef_fast: None,
intra_mode_prescreen: false,
split_early_term_k64: 0,
tx_split_prescreen: false,
tu_proxy_pricing: false,
wiener_rounds: 2,
}
}
}
impl SearchLimits {
pub(crate) fn fast() -> Self {
Self {
tx_depth_steps: 0,
prune_tx_types: true,
rect_partitions: false,
min_leaf: crate::cdf::BLOCK_8X8,
skip_leaf_early_exit: true,
sgr_step: 8,
screen_content_probe: true,
quant_round_64: 22,
lf_effort: 1,
threads: 1,
lr_min_unit_shift: 2,
cdef_fast: Some((0, 3)),
intra_mode_prescreen: true,
split_early_term_k64: 4,
tx_split_prescreen: true,
tu_proxy_pricing: true,
wiener_rounds: 1,
}
}
pub(crate) fn balanced() -> Self {
Self {
tx_depth_steps: 0,
prune_tx_types: true,
rect_partitions: true,
min_leaf: crate::cdf::BLOCK_8X8,
skip_leaf_early_exit: true,
sgr_step: 4,
screen_content_probe: true,
quant_round_64: 22,
lf_effort: 2,
threads: 1,
lr_min_unit_shift: 1,
cdef_fast: Some((2, 4)),
intra_mode_prescreen: true,
split_early_term_k64: 0,
tx_split_prescreen: true,
tu_proxy_pricing: true,
wiener_rounds: 2,
}
}
}
type TileLocalState = (
TileGeometry,
Vec<(u32, u32)>,
(u32, u32),
YuvFrame,
ReconState,
PartitionSyntaxWriter,
);
fn tile_local_state(
input: &YuvFrame,
frame: &ReconState,
geo: TileGeometry,
origins: &[(u32, u32)],
) -> TileLocalState {
let (ssx, ssy) = (frame.subsampling_x, frame.subsampling_y);
let mi_rows = geo.mi_row_end - geo.mi_row_start;
let mi_cols = geo.mi_col_end - geo.mi_col_start;
let (row0, col0) = (
(geo.mi_row_start as usize) * 4,
(geo.mi_col_start as usize) * 4,
);
let width = ((mi_cols as usize) * 4).min(frame.width - col0);
let height = ((mi_rows as usize) * 4).min(frame.height - row0);
let (crow0, ccol0) = frame.chroma_origin(geo.mi_row_start, geo.mi_col_start);
let (chroma_w, chroma_h) = if frame.num_planes > 1 {
(
((width + usize::from(ssx)) >> ssx).min(frame.chroma_w - ccol0),
((height + usize::from(ssy)) >> ssy).min(frame.chroma_h - crow0),
)
} else {
(0, 0)
};
let crop = |src: &[u16], stride: usize, x0: usize, y0: usize, w: usize, h: usize| -> Vec<u16> {
let mut out = Vec::with_capacity(w * h);
for y in 0..h {
out.extend_from_slice(&src[(y0 + y) * stride + x0..(y0 + y) * stride + x0 + w]);
}
out
};
let lin = YuvFrame {
width: width as u32,
height: height as u32,
bit_depth: input.bit_depth,
format: input.format,
y: crop(&input.y, frame.width, col0, row0, width, height),
u: crop(&input.u, frame.chroma_w, ccol0, crow0, chroma_w, chroma_h),
v: crop(&input.v, frame.chroma_w, ccol0, crow0, chroma_w, chroma_h),
};
let lgeo = TileGeometry {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
};
let rc = ReconState {
y: vec![0u16; width * height],
u: vec![0u16; chroma_w * chroma_h],
v: vec![0u16; chroma_w * chroma_h],
width,
height,
chroma_w,
chroma_h,
bit_depth: frame.bit_depth,
subsampling_x: ssx,
subsampling_y: ssy,
num_planes: frame.num_planes,
mi_rows,
mi_cols,
lossless: frame.lossless,
allow_screen_content_tools: frame.allow_screen_content_tools,
allow_intrabc: false,
qp: frame.qp,
search: frame.search,
bd: BlockDecodedMirror::new(ssx, ssy, frame.num_planes),
dv_hash: crate::encoder::dv_hash::DvHashIndex::default(),
};
let st = PartitionSyntaxWriter::new(mi_rows, mi_cols, lgeo)
.expect("tile geometry is a valid frame geometry");
let local_origins: Vec<(u32, u32)> = origins
.iter()
.map(|&(r, c)| (r - geo.mi_row_start, c - geo.mi_col_start))
.collect();
(
lgeo,
local_origins,
(geo.mi_row_start, geo.mi_col_start),
lin,
rc,
st,
)
}
pub(crate) fn screen_content_probe(input: &YuvFrame) -> bool {
let (w, h) = (input.width as usize, input.height as usize);
let mut blocks = 0usize;
let mut flat = 0usize;
let mut y = 0;
while y + 16 <= h {
let mut x = 0;
while x + 16 <= w {
let mut seen: Vec<u16> = Vec::with_capacity(9);
let mut few = true;
'scan: for i in 0..16 {
for j in 0..16 {
let v = input.y[(y + i) * w + x + j];
if !seen.contains(&v) {
if seen.len() == 8 {
few = false;
break 'scan;
}
seen.push(v);
}
}
}
blocks += 1;
flat += usize::from(few);
x += 16;
}
y += 16;
}
blocks == 0 || flat * 4 >= blocks
}
#[derive(Default, Clone, Copy)]
pub(crate) struct KeyExtras<'a> {
pub tiles: (u32, u32),
pub tile_groups: u32,
pub tile_spans: bool,
pub explicit_tiles: Option<(&'a [u32], &'a [u32])>,
pub seq_override: Option<&'a SequenceHeader>,
pub intra_only_refresh: Option<u8>,
pub delta_q: bool,
pub delta_plan: Option<&'a [i32]>,
pub qm: bool,
pub qm_level: Option<u8>,
pub collect_donor_cdfs: bool,
pub superres_elect: bool,
pub superres: Option<(u32, u32)>,
pub film_grain: Option<&'a FilmGrainParams>,
pub superres_source: Option<&'a YuvFrame>,
pub superres_gate: bool,
pub still: bool,
pub still_full_header: bool,
pub full_range: bool,
pub coded_size: Option<(u32, u32)>,
pub color_description: Option<(u8, u8, u8)>,
pub search: SearchLimits,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_key_frame_yuv_full(
input: &YuvFrame,
base_q_idx: u8,
model: RateModel,
alt_q: &[i16],
exact_mask: Option<&[bool]>,
cdef: bool,
cdef_units: bool,
lr: bool,
extras: &KeyExtras<'_>,
) -> Result<
(
EncodedKeyFrameYuv,
crate::encoder::inter_frame::RefSlotCarry,
),
Error,
> {
input.validate_any_extent()?;
let mut extras = *extras;
if extras.explicit_tiles.is_none() {
let mi_cols = 2 * input.width.div_ceil(8);
let mi_rows = 2 * input.height.div_ceil(8);
if let Some((min_cols, min_rows_at_min_cols)) =
crate::tile_info::TileInfo::min_uniform_layout(mi_cols, mi_rows, false)
{
let min_tiles = min_cols + min_rows_at_min_cols;
let cols = extras.tiles.0.max(min_cols);
let rows = extras.tiles.1.max(min_tiles.saturating_sub(cols));
extras.tiles = (cols, rows);
}
}
let extras = &extras;
if input.width % 8 == 0 && input.height % 8 == 0 {
return encode_key_frame_yuv_full_aligned(
input, base_q_idx, model, alt_q, exact_mask, cdef, cdef_units, lr, extras,
);
}
if extras.seq_override.is_some()
|| extras.superres.is_some()
|| extras.superres_source.is_some()
|| extras.superres_gate
|| exact_mask.is_some()
|| !alt_q.is_empty()
{
return Err(Error::PartitionWalkOutOfRange);
}
let padded = input.padded_to_mi_grid();
let mut ex = *extras;
ex.coded_size = Some((input.width, input.height));
ex.superres_elect = false;
let (mut k, carry) = encode_key_frame_yuv_full_aligned(
&padded, base_q_idx, model, alt_q, exact_mask, cdef, cdef_units, lr, &ex,
)?;
let crop = |src: &[u16], sw: u32, w: u32, h: u32| -> Vec<u16> {
let (sw, w, h) = (sw as usize, w as usize, h as usize);
let mut out = Vec::with_capacity(w * h);
for y in 0..h {
out.extend_from_slice(&src[y * sw..y * sw + w]);
}
out
};
k.recon_y = crop(&k.recon_y, padded.width, input.width, input.height);
if input.format != ChromaFormat::Monochrome {
let (cw, ch) = (input.chroma_width(), input.chroma_height());
k.recon_u = crop(&k.recon_u, padded.chroma_width(), cw, ch);
k.recon_v = crop(&k.recon_v, padded.chroma_width(), cw, ch);
}
Ok((k, carry))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_key_frame_yuv_full_aligned(
input: &YuvFrame,
base_q_idx: u8,
model: RateModel,
alt_q: &[i16],
exact_mask: Option<&[bool]>,
cdef: bool,
cdef_units: bool,
lr: bool,
extras: &KeyExtras<'_>,
) -> Result<
(
EncodedKeyFrameYuv,
crate::encoder::inter_frame::RefSlotCarry,
),
Error,
> {
input.validate()?;
if extras.superres_elect && extras.superres.is_none() {
let sr_ok = base_q_idx > 0
&& alt_q.is_empty()
&& exact_mask.is_none()
&& model == RateModel::Twin
&& extras.seq_override.is_none()
&& extras.intra_only_refresh.is_none()
&& extras.explicit_tiles.is_none()
&& extras.coded_size.is_none()
&& crate::encoder::superres_elect::superres_arm_allowed(
base_q_idx,
input.width as usize,
input.height as usize,
)
&& crate::encoder::superres_elect::superres_probe(input);
let candidates: Vec<u32> = if sr_ok {
crate::encoder::superres_elect::candidate_denoms(input.width)
.into_iter()
.filter(|&denom| {
crate::encoder::superres_elect::denom_tile_ok(
input.width,
input.height,
denom,
extras.tiles,
)
})
.collect()
} else {
Vec::new()
};
let mut plain_extras = *extras;
plain_extras.superres_elect = false;
if candidates.is_empty() {
return encode_key_frame_yuv_full_aligned(
input,
base_q_idx,
model,
alt_q,
exact_mask,
cdef,
cdef_units,
lr,
&plain_extras,
);
}
let lambda = lambda_for(&QuantizerParams::neutral(base_q_idx, input.bit_depth));
let score = |k: &EncodedKeyFrameYuv| -> u64 {
let mut d = 0u64;
for (a, b) in k
.recon_y
.iter()
.zip(&input.y)
.chain(k.recon_u.iter().zip(&input.u))
.chain(k.recon_v.iter().zip(&input.v))
{
let diff = i64::from(*a) - i64::from(*b);
d += (diff * diff) as u64;
}
score256(d, lambda, (k.temporal_unit_bytes.len() as u64) * 8 * 256)
};
let mut best = encode_key_frame_yuv_full_aligned(
input,
base_q_idx,
model,
alt_q,
exact_mask,
cdef,
cdef_units,
lr,
&plain_extras,
)?;
let mut best_score = score(&best.0);
for denom in candidates {
let cand = encode_key_frame_superres_arm(
input,
base_q_idx,
model,
alt_q,
exact_mask,
cdef,
cdef_units,
lr,
&plain_extras,
denom,
)?;
let s = score(&cand.0);
if s < best_score {
best_score = s;
best = cand;
}
}
return Ok(best);
}
let elections_ok = base_q_idx > 0
&& alt_q.is_empty()
&& exact_mask.is_none()
&& model == RateModel::Twin
&& extras.delta_plan.is_none()
&& extras.qm_level.is_none();
let qm_candidates: Vec<u8> = if elections_ok
&& extras.qm
&& crate::encoder::inter_frame::qm_arm_allowed(
base_q_idx,
input.width as usize,
input.height as usize,
)
&& crate::encoder::inter_frame::qm_probe(input)
{
crate::encoder::inter_frame::qm_ladder_for_q(base_q_idx)
} else {
Vec::new()
};
let delta_plan_opt: Option<Vec<i32>> = if elections_ok && extras.delta_q {
let (mi_rows_probe, mi_cols_probe) = (input.height / 4, input.width / 4);
crate::encoder::inter_frame::delta_q_plan_units(input, mi_rows_probe, mi_cols_probe)
} else {
None
};
if qm_candidates.is_empty() && delta_plan_opt.is_none() {
return encode_key_frame_yuv_core(
input, base_q_idx, model, alt_q, exact_mask, cdef, cdef_units, lr, extras,
);
}
let flat_extras = KeyExtras {
tiles: extras.tiles,
tile_groups: extras.tile_groups,
tile_spans: extras.tile_spans,
explicit_tiles: extras.explicit_tiles,
seq_override: extras.seq_override,
intra_only_refresh: extras.intra_only_refresh,
delta_q: false,
delta_plan: None,
qm: false,
qm_level: None,
collect_donor_cdfs: extras.collect_donor_cdfs,
superres_elect: false,
superres: extras.superres,
film_grain: extras.film_grain,
superres_source: extras.superres_source,
superres_gate: extras.superres_gate,
still: extras.still,
still_full_header: extras.still_full_header,
full_range: extras.full_range,
coded_size: extras.coded_size,
color_description: extras.color_description,
search: extras.search,
};
let lambda = lambda_for(&QuantizerParams::neutral(base_q_idx, input.bit_depth));
type KeyOut = (
EncodedKeyFrameYuv,
crate::encoder::inter_frame::RefSlotCarry,
);
let score_src: &YuvFrame = match (extras.superres, extras.superres_source) {
(Some(_), Some(orig)) => orig,
_ => input,
};
let plain_score = |k: &KeyOut| -> u64 {
let mut d = 0u64;
for (a, b) in
k.0.recon_y
.iter()
.zip(&score_src.y)
.chain(k.0.recon_u.iter().zip(&score_src.u))
.chain(k.0.recon_v.iter().zip(&score_src.v))
{
let diff = i64::from(*a) - i64::from(*b);
d += (diff * diff) as u64;
}
score256(d, lambda, (k.0.temporal_unit_bytes.len() as u64) * 8 * 256)
};
let base = encode_key_frame_yuv_core(
input,
base_q_idx,
model,
alt_q,
exact_mask,
cdef,
cdef_units,
lr,
&flat_extras,
)?;
let mut stage1_extras = flat_extras;
let mut stage1 = base;
let mut stage1_score = plain_score(&stage1);
let mut stage1_won = false;
for &lvl in &qm_candidates {
let qm_extras = KeyExtras {
qm_level: Some(lvl),
..flat_extras
};
let armed = encode_key_frame_yuv_core(
input, base_q_idx, model, alt_q, exact_mask, cdef, cdef_units, lr, &qm_extras,
)?;
let score = plain_score(&armed);
if score < stage1_score {
stage1_score = score;
stage1_extras = qm_extras;
stage1 = armed;
stage1_won = true;
}
if crate::encoder::inter_frame::QM_LADDER_REFINES_ONLY && !stage1_won {
break;
}
}
if let Some(plan) = &delta_plan_opt {
let armed_extras = KeyExtras {
delta_plan: Some(plan),
..stage1_extras
};
let armed = encode_key_frame_yuv_core(
input,
base_q_idx,
model,
alt_q,
exact_mask,
cdef,
cdef_units,
lr,
&armed_extras,
)?;
let score = |k: &KeyOut| -> u64 {
score256(
key_weighted_sse(
score_src,
&k.0.recon_y,
&k.0.recon_u,
&k.0.recon_v,
plan,
(input.height / 4, input.width / 4),
extras
.superres
.map_or(crate::frame_header::SUPERRES_NUM, |(_, d)| d),
),
lambda,
(k.0.temporal_unit_bytes.len() as u64) * 8 * 256,
)
};
return Ok(if score(&armed) < score(&stage1) {
armed
} else {
stage1
});
}
Ok(stage1)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_key_frame_superres_arm(
input: &YuvFrame,
base_q_idx: u8,
model: RateModel,
alt_q: &[i16],
exact_mask: Option<&[bool]>,
cdef: bool,
cdef_units: bool,
lr: bool,
extras: &KeyExtras<'_>,
denom: u32,
) -> Result<
(
EncodedKeyFrameYuv,
crate::encoder::inter_frame::RefSlotCarry,
),
Error,
> {
let wd = crate::encoder::superres_elect::superres_coded_width(input.width, denom);
let down = crate::encoder::superres_elect::downscale_width(input, wd);
let mut ex = *extras;
ex.superres_elect = false;
ex.superres = Some((input.width, denom));
ex.superres_source = Some(input);
encode_key_frame_yuv_full_aligned(
&down, base_q_idx, model, alt_q, exact_mask, cdef, cdef_units, lr, &ex,
)
}
#[allow(clippy::too_many_arguments)]
fn encode_key_frame_yuv_core(
input: &YuvFrame,
base_q_idx: u8,
model: RateModel,
alt_q: &[i16],
exact_mask: Option<&[bool]>,
cdef: bool,
cdef_units: bool,
lr: bool,
extras: &KeyExtras<'_>,
) -> Result<
(
EncodedKeyFrameYuv,
crate::encoder::inter_frame::RefSlotCarry,
),
Error,
> {
let tiles = extras.tiles;
if !alt_q.is_empty() {
if alt_q.len() == 1
|| alt_q.len() > crate::uncompressed_header_tail::MAX_SEGMENTS
|| alt_q[0] != 0
|| base_q_idx == 0
|| model != RateModel::Twin
{
return Err(Error::PartitionWalkOutOfRange);
}
for &d in alt_q {
if !(-255..=255).contains(&d) || i32::from(base_q_idx) + i32::from(d) > 255 {
return Err(Error::PartitionWalkOutOfRange);
}
}
} else if exact_mask.is_some() {
return Err(Error::PartitionWalkOutOfRange);
}
input.validate()?;
let bit_depth = input.bit_depth;
let (ssx, ssy) = input.format.subsampling();
let mono = input.format == ChromaFormat::Monochrome;
let num_planes = input.format.num_planes();
let width = input.width as usize;
let height = input.height as usize;
let chroma_w = input.chroma_width() as usize;
let chroma_h = input.chroma_height() as usize;
let (coded_w, coded_h) = extras.coded_size.unwrap_or((input.width, input.height));
let sc_eligible = bit_depth == 8 && input.format == ChromaFormat::Yuv420;
let superres: Option<(u32, u32)> = extras.superres;
if let Some((up_w, denom)) = superres {
if !(crate::frame_header::SUPERRES_DENOM_MIN..=16).contains(&denom)
|| crate::encoder::superres_elect::superres_coded_width(up_w, denom) != input.width
|| up_w <= input.width
|| up_w > KEY_FRAME_MAX_DIM
{
return Err(Error::PartitionWalkOutOfRange);
}
if let Some(shared) = extras.seq_override {
if !shared.enable_superres || up_w > shared.max_frame_width_minus_1 + 1 {
return Err(Error::PartitionWalkOutOfRange);
}
}
match extras.superres_source {
Some(s)
if s.width == up_w
&& s.height == input.height
&& s.bit_depth == input.bit_depth
&& s.format == input.format => {}
_ => return Err(Error::PartitionWalkOutOfRange),
}
}
let seq = match extras.seq_override {
Some(shared) => {
if input.width > shared.max_frame_width_minus_1 + 1
|| input.height > shared.max_frame_height_minus_1 + 1
{
return Err(Error::PartitionWalkOutOfRange);
}
shared.clone()
}
None => {
let seq_w = superres.map_or(coded_w, |(up_w, _)| up_w);
let mut s = build_intra_only_seq_yuv(seq_w, coded_h, bit_depth, input.format)?;
s.enable_filter_intra = true;
s.enable_superres = superres.is_some() || extras.superres_gate;
s.film_grain_params_present = extras.film_grain.is_some();
if extras.still {
crate::encoder::still::apply_still_picture_shape(&mut s);
} else if extras.still_full_header {
s.still_picture = true;
if let Some(op) = s.operating_points.first_mut() {
op.seq_level_idx = crate::encoder::still::elect_seq_level_idx(
s.max_frame_width_minus_1 + 1,
s.max_frame_height_minus_1 + 1,
);
}
}
s.color_config.color_range = extras.full_range;
if let Some((cp, tc, mc)) = extras.color_description {
s.color_config.color_description_present_flag = true;
s.color_config.color_primaries = cp;
s.color_config.transfer_characteristics = tc;
s.color_config.matrix_coefficients = mc;
}
s
}
};
let mut fh = build_intra_only_yuv420_8bit_fh_with_q(&seq, coded_w, coded_h, base_q_idx);
if seq.reduced_still_picture_header {
fh.disable_frame_end_update_cdf = true;
}
if let Some((up_w, denom)) = superres {
let fs = fh
.frame_size
.as_mut()
.ok_or(Error::PartitionWalkOutOfRange)?;
fs.upscaled_width = up_w;
fs.render_width = up_w;
fs.use_superres = true;
fs.superres_denom = denom;
fs.coded_denom = (denom - crate::frame_header::SUPERRES_DENOM_MIN) as u8;
}
if let Some(fg) = extras.film_grain {
if extras.seq_override.is_some() || !seq.film_grain_params_present || !fg.apply_grain {
return Err(Error::PartitionWalkOutOfRange);
}
fh.film_grain_params = Some(fg.clone());
}
let display_w = superres.map_or(coded_w, |(up_w, _)| up_w);
fh.frame_size_override_flag = extras.seq_override.is_some()
&& (display_w != seq.max_frame_width_minus_1 + 1
|| coded_h != seq.max_frame_height_minus_1 + 1);
if let Some(mask) = extras.intra_only_refresh {
if mask == 0xFF {
return Err(Error::PartitionWalkOutOfRange);
}
fh.frame_type = crate::frame_header::FrameType::IntraOnly;
fh.refresh_frame_flags = mask;
fh.error_resilient_mode = false;
fh.showable_frame = true;
}
fh.allow_screen_content_tools = fh.allow_screen_content_tools
&& sc_eligible
&& (!extras.search.screen_content_probe || screen_content_probe(input));
let (fs_mi_rows, fs_mi_cols) = {
let fs = fh
.frame_size
.as_ref()
.ok_or(Error::PartitionWalkOutOfRange)?;
(fs.mi_rows, fs.mi_cols)
};
let mut ti = match extras.explicit_tiles {
Some((widths_sb, heights_sb)) => crate::tile_info::TileInfo::explicit_layout(
fs_mi_cols,
fs_mi_rows,
seq.use_128x128_superblock,
widths_sb,
heights_sb,
),
None => crate::tile_info::TileInfo::uniform_layout(
fs_mi_cols,
fs_mi_rows,
seq.use_128x128_superblock,
tiles.0,
tiles.1,
),
}
.ok_or(Error::PartitionWalkOutOfRange)?;
let multi_tile = !ti.is_single_tile();
fh.tile_info = Some(ti.clone());
fh.allow_intrabc = superres.is_none()
&& fh.allow_screen_content_tools
&& !multi_tile
&& intrabc_beneficial(input);
if fh.allow_intrabc {
fh.cdef_params = Some(crate::uncompressed_header_tail::CdefParams::short_circuit());
fh.lr_params = Some(crate::uncompressed_header_tail::LrParams::short_circuit());
}
if base_q_idx > 0 {
fh.tx_mode = Some(crate::uncompressed_header_tail::TxMode::TxModeSelect);
}
let fs = fh
.frame_size
.as_ref()
.ok_or(Error::PartitionWalkOutOfRange)?;
let (mi_rows, mi_cols) = (fs.mi_rows, fs.mi_cols);
let lossless = base_q_idx == 0;
let mut qp = QuantizerParams::neutral(base_q_idx, bit_depth);
if !alt_q.is_empty() {
qp.segmentation_enabled = true;
for (seg, &d) in alt_q.iter().enumerate() {
qp.seg_alt_q_active[seg] = true;
qp.seg_alt_q_data[seg] = d;
}
fh.segmentation_params = Some(crate::encoder::inter_frame::segmentation_params_for(alt_q));
}
if let Some(lvl) = extras.qm_level {
if base_q_idx == 0
|| !alt_q.is_empty()
|| exact_mask.is_some()
|| model != RateModel::Twin
|| lvl >= 15
{
return Err(Error::PartitionWalkOutOfRange);
}
qp.using_qmatrix = true;
for plane in 0..3 {
for seg in 0..crate::uncompressed_header_tail::MAX_SEGMENTS {
qp.seg_qm_level[plane][seg] = lvl;
}
}
if let Some(q) = fh.quantization_params.as_mut() {
q.using_qmatrix = true;
q.qm_y = lvl;
q.qm_u = lvl;
q.qm_v = lvl;
}
}
let delta_plan = extras.delta_plan;
if let Some(plan) = delta_plan {
if base_q_idx == 0
|| !alt_q.is_empty()
|| exact_mask.is_some()
|| model != RateModel::Twin
|| plan.len() != sb_grid_origins(mi_rows, mi_cols).len()
{
return Err(Error::PartitionWalkOutOfRange);
}
qp.delta_q_present = true;
qp.current_q_index = base_q_idx;
fh.delta_q_params = Some(crate::uncompressed_header_tail::DeltaQParams {
delta_q_present: true,
delta_q_res: crate::encoder::inter_frame::DELTA_Q_RES,
});
}
let seg_ll = crate::encoder::inter_frame::seg_lossless_array(base_q_idx, alt_q);
let ll_seg = (0..alt_q.len())
.find(|&sid| seg_ll[sid])
.map(|sid| sid as u8);
if exact_mask.is_some() && ll_seg.is_none() {
return Err(Error::PartitionWalkOutOfRange);
}
if let Some(mask) = exact_mask {
if mask.len() != (mi_rows as usize) * (mi_cols as usize) {
return Err(Error::PartitionWalkOutOfRange);
}
}
let params = SyntaxFrameParams {
subsampling_x: ssx,
subsampling_y: ssy,
num_planes,
seg_id_pre_skip: false,
segmentation_enabled: !alt_q.is_empty(),
seg_ref_frame: [None; crate::uncompressed_header_tail::MAX_SEGMENTS],
seg_skip: [false; crate::uncompressed_header_tail::MAX_SEGMENTS],
seg_globalmv: [false; crate::uncompressed_header_tail::MAX_SEGMENTS],
last_active_seg_id: alt_q.len().saturating_sub(1) as u8,
lossless_array: seg_ll,
coded_lossless: lossless,
enable_cdef: seq.enable_cdef,
allow_intrabc: fh.allow_intrabc,
cdef_bits: 0,
read_deltas: delta_plan.is_some(),
use_128x128_superblock: seq.use_128x128_superblock,
delta_q_res: if delta_plan.is_some() {
crate::encoder::inter_frame::DELTA_Q_RES
} else {
0
},
delta_lf_present: false,
delta_lf_multi: false,
mono_chrome: mono,
delta_lf_res: 0,
allow_screen_content_tools: fh.allow_screen_content_tools,
enable_filter_intra: seq.enable_filter_intra,
bit_depth,
tx_mode_select: !lossless,
quant: qp,
reduced_tx_set: fh.reduced_tx_set.unwrap_or(false),
inter: None,
};
let mut recon = ReconState {
y: vec![0u16; width * height],
u: vec![0u16; chroma_w * chroma_h],
v: vec![0u16; chroma_w * chroma_h],
width,
height,
chroma_w,
chroma_h,
bit_depth,
subsampling_x: ssx,
subsampling_y: ssy,
num_planes,
mi_rows,
mi_cols,
lossless,
allow_screen_content_tools: fh.allow_screen_content_tools,
allow_intrabc: fh.allow_intrabc,
qp,
search: extras.search,
bd: BlockDecodedMirror::new(ssx, ssy, num_planes),
dv_hash: if fh.allow_intrabc {
crate::encoder::dv_hash::DvHashIndex::build(&input.y, width, height)
} else {
crate::encoder::dv_hash::DvHashIndex::default()
},
};
let num_tiles = ti.tile_cols * ti.tile_rows;
let tile_plan: Vec<(TileGeometry, Vec<(u32, u32)>)> = (0..num_tiles)
.map(|t| {
let g = ti.tile_geometry(t).expect("tile_num < NumTiles");
let mut origins: Vec<(u32, u32)> = Vec::new();
let mut r = g.mi_row_start;
while r < g.mi_row_end {
let mut c = g.mi_col_start;
while c < g.mi_col_end {
origins.push((r, c));
c += SB_SIZE4_64;
}
r += SB_SIZE4_64;
}
(g, origins)
})
.collect();
debug_assert_eq!(
tile_plan.iter().map(|(_, o)| o.len()).sum::<usize>(),
sb_grid_origins(mi_rows, mi_cols).len(),
"tile plan must cover every superblock exactly once"
);
let mut state = PartitionSyntaxWriter::new(
mi_rows,
mi_cols,
TileGeometry {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
},
)
.ok_or(Error::PartitionWalkOutOfRange)?;
let mut trees: Vec<crate::encoder::partition_tree::SyntaxNode> = Vec::new();
let mut tree_sb: Vec<(u32, u32)> = Vec::new();
let mut tile_payloads: Vec<Vec<u8>> = Vec::with_capacity(num_tiles as usize);
let mut all_cdfs: Vec<Box<TileCdfContext>> = Vec::with_capacity(num_tiles as usize);
let disable_cdf_update_search = fh.disable_cdf_update;
let frame_mi_cols = mi_cols;
let run_tile = |geo: TileGeometry,
origins: &[(u32, u32)],
abs: (u32, u32),
input: &YuvFrame,
rc: &mut ReconState,
st: &mut PartitionSyntaxWriter|
-> Result<TileSearchOut, Error> {
let mut out_trees: Vec<crate::encoder::partition_tree::SyntaxNode> = Vec::new();
let mut out_sbs: Vec<(u32, u32)> = Vec::new();
if !st.begin_tile(geo) {
return Err(Error::PartitionWalkOutOfRange);
}
rc.bd.set_tile(geo);
let (mi_rows, mi_cols) = (rc.mi_rows, rc.mi_cols);
let mut writer = SymbolWriter::new(disable_cdf_update_search);
let mut tile_cdfs = TileCdfContext::new_from_defaults();
tile_cdfs.init_coeff_cdfs(base_q_idx);
rc.qp.current_q_index = base_q_idx;
let sb_cols_frame = (mi_cols + abs.1).max(frame_mi_cols).div_ceil(16);
let mut twin = RateTwin::snapshot(&tile_cdfs, st, &writer);
for &(sb_r, sb_c) in origins {
rc.bd.clear_for_sb(sb_r, sb_c, mi_rows, mi_cols);
let prev_q = rc.qp.current_q_index;
let dq_units = if let Some(p) = delta_plan {
let k = (((sb_r + abs.0) / 16) * sb_cols_frame + ((sb_c + abs.1) / 16)) as usize;
let step = 1i32 << params.delta_q_res;
let want = (i32::from(base_q_idx) + p[k] * step).clamp(1, 255);
let units = (want - i32::from(prev_q)) / step;
rc.qp.current_q_index = (i32::from(prev_q) + units * step) as u8;
units
} else {
0
};
st.arm_read_deltas();
twin.resync_coder(&tile_cdfs, &writer);
twin.arm_read_deltas();
let seg_demand = exact_mask.map(|mask| KeySegDemand {
mask,
ll_seg: ll_seg.expect("mask requires a lossless segment (validated above)"),
});
let (tree, _cost) = build_search_tree(
sb_r,
sb_c,
BLOCK_64X64,
input,
rc,
&mut twin,
¶ms,
model,
seg_demand.as_ref(),
dq_units,
)?;
write_partition_tree_syntax(
&mut writer,
&mut tile_cdfs,
st,
&tree,
sb_r,
sb_c,
BLOCK_64X64,
¶ms,
)?;
debug_assert!(
twin.matches_state(&tile_cdfs, st, &writer),
"rate twin desynced from the writer after superblock ({sb_r},{sb_c}): {:?}",
twin.differing_state_fields(st)
);
if delta_plan.is_some() {
if let SyntaxNode::Leaf(b) = &tree {
if b.skip == 1 {
rc.qp.current_q_index = prev_q;
}
}
}
out_trees.push(tree);
out_sbs.push((sb_r + abs.0, sb_c + abs.1));
}
Ok(TileSearchOut {
payload: writer.finish(),
cdfs: Box::new(tile_cdfs),
trees: out_trees,
sbs: out_sbs,
})
};
let threads = extras.search.threads.max(1).min(num_tiles as usize);
if threads <= 1 {
for (geo, origins) in tile_plan.iter() {
let out = run_tile(*geo, origins, (0, 0), input, &mut recon, &mut state)?;
tile_payloads.push(out.payload);
all_cdfs.push(out.cdfs);
trees.extend(out.trees);
tree_sb.extend(out.sbs);
}
} else {
let tile_local = !recon.allow_intrabc && exact_mask.is_none();
let next = core::sync::atomic::AtomicUsize::new(0);
type TileRun = (usize, TileSearchOut, ReconState, PartitionSyntaxWriter);
let results: Vec<Result<TileRun, Error>> = std::thread::scope(|scope| {
let mut handles = Vec::with_capacity(threads);
for _ in 0..threads {
let recon_ref = &recon;
let state_ref = &state;
let plan_ref = &tile_plan;
let next_ref = &next;
let run = &run_tile;
handles.push(scope.spawn(move || {
let mut outs = Vec::new();
loop {
let t = next_ref.fetch_add(1, core::sync::atomic::Ordering::SeqCst);
if t >= plan_ref.len() {
break;
}
let (geo, origins) = &plan_ref[t];
if tile_local {
let (lgeo, local_origins, abs, lin, mut rc, mut st) =
tile_local_state(input, recon_ref, *geo, origins);
outs.push(
run(lgeo, &local_origins, abs, &lin, &mut rc, &mut st)
.map(|o| (t, o, rc, st)),
);
} else {
let mut rc = recon_ref.clone();
let mut st = state_ref.clone();
outs.push(
run(*geo, origins, (0, 0), input, &mut rc, &mut st)
.map(|o| (t, o, rc, st)),
);
}
}
outs
}));
}
let mut all = Vec::with_capacity(tile_plan.len());
for h in handles {
all.extend(h.join().expect("tile search thread panicked"));
}
all
});
let mut per_tile: Vec<Option<(TileSearchOut, ReconState, PartitionSyntaxWriter)>> =
(0..tile_plan.len()).map(|_| None).collect();
for r in results {
let (t, o, rc, st) = r?;
per_tile[t] = Some((o, rc, st));
}
for (t, slot) in per_tile.into_iter().enumerate() {
let (o, rc, st) = slot.expect("every tile searched");
let geo = tile_plan[t].0;
let (r0, r1, c0, c1) = (
geo.mi_row_start as usize * 4,
(geo.mi_row_end as usize * 4).min(height),
geo.mi_col_start as usize * 4,
(geo.mi_col_end as usize * 4).min(width),
);
let (sr0, sc0, sw) = if tile_local {
(0usize, 0usize, rc.width)
} else {
(r0, c0, width)
};
for y in r0..r1 {
let sy = y - r0 + sr0;
recon.y[y * width + c0..y * width + c1]
.copy_from_slice(&rc.y[sy * sw + sc0..sy * sw + sc0 + (c1 - c0)]);
}
if num_planes > 1 {
let (cr0, cr1) = (r0 >> ssy, ((r1 + usize::from(ssy)) >> ssy).min(chroma_h));
let (cc0, cc1) = (c0 >> ssx, ((c1 + usize::from(ssx)) >> ssx).min(chroma_w));
let (scr0, scc0, scw) = if tile_local {
(0usize, 0usize, rc.chroma_w)
} else {
(cr0, cc0, chroma_w)
};
for y in cr0..cr1 {
let sy = y - cr0 + scr0;
recon.u[y * chroma_w + cc0..y * chroma_w + cc1]
.copy_from_slice(&rc.u[sy * scw + scc0..sy * scw + scc0 + (cc1 - cc0)]);
recon.v[y * chroma_w + cc0..y * chroma_w + cc1]
.copy_from_slice(&rc.v[sy * scw + scc0..sy * scw + scc0 + (cc1 - cc0)]);
}
}
if tile_local {
state.import_tile_from(&st, geo, ssx, ssy);
} else {
let snap = st.snapshot_price_scope(
geo.mi_row_start,
geo.mi_col_start,
geo.mi_col_end - geo.mi_col_start,
geo.mi_row_end - geo.mi_row_start,
¶ms,
);
state.restore_price_scope(&snap);
}
tile_payloads.push(o.payload);
all_cdfs.push(o.cdfs);
trees.extend(o.trees);
tree_sb.extend(o.sbs);
}
}
let (layout_cols_log2, layout_rows_log2, ctx_update_id) = (
ti.tile_cols_log2,
ti.tile_rows_log2,
ti.context_update_tile_id,
);
let assemble = move |payloads: &[Vec<u8>]| -> Result<Vec<u8>, Error> {
let tsb = min_tile_size_bytes(payloads);
write_tile_group_obu(&TileGroupObu::whole_frame(
num_tiles,
layout_cols_log2,
layout_rows_log2,
tsb,
payloads.iter().cloned().map(TilePayload::new).collect(),
))
};
let mut tile_group_body = assemble(&tile_payloads)?;
let disable_cdf_update = fh.disable_cdf_update;
type EmitOut = (
Vec<Vec<u8>>,
Vec<Box<TileCdfContext>>,
PartitionSyntaxWriter,
);
let emit_tiles = |trees: &[crate::encoder::partition_tree::SyntaxNode],
emit_params: &SyntaxFrameParams,
lr_plan: Option<&crate::encoder::lr_elect::LrPlan>|
-> Result<EmitOut, Error> {
let mut re_state = PartitionSyntaxWriter::new(
mi_rows,
mi_cols,
TileGeometry {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
},
)
.ok_or(Error::PartitionWalkOutOfRange)?;
let mut payloads: Vec<Vec<u8>> = Vec::with_capacity(num_tiles as usize);
let mut all: Vec<Box<TileCdfContext>> = Vec::with_capacity(num_tiles as usize);
let mut tree_idx = 0usize;
for (geo, origins) in tile_plan.iter() {
if !re_state.begin_tile(*geo) {
return Err(Error::PartitionWalkOutOfRange);
}
let mut re_writer = SymbolWriter::new(disable_cdf_update);
let mut re_cdfs = TileCdfContext::new_from_defaults();
re_cdfs.init_coeff_cdfs(base_q_idx);
let mut lr_write_state = crate::encoder::loop_restoration_write::LrWriteState::new();
for &(sb_r, sb_c) in origins {
re_state.arm_read_deltas();
if let Some(plan) = lr_plan {
crate::encoder::loop_restoration_write::write_lr(
&mut re_writer,
&mut re_cdfs,
&mut lr_write_state,
sb_r,
sb_c,
BLOCK_64X64,
&plan.write_params,
&plan.units,
)?;
}
write_partition_tree_syntax(
&mut re_writer,
&mut re_cdfs,
&mut re_state,
&trees[tree_idx],
sb_r,
sb_c,
BLOCK_64X64,
emit_params,
)?;
tree_idx += 1;
}
payloads.push(re_writer.finish());
all.push(Box::new(re_cdfs));
}
Ok((payloads, all, re_state))
};
if extras.search.lf_effort > 0
&& base_q_idx > 0
&& !fh.allow_intrabc
&& exact_mask.is_none()
&& alt_q.is_empty()
{
if let Some(e) =
crate::encoder::lf_elect::elect_loop_filter(&crate::encoder::lf_elect::LfElectInput {
mirror: state.mirror(),
input,
recon_y: &recon.y,
recon_u: &recon.u,
recon_v: &recon.v,
width,
height,
chroma_w,
chroma_h,
frame_width: coded_w,
frame_height: coded_h,
bit_depth,
subsampling_x: ssx,
subsampling_y: ssy,
num_planes,
effort: extras.search.lf_effort,
})
{
recon.y = e.y;
recon.u = e.u;
recon.v = e.v;
fh.loop_filter_params = Some(e.params);
}
}
let lr_armed =
lr && base_q_idx > 0 && seq.enable_restoration && !fh.allow_intrabc && exact_mask.is_none();
let pre_cdef: Option<(Vec<u16>, Vec<u16>, Vec<u16>)> = (lr_armed && superres.is_some())
.then(|| (recon.y.clone(), recon.u.clone(), recon.v.clone()));
let pre_cdef_rows: Option<[crate::encoder::lr_elect::StripeRows; 3]> =
(lr_armed && superres.is_none()).then(|| {
use crate::encoder::lr_elect::StripeRows;
[
StripeRows::capture(&recon.y, width, height, 0),
StripeRows::capture(&recon.u, chroma_w, chroma_h, ssy),
StripeRows::capture(&recon.v, chroma_w, chroma_h, ssy),
]
});
let mut committed_cdef_bits: u32 = 0;
if cdef && base_q_idx > 0 && seq.enable_cdef && !fh.allow_intrabc && exact_mask.is_none() {
if let Some(election) =
crate::encoder::cdef_elect::elect_cdef(&crate::encoder::cdef_elect::CdefElectInput {
mirror: state.mirror(),
input,
recon_y: &recon.y,
recon_u: &recon.u,
recon_v: &recon.v,
width,
height,
chroma_w,
chroma_h,
bit_depth,
subsampling_x: ssx,
subsampling_y: ssy,
num_planes,
lambda: lambda_for(&recon.qp),
max_bits: if cdef_units { 3 } else { 0 },
fast: extras.search.cdef_fast,
threads: extras.search.threads.max(1),
})
{
let lambda = lambda_for(&recon.qp);
let header_len = |cdef_params: &crate::uncompressed_header_tail::CdefParams| {
let mut fh_c = fh.clone();
fh_c.cdef_params = Some(*cdef_params);
let mut bw = crate::encoder::bitwriter::BitWriter::new();
encode_uncompressed_header(&mut bw, &fh_c, &seq);
bw.byte_align();
bw.finish().len()
};
let adopted: Option<crate::encoder::cdef_elect::CdefPlan>;
if election.best.params.cdef_bits > 0 {
let plan = &election.best;
let mut cdef_params_w = params.clone();
cdef_params_w.cdef_bits = u32::from(plan.params.cdef_bits);
let sb_cols_frame = mi_cols.div_ceil(16);
for (tree, &(sb_r, sb_c)) in trees.iter_mut().zip(tree_sb.iter()) {
let k = ((sb_r / 16) * sb_cols_frame + (sb_c / 16)) as usize;
tree.stamp_cdef_idx(plan.unit_idx[k].max(0));
}
let (re_payloads, re_cdfs, re_state) = emit_tiles(&trees, &cdef_params_w, None)?;
let re_body = assemble(&re_payloads)?;
let unit_score = plan.d * 256
+ lambda * 8 * 256 * ((header_len(&plan.params) + re_body.len()) as u64);
let (fb_d, fb_hdr) = match &election.frame_level {
Some(p) => (p.d, header_len(&p.params)),
None => (
election.base_d,
header_len(
fh.cdef_params
.as_ref()
.expect("lossy KEY header carries cdef params"),
),
),
};
let fb_score =
fb_d * 256 + lambda * 8 * 256 * ((fb_hdr + tile_group_body.len()) as u64);
if unit_score < fb_score {
tile_payloads = re_payloads;
tile_group_body = re_body;
all_cdfs = re_cdfs;
state = re_state;
committed_cdef_bits = u32::from(plan.params.cdef_bits);
debug_assert!(
{
let grid = state.mirror().cdef_idx();
let sb_cols_dbg = mi_cols.div_ceil(16);
plan.unit_idx.iter().enumerate().all(|(k, &idx)| {
let (ur, uc) = (k as u32 / sb_cols_dbg, k as u32 % sb_cols_dbg);
grid[((ur * 16) * mi_cols + uc * 16) as usize] == idx
})
},
"re-emitted cdef_idx grid diverged from the elected plan"
);
adopted = Some(election.best);
} else {
for tree in trees.iter_mut() {
tree.stamp_cdef_idx(0);
}
adopted = election.frame_level;
}
} else {
adopted = Some(election.best);
}
if let Some(plan) = adopted {
crate::encoder::cdef_elect::apply_cdef_plan(
state.mirror(),
&plan,
input,
&mut recon.y,
&mut recon.u,
&mut recon.v,
width,
height,
chroma_w,
chroma_h,
bit_depth,
ssx,
ssy,
num_planes,
extras.search.threads.max(1),
);
fh.cdef_params = Some(plan.params);
}
}
}
let mut pre_cdef = pre_cdef;
let (lr_w, lr_chroma_w, lr_src): (usize, usize, &YuvFrame) = if let Some((up_w, _)) = superres {
let np = usize::from(num_planes);
let upscale = |y: &[u16], u: &[u16], v: &[u16]| {
crate::encoder::superres_elect::upscale_recon(
[y, u, v],
width as u32,
height as u32,
up_w,
mi_cols,
bit_depth,
ssx,
ssy,
np,
)
};
let (uy, uu, uv) = upscale(&recon.y, &recon.u, &recon.v)?;
recon.y = uy;
recon.u = uu;
recon.v = uv;
if let Some(pc) = pre_cdef.as_mut() {
let (py, pu, pv) = upscale(&pc.0, &pc.1, &pc.2)?;
*pc = (py, pu, pv);
}
let src = extras
.superres_source
.ok_or(Error::PartitionWalkOutOfRange)?;
(
up_w as usize,
((up_w + u32::from(ssx)) >> ssx) as usize,
src,
)
} else {
(width, chroma_w, input)
};
let pre_cdef_curr: Option<[crate::encoder::lr_elect::LrCurr<'_>; 3]> =
match (pre_cdef.as_ref(), pre_cdef_rows.as_ref()) {
(Some((y, u, v)), _) => Some([
crate::encoder::lr_elect::LrCurr::Full(y),
crate::encoder::lr_elect::LrCurr::Full(u),
crate::encoder::lr_elect::LrCurr::Full(v),
]),
(None, Some(rows)) => Some([
crate::encoder::lr_elect::LrCurr::Rows(&rows[0]),
crate::encoder::lr_elect::LrCurr::Rows(&rows[1]),
crate::encoder::lr_elect::LrCurr::Rows(&rows[2]),
]),
(None, None) => None,
};
if let Some([pcy, pcu, pcv]) = pre_cdef_curr {
let price_cdfs = {
let mut c = TileCdfContext::new_from_defaults();
c.init_coeff_cdfs(base_q_idx);
c
};
let lambda = lambda_for(&recon.qp);
let lr_header_len = |lrp: &crate::uncompressed_header_tail::LrParams| {
let mut fh_c = fh.clone();
fh_c.lr_params = Some(*lrp);
let mut bw = crate::encoder::bitwriter::BitWriter::new();
encode_uncompressed_header(&mut bw, &fh_c, &seq);
bw.byte_align();
bw.finish().len()
};
let off_hdr = lr_header_len(
fh.lr_params
.as_ref()
.expect("lossy KEY header carries lr_params"),
);
let off_score = {
let mut d = 0u64;
let (fw, fhh) = if superres.is_some() {
(lr_w, height)
} else {
(coded_w as usize, coded_h as usize)
};
let (cfw, cfh) = (
(fw + usize::from(ssx)) >> ssx,
(fhh + usize::from(ssy)) >> ssy,
);
for y in 0..fhh {
for x in 0..fw {
let e = i64::from(recon.y[y * lr_w + x]) - i64::from(lr_src.y[y * lr_w + x]);
d += (e * e) as u64;
}
}
if num_planes > 1 {
for y in 0..cfh {
for x in 0..cfw {
let eu = i64::from(recon.u[y * lr_chroma_w + x])
- i64::from(lr_src.u[y * lr_chroma_w + x]);
let ev = i64::from(recon.v[y * lr_chroma_w + x])
- i64::from(lr_src.v[y * lr_chroma_w + x]);
d += (eu * eu + ev * ev) as u64;
}
}
}
d * 256 + lambda * 8 * 256 * ((off_hdr + tile_group_body.len()) as u64)
};
type LrCand = (
crate::encoder::lr_elect::LrPlan,
Vec<Vec<u8>>,
Vec<u8>,
Vec<Box<TileCdfContext>>,
PartitionSyntaxWriter,
u64,
);
let mut best_lr: Option<LrCand> = None;
let min_shift: u8 = if seq.use_128x128_superblock { 1 } else { 0 };
for unit_shift in min_shift.max(extras.search.lr_min_unit_shift.min(2))..=2u8 {
let Some(plan) =
crate::encoder::lr_elect::elect_lr(&crate::encoder::lr_elect::LrElectInput {
input: lr_src,
curr_y: pcy,
curr_u: pcu,
curr_v: pcv,
cdef_y: &recon.y,
cdef_u: &recon.u,
cdef_v: &recon.v,
width: lr_w,
height,
chroma_w: lr_chroma_w,
chroma_h,
bit_depth,
subsampling_x: ssx,
subsampling_y: ssy,
num_planes,
mi_rows,
mi_cols,
frame_width: if superres.is_some() {
lr_w
} else {
coded_w as usize
},
frame_height: coded_h as usize,
sgr_step: extras.search.sgr_step,
wiener_rounds: extras.search.wiener_rounds,
unit_shift,
lambda,
price_cdfs: &price_cdfs,
disable_cdf_update: fh.disable_cdf_update,
use_superres: superres.is_some(),
superres_denom: superres.map_or(crate::frame_header::SUPERRES_NUM, |(_, d)| d),
threads: extras.search.threads.max(1),
})
else {
continue;
};
let mut lr_params_w = params.clone();
lr_params_w.cdef_bits = committed_cdef_bits;
let (re_payloads, re_cdfs, re_state) = emit_tiles(&trees, &lr_params_w, Some(&plan))?;
let re_body = assemble(&re_payloads)?;
let on_score = plan.d * 256
+ lambda * 8 * 256 * ((lr_header_len(&plan.header) + re_body.len()) as u64);
if std::env::var_os("OXIDEAV_AV1_LR_DEBUG").is_some() {
eprintln!(
"lr-elect q={base_q_idx} units {}: d_pre {} d {} | bytes off {} on {} | score off {off_score} on {on_score} | frt {:?}",
64u32 << unit_shift,
plan.d_pre,
plan.d,
off_hdr + tile_group_body.len(),
lr_header_len(&plan.header) + re_body.len(),
plan.header.frame_restoration_type
);
}
if on_score < off_score && best_lr.as_ref().map_or(true, |b| on_score < b.5) {
best_lr = Some((plan, re_payloads, re_body, re_cdfs, re_state, on_score));
}
}
if let Some((plan, re_payloads, re_body, re_cdfs, re_state, _)) = best_lr {
tile_payloads = re_payloads;
tile_group_body = re_body;
all_cdfs = re_cdfs;
state = re_state;
let applied_d = crate::encoder::lr_elect::apply_lr_plan(
&plan,
lr_src,
pcy,
pcu,
pcv,
&mut recon.y,
&mut recon.u,
&mut recon.v,
lr_w,
height,
lr_chroma_w,
chroma_h,
bit_depth,
ssx,
ssy,
num_planes,
mi_rows,
mi_cols,
if superres.is_some() {
lr_w
} else {
coded_w as usize
},
coded_h as usize,
);
debug_assert_eq!(
applied_d, plan.d,
"applied §7.17 SSD diverged from the elected plan"
);
fh.lr_params = Some(plan.header);
}
}
ti.tile_size_bytes = min_tile_size_bytes(&tile_payloads) as u8;
fh.tile_info = Some(ti.clone());
let ctx_update_span: Option<(u32, u32)> = if ti.tile_cols_log2 + ti.tile_rows_log2 > 0 {
let write_with = |ctx: u32| -> Vec<u8> {
let mut fh_c = fh.clone();
if let Some(t) = fh_c.tile_info.as_mut() {
t.context_update_tile_id = ctx;
}
let mut bw = crate::encoder::bitwriter::BitWriter::new();
encode_uncompressed_header(&mut bw, &fh_c, &seq);
bw.byte_align();
bw.finish()
};
let a = write_with(0);
let b = write_with(1);
debug_assert_eq!(a.len(), b.len(), "f(n) field cannot move the header length");
let n = ti.tile_cols_log2 + ti.tile_rows_log2;
let mut lsb: Option<u32> = None;
for (i, (&x, &y)) in a.iter().zip(b.iter()).enumerate() {
let d = x ^ y;
if d != 0 {
debug_assert!(
d.count_ones() == 1 && lsb.is_none(),
"exactly one bit distinguishes ctx ids 0 and 1"
);
lsb = Some((i as u32) * 8 + (7 - d.trailing_zeros()));
}
}
let lsb = lsb.ok_or(Error::PartitionWalkOutOfRange)?;
Some((lsb + 1 - n, n))
} else {
None
};
let sh_body = write_sequence_header_obu(&seq);
let effective_groups = extras.tile_groups.clamp(1, num_tiles);
let spans_split = extras.tile_spans && effective_groups == 1 && num_tiles > 1;
let temporal_unit_bytes = if effective_groups > 1 || spans_split {
let fh_payload = crate::encoder::frame_obu::write_frame_header_obu(&fh, &seq);
let tg_bodies = if spans_split {
vec![crate::encoder::tile_group_obu::single_group_with_spans(
&tile_group_body,
num_tiles,
ti.tile_cols_log2,
ti.tile_rows_log2,
u32::from(ti.tile_size_bytes),
)?]
} else {
crate::encoder::tile_group_obu::split_whole_frame_body(
&tile_group_body,
num_tiles,
ti.tile_cols_log2,
ti.tile_rows_log2,
u32::from(ti.tile_size_bytes),
effective_groups,
)?
};
let mut obus = vec![ObuFrame::new(ObuType::FrameHeader, fh_payload)];
obus.extend(
tg_bodies
.into_iter()
.map(|b| ObuFrame::new(ObuType::TileGroup, b)),
);
build_temporal_unit(Some(&sh_body), &obus)
} else {
let frame_body = {
let mut bw = crate::encoder::bitwriter::BitWriter::new();
encode_uncompressed_header(&mut bw, &fh, &seq);
bw.byte_align();
let mut body = bw.finish();
body.extend_from_slice(&tile_group_body);
body
};
build_temporal_unit(Some(&sh_body), &[ObuFrame::new(ObuType::Frame, frame_body)])
};
let ivf_w = superres.map_or(coded_w, |(up_w, _)| up_w);
let mut ivf_bytes: Vec<u8> = Vec::new();
{
let cursor = std::io::Cursor::new(&mut ivf_bytes);
let mut iw = IvfWriter::new(cursor, FOURCC_AV01, ivf_w as u16, coded_h as u16, 25, 1)
.map_err(|_| Error::PartitionWalkOutOfRange)?;
iw.write_frame(&temporal_unit_bytes, 0)
.map_err(|_| Error::PartitionWalkOutOfRange)?;
iw.patch_frame_count()
.map_err(|_| Error::PartitionWalkOutOfRange)?;
}
let (cdfs, donor_cdfs) = if extras.collect_donor_cdfs && all_cdfs.len() > 1 {
(all_cdfs[ctx_update_id as usize].clone(), all_cdfs)
} else {
(all_cdfs.swap_remove(ctx_update_id as usize), Vec::new())
};
let carry = crate::encoder::inter_frame::RefSlotCarry {
cdfs,
segment_ids: state.mirror().segment_ids().to_vec(),
mi_rows,
mi_cols,
gm_params: crate::uncompressed_header_tail::prev_gm_params_default(),
donor_cdfs,
ctx_update_span,
};
Ok((
EncodedKeyFrameYuv {
ivf_bytes,
temporal_unit_bytes,
recon_y: recon.y,
recon_u: recon.u,
recon_v: recon.v,
seq,
fh,
},
carry,
))
}
pub(crate) fn key_weighted_sse(
src: &YuvFrame,
ry: &[u16],
ru: &[u16],
rv: &[u16],
plan: &[i32],
coded_mi: (u32, u32),
superres_denom: u32,
) -> u64 {
let w = src.width as usize;
let h = src.height as usize;
let (ssx, ssy) = src.format.subsampling();
let cw = src.chroma_width() as usize;
let ch = src.chroma_height() as usize;
let num_planes = src.format.num_planes();
let (mi_rows, mi_cols) = coded_mi;
let sb_rows = mi_rows.div_ceil(16) as usize;
let sb_cols = mi_cols.div_ceil(16) as usize;
debug_assert_eq!(
plan.len(),
sb_rows * sb_cols,
"plan is per coded superblock"
);
let num = u64::from(crate::frame_header::SUPERRES_NUM);
let den = u64::from(superres_denom);
let sb_col_of =
|c: usize| -> usize { (((c as u64 * num / den) / 64) as usize).min(sb_cols - 1) };
let sb_row_of = |r: usize| -> usize { (r / 64).min(sb_rows - 1) };
let mut sse = vec![0u64; sb_rows * sb_cols];
for r in 0..h {
let k_row = sb_row_of(r) * sb_cols;
for c in 0..w {
let diff = i64::from(ry[r * w + c]) - i64::from(src.y[r * w + c]);
sse[k_row + sb_col_of(c)] += (diff * diff) as u64;
}
}
if num_planes > 1 {
for r in 0..ch {
let k_row = sb_row_of(r << ssy) * sb_cols;
for c in 0..cw {
let k = k_row + sb_col_of(c << ssx);
let du = i64::from(ru[r * cw + c]) - i64::from(src.u[r * cw + c]);
let dv = i64::from(rv[r * cw + c]) - i64::from(src.v[r * cw + c]);
sse[k] += (du * du + dv * dv) as u64;
}
}
}
let mut d = 0u64;
for (k, &s) in sse.iter().enumerate() {
let units = plan[k];
d += if units >= 0 {
s >> (2 * units)
} else {
s << (-2 * units)
};
}
d
}
pub(crate) fn min_tile_size_bytes(payloads: &[Vec<u8>]) -> u32 {
let mut need = 1u32;
if let Some((_, non_last)) = payloads.split_last() {
for p in non_last {
debug_assert!(!p.is_empty(), "§5.11.1: non-last tile cannot be empty");
let v = (p.len().max(1) - 1) as u64;
let bytes = match v {
0..=0xFF => 1,
0x100..=0xFFFF => 2,
0x1_0000..=0xFF_FFFF => 3,
_ => 4,
};
need = need.max(bytes);
}
}
need
}
const BD_STRIDE: usize = 18;
#[derive(Clone)]
pub(crate) struct BlockDecodedMirror {
bd: Vec<u8>,
sub_x: u8,
sub_y: u8,
num_planes: u8,
tile: TileGeometry,
}
impl BlockDecodedMirror {
pub(crate) fn new(sub_x: u8, sub_y: u8, num_planes: u8) -> Self {
Self {
bd: vec![0u8; 3 * BD_STRIDE * BD_STRIDE],
sub_x,
sub_y,
num_planes,
tile: TileGeometry {
mi_row_start: 0,
mi_row_end: u32::MAX,
mi_col_start: 0,
mi_col_end: u32::MAX,
},
}
}
pub(crate) fn set_tile(&mut self, tile: TileGeometry) {
self.tile = tile;
}
pub(crate) fn tile_origin_px(&self, plane: usize) -> (usize, usize) {
let (sx, sy) = self.plane_sub(plane);
(
((self.tile.mi_col_start as usize) * 4) >> sx,
((self.tile.mi_row_start as usize) * 4) >> sy,
)
}
#[inline]
fn plane_sub(&self, plane: usize) -> (u32, u32) {
if plane > 0 {
(u32::from(self.sub_x), u32::from(self.sub_y))
} else {
(0, 0)
}
}
#[inline]
fn slot(plane: usize, y: i32, x: i32) -> Option<usize> {
if plane >= 3 {
return None;
}
let yi = y + 1;
let xi = x + 1;
if yi < 0 || xi < 0 {
return None;
}
let (yi, xi) = (yi as usize, xi as usize);
if yi >= BD_STRIDE || xi >= BD_STRIDE {
return None;
}
Some(plane * BD_STRIDE * BD_STRIDE + yi * BD_STRIDE + xi)
}
pub(crate) fn clear_for_sb(&mut self, sb_r: u32, sb_c: u32, mi_rows: u32, mi_cols: u32) {
const SB_SIZE4: i32 = 16;
let mi_row_end = self.tile.mi_row_end.min(mi_rows);
let mi_col_end = self.tile.mi_col_end.min(mi_cols);
for plane in 0..usize::from(self.num_planes) {
let (sx, sy) = self.plane_sub(plane);
let (sub_x, sub_y) = (sx as i32, sy as i32);
let sb_width4 = (mi_col_end as i32 - sb_c as i32) >> sub_x;
let sb_height4 = (mi_row_end as i32 - sb_r as i32) >> sub_y;
let y_max = SB_SIZE4 >> sub_y;
let x_max = SB_SIZE4 >> sub_x;
for y in -1..=y_max {
for x in -1..=x_max {
let val = u8::from((y < 0 && x < sb_width4) || (x < 0 && y < sb_height4));
if let Some(slot) = Self::slot(plane, y, x) {
self.bd[slot] = val;
}
}
}
if let Some(slot) = Self::slot(plane, y_max, -1) {
self.bd[slot] = 0;
}
}
}
#[inline]
fn get(&self, plane: usize, y: i32, x: i32) -> bool {
Self::slot(plane, y, x).is_some_and(|s| self.bd[s] != 0)
}
#[inline]
fn set(&mut self, plane: usize, y: i32, x: i32) {
if let Some(slot) = Self::slot(plane, y, x) {
self.bd[slot] = 1;
}
}
}
#[inline]
fn tu_bd_anchor(
bd: &BlockDecodedMirror,
plane: usize,
start_x: usize,
start_y: usize,
) -> (i32, i32) {
let (sub_x, sub_y) = bd.plane_sub(plane);
let row = ((start_y as u32) << sub_y) >> 2;
let col = ((start_x as u32) << sub_x) >> 2;
let base_row = ((row & 15) >> sub_y) as i32;
let base_col = ((col & 15) >> sub_x) as i32;
(base_row, base_col)
}
#[inline]
pub(crate) fn tu_corner_avail(
bd: &BlockDecodedMirror,
plane: usize,
start_x: usize,
start_y: usize,
tx_w: usize,
tx_h: usize,
) -> (bool, bool) {
let (base_row, base_col) = tu_bd_anchor(bd, plane, start_x, start_y);
let step_x4 = (tx_w >> 2) as i32;
let step_y4 = (tx_h >> 2) as i32;
let above_right = bd.get(plane, base_row - 1, base_col + step_x4);
let below_left = bd.get(plane, base_row + step_y4, base_col - 1);
(above_right, below_left)
}
#[inline]
pub(crate) fn tu_bd_stamp(
bd: &mut BlockDecodedMirror,
plane: usize,
start_x: usize,
start_y: usize,
tx_w: usize,
tx_h: usize,
) {
let (base_row, base_col) = tu_bd_anchor(bd, plane, start_x, start_y);
let step_x4 = (tx_w >> 2) as i32;
let step_y4 = (tx_h >> 2) as i32;
for i in 0..step_y4 {
for j in 0..step_x4 {
bd.set(plane, base_row + i, base_col + j);
}
}
}
#[derive(Clone)]
pub(crate) struct ReconState {
pub(crate) y: Vec<u16>,
pub(crate) u: Vec<u16>,
pub(crate) v: Vec<u16>,
pub(crate) width: usize,
pub(crate) height: usize,
pub(crate) chroma_w: usize,
pub(crate) chroma_h: usize,
pub(crate) bit_depth: u8,
pub(crate) subsampling_x: u8,
pub(crate) subsampling_y: u8,
pub(crate) num_planes: u8,
pub(crate) mi_rows: u32,
pub(crate) mi_cols: u32,
pub(crate) lossless: bool,
pub(crate) allow_screen_content_tools: bool,
pub(crate) allow_intrabc: bool,
pub(crate) qp: QuantizerParams,
pub(crate) bd: BlockDecodedMirror,
pub(crate) dv_hash: crate::encoder::dv_hash::DvHashIndex,
pub(crate) search: SearchLimits,
}
impl ReconState {
pub(crate) fn plane(&self, plane: usize) -> (&[u16], usize, usize) {
match plane {
0 => (&self.y, self.width, self.height),
1 => (&self.u, self.chroma_w, self.chroma_h),
_ => (&self.v, self.chroma_w, self.chroma_h),
}
}
#[inline]
pub(crate) fn chroma_origin(&self, r: u32, c: u32) -> (usize, usize) {
(
((r >> self.subsampling_y) as usize) * 4,
((c >> self.subsampling_x) as usize) * 4,
)
}
}
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub(crate) fn build_tu_neighbours(
plane_buf: &[u16],
pw: usize,
ph: usize,
start_x: usize,
start_y: usize,
tile_x0: usize,
tile_y0: usize,
w: usize,
h: usize,
have_above_right: bool,
have_below_left: bool,
bit_depth: u8,
) -> (Vec<u16>, Vec<u16>, bool, bool) {
let have_above = start_y > tile_y0;
let have_left = start_x > tile_x0;
let max_x = pw - 1;
let max_y = ph - 1;
let read = |yy: usize, xx: usize| -> u16 { plane_buf[yy.min(max_y) * pw + xx.min(max_x)] };
let half: u16 = 1 << (bit_depth - 1);
let corner: u16 = if have_above && have_left {
read(start_y - 1, start_x - 1)
} else if have_above {
read(start_y - 1, start_x)
} else if have_left {
read(start_y, start_x - 1)
} else {
half
};
let span = w + h;
let ext_cap = 2 * span + 2;
let mut above_ext = vec![0u16; ext_cap];
above_ext[0] = corner;
above_ext[1] = corner;
if !have_above && have_left {
let v = read(start_y, start_x - 1);
for slot in above_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else if !have_above && !have_left {
let v = half - 1;
for slot in above_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else {
let above_limit = max_x.min(start_x + (if have_above_right { 2 * w } else { w }) - 1);
for (i, slot) in above_ext.iter_mut().skip(2).take(span).enumerate() {
*slot = read(start_y - 1, above_limit.min(start_x + i));
}
}
let mut left_ext = vec![0u16; ext_cap];
left_ext[0] = corner;
left_ext[1] = corner;
if !have_left && have_above {
let v = read(start_y - 1, start_x);
for slot in left_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else if !have_left && !have_above {
let v = half + 1;
for slot in left_ext.iter_mut().skip(2).take(span) {
*slot = v;
}
} else {
let left_limit = max_y.min(start_y + (if have_below_left { 2 * h } else { h }) - 1);
for (i, slot) in left_ext.iter_mut().skip(2).take(span).enumerate() {
*slot = read(left_limit.min(start_y + i), start_x - 1);
}
}
(above_ext, left_ext, have_above, have_left)
}
#[allow(clippy::too_many_arguments)]
fn predict_mode_from_neighbours(
mode: usize,
angle_delta: i32,
w: usize,
h: usize,
above_ext: &[u16],
left_ext: &[u16],
have_above: bool,
have_left: bool,
bit_depth: u8,
) -> Option<Vec<u16>> {
let span = w + h;
let above_row = &above_ext[2..2 + span];
let left_col = &left_ext[2..2 + span];
let corner = above_ext[1];
let log2_w = w.trailing_zeros();
let log2_h = h.trailing_zeros();
let mut pred = vec![0u16; w * h];
let ok = match mode {
m if m == DC_PRED => predict_intra_dc_pred(
u8::from(have_left),
u8::from(have_above),
log2_w,
log2_h,
w,
h,
bit_depth,
above_row,
left_col,
&mut pred,
)
.is_ok(),
m if m == V_PRED && angle_delta == 0 => {
predict_intra_v_pred(w, h, above_row, &mut pred).is_ok()
}
m if m == H_PRED && angle_delta == 0 => {
predict_intra_h_pred(w, h, left_col, &mut pred).is_ok()
}
m if (V_PRED..=D67_PRED).contains(&m) => predict_intra_directional(
w,
h,
MODE_TO_ANGLE[m] + angle_delta * ANGLE_STEP,
0,
0,
above_ext,
left_ext,
&mut pred,
)
.is_ok(),
m if m == SMOOTH_PRED => {
predict_intra_smooth_pred(log2_w, log2_h, w, h, above_row, left_col, &mut pred).is_ok()
}
m if m == SMOOTH_V_PRED => {
predict_intra_smooth_v_pred(log2_h, w, h, above_row, left_col, &mut pred).is_ok()
}
m if m == SMOOTH_H_PRED => {
predict_intra_smooth_h_pred(log2_w, w, h, above_row, left_col, &mut pred).is_ok()
}
m if m == PAETH_PRED => {
predict_intra_paeth_pred(w, h, above_row, left_col, corner, &mut pred).is_ok()
}
_ => false,
};
if !ok {
return None;
}
Some(pred)
}
pub(crate) fn predict_filter_intra_from_neighbours(
fim: usize,
w: usize,
h: usize,
above_ext: &[u16],
left_ext: &[u16],
bit_depth: u8,
) -> Option<Vec<u16>> {
let mut pred = vec![0u16; w * h];
crate::cdf::predict_intra_recursive(w, h, fim, bit_depth, above_ext, left_ext, &mut pred)
.ok()?;
Some(pred)
}
#[allow(clippy::too_many_arguments)]
fn predict_tu(
recon: &ReconState,
plane: usize,
start_x: usize,
start_y: usize,
w: usize,
h: usize,
mode: usize,
angle_delta: i32,
fim: Option<usize>,
) -> Vec<u16> {
let (buf, pw, ph) = recon.plane(plane);
let (avail_ar, avail_bl) = tu_corner_avail(&recon.bd, plane, start_x, start_y, w, h);
let (tile_x0, tile_y0) = recon.bd.tile_origin_px(plane);
let (above_ext, left_ext, have_above, have_left) = build_tu_neighbours(
buf,
pw,
ph,
start_x,
start_y,
tile_x0,
tile_y0,
w,
h,
avail_ar,
avail_bl,
recon.bit_depth,
);
if let Some(f) = fim.filter(|_| plane == 0) {
return predict_filter_intra_from_neighbours(
f,
w,
h,
&above_ext,
&left_ext,
recon.bit_depth,
)
.expect("filter-intra kernel domain holds for coded TU sizes");
}
predict_mode_from_neighbours(
mode,
angle_delta,
w,
h,
&above_ext,
&left_ext,
have_above,
have_left,
recon.bit_depth,
)
.expect("supported intra mode always predicts")
}
#[inline]
fn round2_signed(x: i64, n: u32) -> i64 {
let half: i64 = 1i64 << (n - 1);
if x < 0 {
-(((-x) + half) >> n)
} else {
(x + half) >> n
}
}
#[allow(clippy::too_many_arguments)]
fn cfl_layer(
dc_pred: &[u16],
recon_y: &[u16],
luma_w: usize,
start_x: usize,
start_y: usize,
w: usize,
h: usize,
alpha: i8,
max_luma_w: usize,
max_luma_h: usize,
sub_x: u8,
sub_y: u8,
bit_depth: u8,
) -> Vec<u16> {
let (sub_x, sub_y) = (u32::from(sub_x), u32::from(sub_y));
let clamp_x = max_luma_w.saturating_sub(1 << sub_x);
let clamp_y = max_luma_h.saturating_sub(1 << sub_y);
let mut l = vec![0i64; w * h];
let mut luma_sum: i64 = 0;
for i in 0..h {
let luma_y = ((start_y + i) << sub_y).min(clamp_y);
for j in 0..w {
let luma_x = ((start_x + j) << sub_x).min(clamp_x);
let mut t = 0i64;
for dy in 0..=sub_y as usize {
for dx in 0..=sub_x as usize {
t += i64::from(recon_y[(luma_y + dy) * luma_w + luma_x + dx]);
}
}
let v = t << (3 - sub_x - sub_y);
l[i * w + j] = v;
luma_sum += v;
}
}
let log2_w = w.trailing_zeros();
let log2_h = h.trailing_zeros();
let luma_avg = round2_signed(luma_sum, log2_w + log2_h);
let max_val = (1i64 << bit_depth) - 1;
let mut out = vec![0u16; w * h];
for k in 0..w * h {
let scaled = round2_signed((alpha as i64) * (l[k] - luma_avg), 6);
out[k] = (i64::from(dc_pred[k]) + scaled).clamp(0, max_val) as u16;
}
out
}
#[allow(clippy::too_many_arguments)]
fn cfl_luma_ac(
recon_y: &[u16],
luma_w: usize,
start_x: usize,
start_y: usize,
w: usize,
h: usize,
max_luma_w: usize,
max_luma_h: usize,
sub_x: u8,
sub_y: u8,
) -> Vec<i64> {
let (sub_x, sub_y) = (u32::from(sub_x), u32::from(sub_y));
let clamp_x = max_luma_w.saturating_sub(1 << sub_x);
let clamp_y = max_luma_h.saturating_sub(1 << sub_y);
let mut l = vec![0i64; w * h];
let mut luma_sum: i64 = 0;
for i in 0..h {
let luma_y = ((start_y + i) << sub_y).min(clamp_y);
for j in 0..w {
let luma_x = ((start_x + j) << sub_x).min(clamp_x);
let mut t = 0i64;
for dy in 0..=sub_y as usize {
for dx in 0..=sub_x as usize {
t += i64::from(recon_y[(luma_y + dy) * luma_w + luma_x + dx]);
}
}
let v = t << (3 - sub_x - sub_y);
l[i * w + j] = v;
luma_sum += v;
}
}
let luma_avg = round2_signed(luma_sum, w.trailing_zeros() + h.trailing_zeros());
for v in l.iter_mut() {
*v -= luma_avg;
}
l
}
pub(crate) fn repack_compact(dense: Vec<i32>, w: usize, h: usize) -> Vec<i32> {
let tw = w.min(32);
let th = h.min(32);
if tw == w && th == h {
return dense;
}
let mut q = vec![0i32; w * h];
for i in 0..th {
for j in 0..tw {
q[i * tw + j] = dense[i * w + j];
}
}
q
}
#[inline]
pub(crate) fn step3_flips(tx_type: usize) -> (bool, bool) {
use crate::cdf::{
ADST_FLIPADST, DCT_FLIPADST, FLIPADST_ADST, FLIPADST_DCT, FLIPADST_FLIPADST, H_FLIPADST,
V_FLIPADST,
};
let flip_ud = matches!(
tx_type,
FLIPADST_DCT | FLIPADST_ADST | V_FLIPADST | FLIPADST_FLIPADST
);
let flip_lr = matches!(
tx_type,
DCT_FLIPADST | ADST_FLIPADST | H_FLIPADST | FLIPADST_FLIPADST
);
(flip_ud, flip_lr)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn residual_tx(
input_plane: &[u16],
recon_plane: &mut [u16],
pw: usize,
row0: usize,
col0: usize,
tx_sz: usize,
pred: &[u16],
plane: u8,
lossless: bool,
tx_type: usize,
qp: &QuantizerParams,
) -> Vec<i32> {
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
residual_tx_avail(
input_plane,
recon_plane,
pw,
row0,
col0,
tx_sz,
pred,
plane,
lossless,
tx_type,
qp,
w,
h,
crate::encoder::forward_quantize::QUANT_ROUND_HALF,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn residual_tx_avail(
input_plane: &[u16],
recon_plane: &mut [u16],
pw: usize,
row0: usize,
col0: usize,
tx_sz: usize,
pred: &[u16],
plane: u8,
lossless: bool,
tx_type: usize,
qp: &QuantizerParams,
avail_w: usize,
avail_h: usize,
round_num_64: i64,
) -> Vec<i32> {
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
let (aw, ah) = (avail_w.min(w), avail_h.min(h));
let mut residual = vec![0i64; w * h];
for i in 0..ah {
for j in 0..aw {
residual[i * w + j] =
i64::from(input_plane[(row0 + i) * pw + (col0 + j)]) - i64::from(pred[i * w + j]);
}
}
let coeffs = if lossless {
debug_assert_eq!(tx_sz, TX_4X4);
let mut r16 = [0i64; 16];
r16.copy_from_slice(&residual);
forward_wht_4x4(&r16).to_vec()
} else {
forward_transform_2d(&residual, tx_sz, tx_type, false)
};
let qm = qp.seg_qm_level[plane as usize][0];
let dense = forward_quantize_rounded(&coeffs, tx_sz, plane, 0, tx_type, qm, qp, round_num_64);
let quant = repack_compact(dense, w, h);
let dequant = dequantize_step1(&quant, tx_sz, plane, 0, tx_type, qm, qp);
let res_back =
inverse_transform_2d(&dequant, tx_sz, tx_type, u32::from(qp.bit_depth), lossless);
let (flip_ud, flip_lr) = step3_flips(tx_type);
let max_val = (1i64 << qp.bit_depth) - 1;
for i in 0..h {
let yy = if flip_ud { h - 1 - i } else { i };
for j in 0..w {
let xx = if flip_lr { w - 1 - j } else { j };
if yy >= ah || xx >= aw {
continue;
}
let p = i64::from(pred[yy * w + xx]) + res_back[i * w + j];
recon_plane[(row0 + yy) * pw + (col0 + xx)] = p.clamp(0, max_val) as u16;
}
}
quant
}
#[allow(clippy::too_many_arguments)]
fn residual_tx_search_luma_avail(
input_plane: &[u16],
recon_plane: &mut [u16],
pw: usize,
row0: usize,
col0: usize,
tx_sz: usize,
pred: &[u16],
qp: &QuantizerParams,
fork: Option<(&mut TuFork, &TuCtx<'_>)>,
avail_w: usize,
avail_h: usize,
tx_type_shortlist: Option<&[usize]>,
round_num_64: i64,
) -> Result<(Vec<i32>, u8), Error> {
let w = TX_WIDTH[tx_sz];
let h = TX_HEIGHT[tx_sz];
let (aw, ah) = (avail_w.min(w), avail_h.min(h));
let max_val = (1i64 << qp.bit_depth) - 1;
let mut residual = vec![0i64; w * h];
for i in 0..ah {
for j in 0..aw {
residual[i * w + j] =
i64::from(input_plane[(row0 + i) * pw + (col0 + j)]) - i64::from(pred[i * w + j]);
}
}
let set = intra_tx_type_set(
tx_size_sqr_index(tx_sz) as u32,
TX_SIZE_SQR_UP[tx_sz] as u32,
false,
);
let lambda = lambda_for(qp);
let fork_xy = fork.as_ref().map(|(_, ctx)| {
(
(col0 as u32 - ctx.base_x) / 4,
(row0 as u32 - ctx.base_y) / 4,
)
});
let mut best: Option<(Vec<i32>, Vec<i64>, u8, u64)> = None;
for t in 0..crate::cdf::TX_TYPES {
let admissible = t == DCT_DCT || (set > 0 && is_tx_type_in_set(false, set, t));
if !admissible || tx_type_shortlist.is_some_and(|l| !l.contains(&t)) {
continue;
}
let coeffs = forward_transform_2d(&residual, tx_sz, t, false);
let qm = qp.seg_qm_level[0][0];
let quant = repack_compact(
forward_quantize_rounded(&coeffs, tx_sz, 0, 0, t, qm, qp, round_num_64),
w,
h,
);
let all_zero = quant.iter().all(|&q| q == 0);
let dequant = dequantize_step1(&quant, tx_sz, 0, 0, t, qm, qp);
let res_back = inverse_transform_2d(&dequant, tx_sz, t, u32::from(qp.bit_depth), false);
let (flip_ud, flip_lr) = step3_flips(t);
let src_idx = |i: usize, j: usize| -> usize {
let yy = if flip_ud { h - 1 - i } else { i };
let xx = if flip_lr { w - 1 - j } else { j };
yy * w + xx
};
let mut d = 0u64;
for i in 0..ah {
for j in 0..aw {
let rec = (i64::from(pred[i * w + j]) + res_back[src_idx(i, j)]).clamp(0, max_val);
let diff = i64::from(input_plane[(row0 + i) * pw + (col0 + j)]) - rec;
d += (diff * diff) as u64;
}
}
let label = if all_zero { DCT_DCT as u8 } else { t as u8 };
let score = match (&fork, fork_xy) {
(Some((tu_fork, ctx)), Some((fx, fy))) => {
let bits256 = tu_fork.price_luma_tu(ctx, tx_sz, fx, fy, &quant, label)?;
score256(d, lambda, bits256)
}
_ => {
let mut rate = 0u64;
for &qv in &quant {
if qv != 0 {
rate += 3 + u64::from(32 - qv.unsigned_abs().leading_zeros());
}
}
d + lambda * rate
}
};
let improves = match best.as_ref() {
Some((_, _, _, s)) => score < *s,
None => true,
};
if improves {
best = Some((quant, res_back, label, score));
}
if t == DCT_DCT && all_zero {
break;
}
}
let (quant, res_back, label, _) = best.expect("DCT_DCT is always admissible");
if let (Some((tu_fork, ctx)), Some((fx, fy))) = (fork, fork_xy) {
tu_fork.commit_luma_tu(ctx, tx_sz, fx, fy, &quant, label)?;
}
let (flip_ud, flip_lr) = step3_flips(label as usize);
for i in 0..ah {
for j in 0..aw {
let yy = if flip_ud { h - 1 - i } else { i };
let xx = if flip_lr { w - 1 - j } else { j };
let p = i64::from(pred[i * w + j]) + res_back[yy * w + xx];
recon_plane[(row0 + i) * pw + (col0 + j)] = p.clamp(0, max_val) as u16;
}
}
Ok((quant, label))
}
fn angle_delta_candidates(mode: usize, n: usize) -> core::ops::RangeInclusive<i32> {
if (V_PRED..=D67_PRED).contains(&mode) && n >= 8 {
-3..=3
} else {
0..=0
}
}
fn pick_y_mode(
recon: &ReconState,
input: &YuvFrame,
row0: usize,
col0: usize,
bw: usize,
bh: usize,
) -> (u8, i8, Option<u8>) {
let (aw, ah) = (bw.min(recon.width - col0), bh.min(recon.height - row0));
let (avail_ar, avail_bl) = tu_corner_avail(&recon.bd, 0, col0, row0, bw, bh);
let (tile_x0, tile_y0) = recon.bd.tile_origin_px(0);
let (above_ext, left_ext, have_above, have_left) = build_tu_neighbours(
&recon.y,
recon.width,
recon.height,
col0,
row0,
tile_x0,
tile_y0,
bw,
bh,
avail_ar,
avail_bl,
recon.bit_depth,
);
let ssd_of = |pred: &[u16]| -> u64 {
let mut ssd = 0u64;
for i in 0..ah {
for j in 0..aw {
let d = i64::from(input.y[(row0 + i) * recon.width + (col0 + j)])
- i64::from(pred[i * bw + j]);
ssd += (d * d) as u64;
}
}
ssd
};
let mut best = (DC_PRED as u8, 0i8, None);
let mut best_ssd = u64::MAX;
let try_mode = |mode: usize,
delta: i32,
best: &mut (u8, i8, Option<u8>),
best_ssd: &mut u64|
-> Option<u64> {
let pred = predict_mode_from_neighbours(
mode,
delta,
bw,
bh,
&above_ext,
&left_ext,
have_above,
have_left,
recon.bit_depth,
)?;
let ssd = ssd_of(&pred);
if ssd < *best_ssd {
*best_ssd = ssd;
*best = (mode as u8, delta as i8, None);
}
Some(ssd)
};
if recon.search.intra_mode_prescreen {
let mut dir_scores: Vec<(u64, usize)> = Vec::with_capacity(8);
for mode in 0..INTRA_MODES {
if let Some(ssd) = try_mode(mode, 0, &mut best, &mut best_ssd) {
if (V_PRED..=D67_PRED).contains(&mode) {
dir_scores.push((ssd, mode));
}
}
}
dir_scores.sort_unstable();
for &(_, mode) in dir_scores.iter().take(2) {
for delta in angle_delta_candidates(mode, bw.min(bh)) {
if delta != 0 && delta.abs() <= 1 {
try_mode(mode, delta, &mut best, &mut best_ssd);
}
}
}
} else {
for mode in 0..INTRA_MODES {
for delta in angle_delta_candidates(mode, bw.min(bh)) {
try_mode(mode, delta, &mut best, &mut best_ssd);
}
}
}
if bw.max(bh) <= 32 {
for fim in 0..crate::cdf::INTRA_FILTER_MODES {
let Some(pred) = predict_filter_intra_from_neighbours(
fim,
bw,
bh,
&above_ext,
&left_ext,
recon.bit_depth,
) else {
continue;
};
let ssd = ssd_of(&pred);
if ssd < best_ssd {
best_ssd = ssd;
best = (DC_PRED as u8, 0, Some(fim as u8));
}
}
}
best
}
#[allow(clippy::too_many_arguments)]
fn pick_uv_mode(
recon: &ReconState,
input: &YuvFrame,
crow0: usize,
ccol0: usize,
cbw: usize,
cbh: usize,
cfl_allowed: bool,
luma_n: usize,
max_luma_w: usize,
max_luma_h: usize,
luma_hint: (u8, i8),
) -> (u8, i8, Option<(i8, i8)>) {
let pw = recon.chroma_w;
let (ar_u, bl_u) = tu_corner_avail(&recon.bd, 1, ccol0, crow0, cbw, cbh);
let (ctile_x0, ctile_y0) = recon.bd.tile_origin_px(1);
let (above_u, left_u, ha_u, hl_u) = build_tu_neighbours(
&recon.u,
recon.chroma_w,
recon.chroma_h,
ccol0,
crow0,
ctile_x0,
ctile_y0,
cbw,
cbh,
ar_u,
bl_u,
recon.bit_depth,
);
let (ar_v, bl_v) = tu_corner_avail(&recon.bd, 2, ccol0, crow0, cbw, cbh);
let (above_v, left_v, ha_v, hl_v) = build_tu_neighbours(
&recon.v,
recon.chroma_w,
recon.chroma_h,
ccol0,
crow0,
ctile_x0,
ctile_y0,
cbw,
cbh,
ar_v,
bl_v,
recon.bit_depth,
);
let (caw, cah) = (
cbw.min(recon.chroma_w - ccol0),
cbh.min(recon.chroma_h - crow0),
);
let ssd_uv = |pred_u: &[u16], pred_v: &[u16]| -> u64 {
let mut ssd = 0u64;
for i in 0..cah {
for j in 0..caw {
let idx = (crow0 + i) * pw + (ccol0 + j);
let du = i64::from(input.u[idx]) - i64::from(pred_u[i * cbw + j]);
let dv = i64::from(input.v[idx]) - i64::from(pred_v[i * cbw + j]);
ssd += (du * du + dv * dv) as u64;
}
}
ssd
};
let mut best_mode = DC_PRED as u8;
let mut best_delta = 0i8;
let mut best_alpha: Option<(i8, i8)> = None;
let mut best_ssd = u64::MAX;
let mut dc_pred_u: Vec<u16> = Vec::new();
let mut dc_pred_v: Vec<u16> = Vec::new();
let try_mode = |mode: usize,
delta: i32,
best_mode: &mut u8,
best_delta: &mut i8,
best_alpha: &mut Option<(i8, i8)>,
best_ssd: &mut u64,
dc_pred_u: &mut Vec<u16>,
dc_pred_v: &mut Vec<u16>|
-> Option<u64> {
let pred_u = predict_mode_from_neighbours(
mode,
delta,
cbw,
cbh,
&above_u,
&left_u,
ha_u,
hl_u,
recon.bit_depth,
)?;
let pred_v = predict_mode_from_neighbours(
mode,
delta,
cbw,
cbh,
&above_v,
&left_v,
ha_v,
hl_v,
recon.bit_depth,
)?;
let ssd = ssd_uv(&pred_u, &pred_v);
if ssd < *best_ssd {
*best_ssd = ssd;
*best_mode = mode as u8;
*best_delta = delta as i8;
*best_alpha = None;
}
if mode == DC_PRED {
*dc_pred_u = pred_u;
*dc_pred_v = pred_v;
}
Some(ssd)
};
if recon.search.intra_mode_prescreen {
let mut list: Vec<(usize, i32)> = vec![
(DC_PRED, 0),
(V_PRED, 0),
(H_PRED, 0),
(SMOOTH_PRED, 0),
(PAETH_PRED, 0),
];
let (ym, yd) = (luma_hint.0 as usize, i32::from(luma_hint.1));
if ym < INTRA_MODES && !list.contains(&(ym, yd)) {
let yd = if angle_delta_candidates(ym, luma_n).contains(&yd) {
yd
} else {
0
};
if !list.contains(&(ym, yd)) {
list.push((ym, yd));
}
}
for (mode, delta) in list {
try_mode(
mode,
delta,
&mut best_mode,
&mut best_delta,
&mut best_alpha,
&mut best_ssd,
&mut dc_pred_u,
&mut dc_pred_v,
);
}
} else {
for mode in 0..INTRA_MODES {
for delta in angle_delta_candidates(mode, luma_n) {
try_mode(
mode,
delta,
&mut best_mode,
&mut best_delta,
&mut best_alpha,
&mut best_ssd,
&mut dc_pred_u,
&mut dc_pred_v,
);
}
}
}
if cfl_allowed {
let ac = cfl_luma_ac(
&recon.y,
recon.width,
ccol0,
crow0,
cbw,
cbh,
max_luma_w,
max_luma_h,
recon.subsampling_x,
recon.subsampling_y,
);
let seed = |plane_in: &[u16], dc: &[u16]| -> i8 {
let mut num = 0i128;
let mut den = 0i128;
for i in 0..cah {
for j in 0..caw {
let k = i * cbw + j;
let r = i64::from(plane_in[(crow0 + i) * pw + (ccol0 + j)]) - i64::from(dc[k]);
num += i128::from(r) * i128::from(ac[k]);
den += i128::from(ac[k]) * i128::from(ac[k]);
}
}
if den == 0 {
return 0;
}
let a = (64 * num + den.signum() * den / 2) / den;
a.clamp(-16, 16) as i8
};
let (su, sv) = (seed(&input.u, &dc_pred_u), seed(&input.v, &dc_pred_v));
let around =
|a: i8| -> [i8; 3] { [a.saturating_sub(1).max(-16), a, a.saturating_add(1).min(16)] };
let mut cands: Vec<(i8, i8)> = Vec::with_capacity(9);
if recon.search.intra_mode_prescreen {
let (ar, av_) = (around(su), around(sv));
for &(au, av) in &[
(su, sv),
(ar[0], sv),
(ar[2], sv),
(su, av_[0]),
(su, av_[2]),
] {
if (au != 0 || av != 0) && !cands.contains(&(au, av)) {
cands.push((au, av));
}
}
} else {
for &au in &around(su) {
for &av in &around(sv) {
if (au != 0 || av != 0) && !cands.contains(&(au, av)) {
cands.push((au, av));
}
}
}
}
for &(au, av) in &cands {
let pred_u = cfl_layer(
&dc_pred_u,
&recon.y,
recon.width,
ccol0,
crow0,
cbw,
cbh,
au,
max_luma_w,
max_luma_h,
recon.subsampling_x,
recon.subsampling_y,
recon.bit_depth,
);
let pred_v = cfl_layer(
&dc_pred_v,
&recon.y,
recon.width,
ccol0,
crow0,
cbw,
cbh,
av,
max_luma_w,
max_luma_h,
recon.subsampling_x,
recon.subsampling_y,
recon.bit_depth,
);
let ssd = ssd_uv(&pred_u, &pred_v);
if ssd < best_ssd {
best_ssd = ssd;
best_mode = UV_CFL_PRED as u8;
best_delta = 0;
best_alpha = Some((au, av));
}
}
}
(best_mode, best_delta, best_alpha)
}
fn chroma_tx_type_for(uv_mode: u8, chroma_tx: usize, lossless: bool) -> usize {
if lossless {
return DCT_DCT;
}
if TX_SIZE_SQR_UP[chroma_tx] > crate::cdf::TX_32X32 {
return DCT_DCT;
}
let t = MODE_TO_TXFM
.get(uv_mode as usize)
.copied()
.unwrap_or(DCT_DCT);
let set = intra_tx_type_set(
tx_size_sqr_index(chroma_tx) as u32,
TX_SIZE_SQR_UP[chroma_tx] as u32,
false,
);
if is_tx_type_in_set(false, set, t) {
t
} else {
DCT_DCT
}
}
pub(crate) fn encode_leaf_sq(
mi_r: u32,
mi_c: u32,
b_size: usize,
input: &YuvFrame,
recon: &mut ReconState,
pricing: Option<(&RateTwin, &SyntaxFrameParams)>,
) -> Result<SyntaxBlock, Error> {
encode_leaf_sq_seg(mi_r, mi_c, b_size, input, recon, pricing, None)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_leaf_sq_seg(
mi_r: u32,
mi_c: u32,
b_size: usize,
input: &YuvFrame,
recon: &mut ReconState,
pricing: Option<(&RateTwin, &SyntaxFrameParams)>,
segment_override: Option<u8>,
) -> Result<SyntaxBlock, Error> {
let max_pal = if pricing.is_some() { 2 } else { 1 };
let (pal_y_list, pal_uv_list) = if recon.allow_screen_content_tools {
(
palette_candidates_y(input, recon, mi_r, mi_c, b_size, max_pal),
palette_candidates_uv(input, recon, mi_r, mi_c, b_size, max_pal),
)
} else {
(Vec::new(), Vec::new())
};
let single_shape = recon.lossless || b_size == BLOCK_4X4;
let cands: Vec<usize> = if single_shape {
vec![if recon.lossless {
TX_4X4
} else {
MAX_TX_SIZE_RECT[b_size]
}]
} else {
let max_steps = if b_size == BLOCK_8X8 {
1.min(recon.search.tx_depth_steps)
} else {
MAX_TX_DEPTH.min(recon.search.tx_depth_steps)
};
let mut v = vec![MAX_TX_SIZE_RECT[b_size]];
for _ in 0..max_steps {
let next = SPLIT_TX_SIZE[*v.last().expect("non-empty")];
v.push(next);
}
v
};
let mut combos: Vec<(Option<&PaletteCandY>, Option<&PaletteCandUv>)> = vec![(None, None)];
for p in &pal_y_list {
combos.push((Some(p), None));
}
for p in &pal_uv_list {
combos.push((None, Some(p)));
}
for a in &pal_y_list {
for b in &pal_uv_list {
combos.push((Some(a), Some(b)));
}
}
let intrabc_dv = if recon.allow_intrabc
&& matches!(
b_size,
BLOCK_8X8 | crate::cdf::BLOCK_16X16 | crate::cdf::BLOCK_32X32 | BLOCK_64X64
) {
intrabc_best_dv(input, recon, mi_r, mi_c, b_size)
} else {
None
};
if (single_shape || cands.len() == 1) && combos.len() == 1 && intrabc_dv.is_none() {
let mut leaf = encode_leaf_with_tx(
mi_r, mi_c, b_size, cands[0], input, recon, None, None, pricing,
)?;
if let Some(seg) = segment_override {
leaf.segment_id = seg;
}
if let Some((twin, params)) = pricing {
if params.segmentation_enabled && !params.seg_id_pre_skip && leaf.skip == 1 {
leaf.segment_id = twin.spatial_segment_pred(mi_r, mi_c);
if params.lossless_array[leaf.segment_id as usize] {
leaf.tx_size = None;
}
}
}
return Ok(leaf);
}
let w4 = NUM_4X4_BLOCKS_WIDE[b_size];
let h4 = crate::cdf::NUM_4X4_BLOCKS_HIGH[b_size];
let lambda = lambda_for(&recon.qp);
let rate_of = |leaf: &SyntaxBlock, depth: u64| -> Result<u64, Error> {
match pricing {
Some((twin, params)) => twin.price_block(leaf, mi_r, mi_c, b_size, params),
None => Ok(leaf_rate(leaf) + 2 * depth),
}
};
let score_of = |d: u64, rate: u64| -> u64 {
match pricing {
Some(_) => score256(d, lambda, rate),
None => d + lambda * rate,
}
};
let before = save_region_wh(recon, mi_r, mi_c, w4, h4);
let fix_skip_segment = |leaf: &mut SyntaxBlock| {
if let Some(seg) = segment_override {
leaf.segment_id = seg;
}
if let Some((twin, params)) = pricing {
if params.segmentation_enabled && !params.seg_id_pre_skip && leaf.skip == 1 {
leaf.segment_id = twin.spatial_segment_pred(mi_r, mi_c);
if params.lossless_array[leaf.segment_id as usize] {
leaf.tx_size = None;
}
}
}
};
let elect_v_arm = |leaf: &mut SyntaxBlock| -> Result<(), Error> {
if leaf.palette.size_uv == 0 {
return Ok(());
}
if let Some((twin, params)) = pricing {
let literal = twin.price_block(leaf, mi_r, mi_c, b_size, params)?;
let mut alt = leaf.clone();
alt.palette.delta_encode_v = true;
if let Ok(delta) = twin.price_block(&alt, mi_r, mi_c, b_size, params) {
if delta < literal {
*leaf = alt;
}
}
}
Ok(())
};
let mut best: Option<(SyntaxBlock, RegionSnapshot, u64)> = None;
for (depth, &cand) in cands.iter().enumerate() {
if depth > 0
&& recon.search.tx_split_prescreen
&& best.as_ref().is_some_and(|(leaf, _, _)| leaf.skip == 1)
{
break;
}
for &(py, puv) in combos.iter() {
let mut leaf =
encode_leaf_with_tx(mi_r, mi_c, b_size, cand, input, recon, py, puv, pricing)?;
fix_skip_segment(&mut leaf);
elect_v_arm(&mut leaf)?;
let d = region_distortion_wh(recon, input, mi_r, mi_c, w4, h4);
let score = score_of(d, rate_of(&leaf, depth as u64)?);
let improves = match best.as_ref() {
Some((_, _, s)) => score < *s,
None => true,
};
if improves {
best = Some((leaf, save_region_wh(recon, mi_r, mi_c, w4, h4), score));
}
restore_region(recon, mi_r, mi_c, &before);
}
}
if let Some(dv) = intrabc_dv {
let mut leaf = encode_intrabc_leaf(mi_r, mi_c, b_size, dv, input, recon, pricing)?;
fix_skip_segment(&mut leaf);
let d = region_distortion_wh(recon, input, mi_r, mi_c, w4, h4);
let score = score_of(d, rate_of(&leaf, 0)?);
let improves = match best.as_ref() {
Some((_, _, s)) => score < *s,
None => true,
};
if improves {
best = Some((leaf, save_region_wh(recon, mi_r, mi_c, w4, h4), score));
}
restore_region(recon, mi_r, mi_c, &before);
}
let (leaf, after, _) = best.expect("at least one tx candidate");
restore_region(recon, mi_r, mi_c, &after);
Ok(leaf)
}
#[derive(Debug, Clone)]
pub(crate) struct PaletteCandY {
pub(crate) colors: Vec<u16>,
pub(crate) map: Vec<u8>,
}
#[cfg(test)]
fn palette_candidate_y(
input: &YuvFrame,
recon: &ReconState,
mi_r: u32,
mi_c: u32,
b_size: usize,
) -> Option<PaletteCandY> {
palette_candidates_y(input, recon, mi_r, mi_c, b_size, 1)
.into_iter()
.next()
}
fn palette_candidates_y(
input: &YuvFrame,
recon: &ReconState,
mi_r: u32,
mi_c: u32,
b_size: usize,
max_cands: usize,
) -> Vec<PaletteCandY> {
use crate::cdf::PALETTE_COLORS;
if recon.bit_depth != 8 || recon.subsampling_x != 1 || recon.subsampling_y != 1 {
return Vec::new();
}
let bw = NUM_4X4_BLOCKS_WIDE[b_size] * 4;
let bh = crate::cdf::NUM_4X4_BLOCKS_HIGH[b_size] * 4;
if !(8..=64).contains(&bw) || !(8..=64).contains(&bh) {
return Vec::new();
}
let (row0, col0) = ((mi_r as usize) * 4, (mi_c as usize) * 4);
if row0 >= recon.height || col0 >= recon.width {
return Vec::new();
}
let os_w = bw.min(recon.width - col0);
let os_h = bh.min(recon.height - row0);
let mut hist = [0u32; 256];
let mut distinct = 0usize;
for i in 0..os_h {
let row = &input.y[(row0 + i) * recon.width + col0..][..os_w];
for &v in row {
if hist[v as usize] == 0 {
distinct += 1;
}
hist[v as usize] += 1;
}
}
if distinct < 2 {
return Vec::new();
}
let color_lists: Vec<Vec<u16>> = if distinct <= PALETTE_COLORS {
vec![(0..256u16).filter(|&c| hist[c as usize] > 0).collect()]
} else if !recon.lossless && distinct <= kmeans_distinct_bound(os_w * os_h) {
kmeans_palette_1d_candidates(&hist, lambda_for(&recon.qp), max_cands)
} else {
return Vec::new();
};
color_lists
.into_iter()
.filter(|colors| colors.len() >= 2)
.map(|colors| {
let mut lut = [0u8; 256];
for (c, &cnt) in hist.iter().enumerate() {
if cnt > 0 {
let mut best = 0usize;
let mut best_d = u32::MAX;
for (idx, &pc) in colors.iter().enumerate() {
let d = (c as i32 - i32::from(pc)).unsigned_abs();
if d < best_d {
best_d = d;
best = idx;
}
}
lut[c] = best as u8;
}
}
let mut map = vec![0u8; bw * bh];
for i in 0..os_h {
let row = &input.y[(row0 + i) * recon.width + col0..][..os_w];
for (j, &v) in row.iter().enumerate() {
map[i * bw + j] = lut[v as usize];
}
}
for i in 0..os_h {
for j in os_w..bw {
map[i * bw + j] = map[i * bw + os_w - 1];
}
}
for i in os_h..bh {
let (head, tail) = map.split_at_mut(i * bw);
tail[..bw].copy_from_slice(&head[(os_h - 1) * bw..][..bw]);
}
PaletteCandY { colors, map }
})
.collect()
}
const KMEANS_MAX_DISTINCT: usize = 64;
fn kmeans_distinct_bound(samples: usize) -> usize {
(samples / 8).min(KMEANS_MAX_DISTINCT)
}
fn kmeans_palette_1d_candidates(hist: &[u32; 256], lambda: u64, max_cands: usize) -> Vec<Vec<u16>> {
use crate::cdf::PALETTE_COLORS;
let values: Vec<(u32, u32)> = (0..256u32)
.filter(|&c| hist[c as usize] > 0)
.map(|c| (c, hist[c as usize]))
.collect();
let total: u64 = values.iter().map(|&(_, w)| u64::from(w)).sum();
let mut scored: Vec<(Vec<u16>, u64)> = Vec::new();
for k in 2..=PALETTE_COLORS {
let mut centroids: Vec<f64> = Vec::with_capacity(k);
let mut acc = 0u64;
let mut vi = 0usize;
for c in 0..k {
let target = (total * (2 * c as u64 + 1)) / (2 * k as u64);
while vi + 1 < values.len() && acc + u64::from(values[vi].1) <= target {
acc += u64::from(values[vi].1);
vi += 1;
}
centroids.push(f64::from(values[vi].0));
}
for _ in 0..8 {
let mut sum = vec![0f64; k];
let mut cnt = vec![0f64; k];
for &(v, w) in &values {
let mut bi = 0usize;
let mut bd = f64::MAX;
for (i, &c) in centroids.iter().enumerate() {
let d = (f64::from(v) - c).abs();
if d < bd {
bd = d;
bi = i;
}
}
sum[bi] += f64::from(v) * f64::from(w);
cnt[bi] += f64::from(w);
}
for i in 0..k {
if cnt[i] > 0.0 {
centroids[i] = sum[i] / cnt[i];
}
}
}
let mut cols: Vec<u16> = centroids
.iter()
.map(|&c| c.round().clamp(0.0, 255.0) as u16)
.collect();
cols.sort_unstable();
cols.dedup();
if cols.len() < 2 {
continue;
}
let mut sse = 0u64;
for &(v, w) in &values {
let d = cols
.iter()
.map(|&c| (i64::from(v) - i64::from(c)).unsigned_abs())
.min()
.unwrap_or(0);
sse += d * d * u64::from(w);
}
let score = sse + lambda * (10 + 8 * cols.len() as u64);
if !scored.iter().any(|(c, _)| *c == cols) {
scored.push((cols, score));
}
}
scored.sort_by_key(|&(_, s)| s);
scored.truncate(max_cands);
scored.into_iter().map(|(cols, _)| cols).collect()
}
#[derive(Debug, Clone)]
pub(crate) struct PaletteCandUv {
pub(crate) colors_u: Vec<u16>,
pub(crate) colors_v: Vec<u16>,
pub(crate) map: Vec<u8>,
}
fn palette_candidates_uv(
input: &YuvFrame,
recon: &ReconState,
mi_r: u32,
mi_c: u32,
b_size: usize,
max_cands: usize,
) -> Vec<PaletteCandUv> {
use crate::cdf::PALETTE_COLORS;
if recon.bit_depth != 8 || recon.subsampling_x != 1 || recon.subsampling_y != 1 {
return Vec::new();
}
let bw = NUM_4X4_BLOCKS_WIDE[b_size] * 4;
let bh = crate::cdf::NUM_4X4_BLOCKS_HIGH[b_size] * 4;
if !(8..=64).contains(&bw) || !(8..=64).contains(&bh) {
return Vec::new();
}
let (row0, col0) = ((mi_r as usize) * 4, (mi_c as usize) * 4);
if row0 >= recon.height || col0 >= recon.width {
return Vec::new();
}
let (cbw, cbh) = (bw / 2, bh / 2);
let (crow0, ccol0) = (row0 / 2, col0 / 2);
let cw = recon.chroma_w;
let os_cw = cbw.min(cw - ccol0);
let os_ch = cbh.min(recon.chroma_h - crow0);
let mut weights: std::collections::BTreeMap<(u16, u16), u32> =
std::collections::BTreeMap::new();
for i in 0..os_ch {
for j in 0..os_cw {
let off = (crow0 + i) * cw + (ccol0 + j);
let p = (input.u[off], input.v[off]);
*weights.entry(p).or_insert(0) += 1;
if weights.len() > kmeans_distinct_bound(os_cw * os_ch).max(PALETTE_COLORS) {
return Vec::new();
}
}
}
if weights.len() < 2 {
return Vec::new();
}
let pair_lists: Vec<Vec<(u16, u16)>> = if weights.len() <= PALETTE_COLORS {
vec![weights.keys().copied().collect()]
} else if !recon.lossless {
kmeans_palette_2d_candidates(&weights, lambda_for(&recon.qp), max_cands)
} else {
return Vec::new();
};
pair_lists
.into_iter()
.filter(|pairs| pairs.len() >= 2)
.map(|mut pairs| {
pairs.sort_unstable();
let mut map = vec![0u8; cbw * cbh];
for i in 0..os_ch {
for j in 0..os_cw {
let off = (crow0 + i) * cw + (ccol0 + j);
let p = (i64::from(input.u[off]), i64::from(input.v[off]));
let mut bi = 0usize;
let mut bd = i64::MAX;
for (idx, &(pu, pv)) in pairs.iter().enumerate() {
let d = (p.0 - i64::from(pu)).pow(2) + (p.1 - i64::from(pv)).pow(2);
if d < bd {
bd = d;
bi = idx;
}
}
map[i * cbw + j] = bi as u8;
}
}
for i in 0..os_ch {
for j in os_cw..cbw {
map[i * cbw + j] = map[i * cbw + os_cw - 1];
}
}
for i in os_ch..cbh {
let (head, tail) = map.split_at_mut(i * cbw);
tail[..cbw].copy_from_slice(&head[(os_ch - 1) * cbw..][..cbw]);
}
PaletteCandUv {
colors_u: pairs.iter().map(|&(u, _)| u).collect(),
colors_v: pairs.iter().map(|&(_, v)| v).collect(),
map,
}
})
.collect()
}
fn kmeans_palette_2d_candidates(
weights: &std::collections::BTreeMap<(u16, u16), u32>,
lambda: u64,
max_cands: usize,
) -> Vec<Vec<(u16, u16)>> {
use crate::cdf::PALETTE_COLORS;
let values: Vec<((f64, f64), u32)> = weights
.iter()
.map(|(&(u, v), &w)| ((f64::from(u), f64::from(v)), w))
.collect();
let total: u64 = values.iter().map(|&(_, w)| u64::from(w)).sum();
let mut scored: Vec<(Vec<(u16, u16)>, u64)> = Vec::new();
for k in 2..=PALETTE_COLORS {
let mut centroids: Vec<(f64, f64)> = Vec::with_capacity(k);
let mut acc = 0u64;
let mut vi = 0usize;
for c in 0..k {
let target = (total * (2 * c as u64 + 1)) / (2 * k as u64);
while vi + 1 < values.len() && acc + u64::from(values[vi].1) <= target {
acc += u64::from(values[vi].1);
vi += 1;
}
centroids.push(values[vi].0);
}
for _ in 0..8 {
let mut sum = vec![(0f64, 0f64); k];
let mut cnt = vec![0f64; k];
for &((u, v), w) in &values {
let mut bi = 0usize;
let mut bd = f64::MAX;
for (i, &(cu, cv)) in centroids.iter().enumerate() {
let d = (u - cu) * (u - cu) + (v - cv) * (v - cv);
if d < bd {
bd = d;
bi = i;
}
}
sum[bi].0 += u * f64::from(w);
sum[bi].1 += v * f64::from(w);
cnt[bi] += f64::from(w);
}
for i in 0..k {
if cnt[i] > 0.0 {
centroids[i] = (sum[i].0 / cnt[i], sum[i].1 / cnt[i]);
}
}
}
let mut cols: Vec<(u16, u16)> = centroids
.iter()
.map(|&(u, v)| {
(
u.round().clamp(0.0, 255.0) as u16,
v.round().clamp(0.0, 255.0) as u16,
)
})
.collect();
cols.sort_unstable();
cols.dedup();
if cols.len() < 2 {
continue;
}
let mut sse = 0u64;
for &((u, v), w) in &values {
let d = cols
.iter()
.map(|&(cu, cv)| {
let du = (u - f64::from(cu)).abs() as u64;
let dv = (v - f64::from(cv)).abs() as u64;
du * du + dv * dv
})
.min()
.unwrap_or(0);
sse += d * u64::from(w);
}
let score = sse + lambda * (10 + 16 * cols.len() as u64);
if !scored.iter().any(|(c, _)| *c == cols) {
scored.push((cols, score));
}
}
scored.sort_by_key(|&(_, s)| s);
scored.truncate(max_cands);
scored.into_iter().map(|(cols, _)| cols).collect()
}
pub(crate) fn intrabc_dv_valid(
mi_r: u32,
mi_c: u32,
b_size: usize,
dv_r: i32,
dv_c: i32,
mi_rows: u32,
mi_cols: u32,
) -> bool {
const INTRABC_DELAY_SB64: i64 = 4;
let bw = (NUM_4X4_BLOCKS_WIDE[b_size] * 4) as i64;
let bh = bw; if (i64::from(dv_r) * 8).abs() >= (1 << 14) || (i64::from(dv_c) * 8).abs() >= (1 << 14) {
return false;
}
let src_top = i64::from(mi_r) * 4 + i64::from(dv_r);
let src_left = i64::from(mi_c) * 4 + i64::from(dv_c);
let src_bottom = src_top + bh;
let src_right = src_left + bw;
if src_top < 0
|| src_left < 0
|| src_bottom > i64::from(mi_rows) * 4
|| src_right > i64::from(mi_cols) * 4
{
return false;
}
let active_sb_row = (i64::from(mi_r) * 4) / 64;
let active_sb64_col = (i64::from(mi_c) * 4) >> 6;
let src_sb_row = (src_bottom - 1) / 64;
let src_sb64_col = (src_right - 1) >> 6;
let total_sb64_per_row = ((i64::from(mi_cols) - 1) >> 4) + 1;
let active_sb64 = active_sb_row * total_sb64_per_row + active_sb64_col;
let src_sb64 = src_sb_row * total_sb64_per_row + src_sb64_col;
if src_sb64 >= active_sb64 - INTRABC_DELAY_SB64 {
return false;
}
let gradient = 1 + INTRABC_DELAY_SB64; let wf_offset = gradient * (active_sb_row - src_sb_row);
if src_sb_row > active_sb_row
|| src_sb64_col >= active_sb64_col - INTRABC_DELAY_SB64 + wf_offset
{
return false;
}
true
}
fn intrabc_beneficial(input: &YuvFrame) -> bool {
let (w, h) = (input.width as usize, input.height as usize);
let cw = w / 2;
let mi_rows = 2 * ((h as u32 + 7) >> 3);
let mi_cols = 2 * ((w as u32 + 7) >> 3);
let fnv = |hash: &mut u64, v: u16| {
*hash ^= u64::from(v);
*hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
};
let (sb_rows, sb_cols) = (h / 64, w / 64);
if sb_rows * sb_cols >= 2 {
let mut tiles: Vec<(usize, usize, u64)> = Vec::new();
for sbr in 0..sb_rows {
for sbc in 0..sb_cols {
let mut hash = 0xcbf2_9ce4_8422_2325u64;
for i in 0..64 {
for j in 0..64 {
fnv(&mut hash, input.y[(sbr * 64 + i) * w + sbc * 64 + j]);
}
}
for i in 0..32 {
for j in 0..32 {
let off = (sbr * 32 + i) * cw + sbc * 32 + j;
fnv(&mut hash, input.u[off]);
fnv(&mut hash, input.v[off]);
}
}
tiles.push((sbr, sbc, hash));
}
}
for (i, &(r1, c1, h1)) in tiles.iter().enumerate() {
for &(r0, c0, h0) in &tiles[..i] {
if h0 == h1
&& intrabc_dv_valid(
(r1 * 16) as u32,
(c1 * 16) as u32,
BLOCK_64X64,
(r0 as i32 - r1 as i32) * 64,
(c0 as i32 - c1 as i32) * 64,
mi_rows,
mi_cols,
)
{
return true;
}
}
}
}
let (cell_rows, cell_cols) = (h / 16, w / 16);
let total_cells = cell_rows * cell_cols;
if total_cells < 2 * INTRABC_GATE_MIN_CELLS {
return false;
}
let mut buckets: std::collections::HashMap<u64, Vec<(usize, usize)>> =
std::collections::HashMap::new();
let mut matched = 0usize;
for cr in 0..cell_rows {
for cc in 0..cell_cols {
let (py, px) = (cr * 16, cc * 16);
let first = input.y[py * w + px];
let mut flat = true;
let mut hash = 0xcbf2_9ce4_8422_2325u64;
for i in 0..16 {
for j in 0..16 {
let v = input.y[(py + i) * w + px + j];
flat &= v == first;
fnv(&mut hash, v);
}
}
if flat {
continue;
}
for i in 0..8 {
for j in 0..8 {
let off = (py / 2 + i) * cw + px / 2 + j;
fnv(&mut hash, input.u[off]);
fnv(&mut hash, input.v[off]);
}
}
let bucket = buckets.entry(hash).or_default();
if bucket.iter().take(16).any(|&(sy, sx)| {
intrabc_dv_valid(
(py / 4) as u32,
(px / 4) as u32,
crate::cdf::BLOCK_16X16,
sy as i32 - py as i32,
sx as i32 - px as i32,
mi_rows,
mi_cols,
)
}) {
matched += 1;
}
bucket.push((py, px));
}
}
matched >= INTRABC_GATE_MIN_CELLS && matched * INTRABC_GATE_CELL_FRACTION >= total_cells
}
const INTRABC_GATE_MIN_CELLS: usize = 4;
const INTRABC_GATE_CELL_FRACTION: usize = 64;
fn intrabc_best_dv(
input: &YuvFrame,
recon: &ReconState,
mi_r: u32,
mi_c: u32,
b_size: usize,
) -> Option<(i32, i32)> {
let n = NUM_4X4_BLOCKS_WIDE[b_size] * 4;
let (row0, col0) = ((mi_r as usize) * 4, (mi_c as usize) * 4);
if row0 + n > recon.height || col0 + n > recon.width {
return None;
}
let mut cands: Vec<(i32, i32)> = Vec::new();
if let Some(tier) = crate::encoder::dv_hash::dv_hash_size_idx(n) {
let (hash, uniform) =
crate::encoder::dv_hash::hash_block_direct(&input.y, recon.width, row0, col0, n);
if !uniform {
cands.extend(recon.dv_hash.candidates(hash, tier, row0, col0));
}
}
for k in 1..=3i32 {
for &(dr, dc) in &[(0, -64 * k), (-64 * k, 0), (-64 * k, -64 * k)] {
cands.push((dr, dc));
}
if n < 64 {
let s = (n as i32) * k;
for &(dr, dc) in &[(0, -s), (-s, 0), (-s, -s)] {
cands.push((dr, dc));
}
}
}
let mut best: Option<((i32, i32), u64)> = None;
for (dr, dc) in cands {
if !intrabc_dv_valid(mi_r, mi_c, b_size, dr, dc, recon.mi_rows, recon.mi_cols) {
continue;
}
let (sr, sc) = ((row0 as i32 + dr) as usize, (col0 as i32 + dc) as usize);
let mut ssd = 0u64;
for i in 0..n {
for j in 0..n {
let d = i64::from(input.y[(row0 + i) * recon.width + col0 + j])
- i64::from(recon.y[(sr + i) * recon.width + sc + j]);
ssd += (d * d) as u64;
}
}
if best.as_ref().map_or(true, |&(_, s)| ssd < s) {
best = Some(((dr, dc), ssd));
}
}
best.map(|(dv, _)| dv)
}
fn encode_intrabc_leaf(
mi_r: u32,
mi_c: u32,
b_size: usize,
dv: (i32, i32),
input: &YuvFrame,
recon: &mut ReconState,
pricing: Option<(&RateTwin, &SyntaxFrameParams)>,
) -> Result<SyntaxBlock, Error> {
use crate::cdf::inter_tx_type_set;
let n = NUM_4X4_BLOCKS_WIDE[b_size] * 4;
let (row0, col0) = ((mi_r as usize) * 4, (mi_c as usize) * 4);
let width = recon.width;
let lossless = recon.lossless;
let qp = recon.qp;
let mut tu_fork = match (&pricing, lossless) {
(Some((twin, _)), false) if !recon.search.tu_proxy_pricing => Some(twin.tu_fork()),
_ => None,
};
let tu_ctx = pricing.map(|(_, params)| TuCtx {
params,
mi_row: mi_r,
mi_col: mi_c,
mi_size: b_size,
base_x: col0 as u32,
base_y: row0 as u32,
is_inter: true,
segment_id: 0,
y_mode: 0,
use_filter_intra: false,
filter_intra_mode: None,
});
let (dv_r, dv_c) = dv;
let (src_row0, src_col0) = ((row0 as i32 + dv_r) as usize, (col0 as i32 + dv_c) as usize);
let luma_tx = if lossless {
TX_4X4
} else {
MAX_TX_SIZE_RECT[b_size]
};
let (ltw, lth) = (TX_WIDTH[luma_tx], TX_HEIGHT[luma_tx]);
let mut residual_quant: Vec<Vec<i32>> = Vec::new();
let mut luma_tx_types: Vec<u8> = Vec::new();
let tu_cols = n / ltw;
let mut tu_type_grid = vec![DCT_DCT as u8; tu_cols * (n / lth)];
let mut ty = 0usize;
while ty < n {
let mut tx = 0usize;
while tx < n {
let (tr, tc) = (row0 + ty, col0 + tx);
let mut pred = vec![0u16; ltw * lth];
for i in 0..lth {
for j in 0..ltw {
pred[i * ltw + j] = recon.y[(src_row0 + ty + i) * width + src_col0 + tx + j];
}
}
let (q, tt) = if lossless {
(
residual_tx(
&input.y,
&mut recon.y,
width,
tr,
tc,
luma_tx,
&pred,
0,
lossless,
DCT_DCT,
&qp,
),
DCT_DCT as u8,
)
} else {
crate::encoder::inter_frame::residual_tx_search_luma_inter(
&input.y,
&mut recon.y,
width,
tr,
tc,
luma_tx,
&pred,
&qp,
match (&mut tu_fork, &tu_ctx) {
(Some(f), Some(c)) => Some((f, c)),
_ => None,
},
)?
};
tu_bd_stamp(&mut recon.bd, 0, tc, tr, ltw, lth);
residual_quant.push(q);
luma_tx_types.push(tt);
tu_type_grid[(ty / lth) * tu_cols + tx / ltw] = tt;
tx += ltw;
}
ty += lth;
}
let (ssx, ssy) = (
u32::from(recon.subsampling_x),
u32::from(recon.subsampling_y),
);
let (cn_w, cn_h) = (n >> ssx, n >> ssy);
let (crow0, ccol0) = (row0 >> ssy, col0 >> ssx);
let (csrc_row0, csrc_col0) = (
((row0 as i32 + dv_r) >> ssy) as usize,
((col0 as i32 + dv_c) >> ssx) as usize,
);
let chroma_tx = if lossless {
TX_4X4
} else {
get_tx_size(1, luma_tx, b_size, recon.subsampling_x, recon.subsampling_y).unwrap_or(TX_4X4)
};
let (ctw, cth) = (TX_WIDTH[chroma_tx], TX_HEIGHT[chroma_tx]);
let chroma_set = inter_tx_type_set(
tx_size_sqr_index(chroma_tx) as u32,
TX_SIZE_SQR_UP[chroma_tx] as u32,
false,
);
let cw = recon.chroma_w;
for plane in 1..usize::from(recon.num_planes) {
let mut ty = 0usize;
while ty < cn_h {
let mut tx = 0usize;
while tx < cn_w {
let (tr, tc) = (crow0 + ty, ccol0 + tx);
let chroma_tt = if lossless || TX_SIZE_SQR_UP[chroma_tx] > crate::cdf::TX_32X32 {
DCT_DCT
} else {
let lifted_x = ((tc >> 2) << (2 + ssx)).saturating_sub(col0);
let lifted_y = ((tr >> 2) << (2 + ssy)).saturating_sub(row0);
let luma_tt =
tu_type_grid[(lifted_y / lth) * tu_cols + lifted_x / ltw] as usize;
if is_tx_type_in_set(true, chroma_set, luma_tt) {
luma_tt
} else {
DCT_DCT
}
};
let src_plane: &[u16] = if plane == 1 { &recon.u } else { &recon.v };
let mut pred = vec![0u16; ctw * cth];
for i in 0..cth {
for j in 0..ctw {
pred[i * ctw + j] =
src_plane[(csrc_row0 + ty + i) * cw + csrc_col0 + tx + j];
}
}
let plane_buf = if plane == 1 {
&mut recon.u
} else {
&mut recon.v
};
let q = residual_tx(
if plane == 1 { &input.u } else { &input.v },
plane_buf,
cw,
tr,
tc,
chroma_tx,
&pred,
plane as u8,
lossless,
chroma_tt,
&qp,
);
tu_bd_stamp(&mut recon.bd, plane, tc, tr, ctw, cth);
residual_quant.push(q);
tx += ctw;
}
ty += cth;
}
}
let all_zero = residual_quant.iter().all(|tu| tu.iter().all(|&q| q == 0));
let skip = u8::from(all_zero);
if all_zero {
residual_quant.clear();
luma_tx_types.clear();
}
if luma_tx_types.iter().all(|&t| t == DCT_DCT as u8) {
luma_tx_types.clear();
}
let mut block = SyntaxBlock::skip_leaf(0, None);
block.skip = skip;
block.intrabc_mv = Some([dv_r * 8, dv_c * 8]);
block.residual_quant = residual_quant.into();
block.residual_tx_type = luma_tx_types;
if !lossless && skip == 0 {
block.var_tx_trees = vec![crate::encoder::inter_frame::uniform_var_tx_tree(
MAX_TX_SIZE_RECT[b_size],
0,
)];
}
Ok(block)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode_leaf_with_tx(
mi_r: u32,
mi_c: u32,
b_size: usize,
luma_tx: usize,
input: &YuvFrame,
recon: &mut ReconState,
palette_y: Option<&PaletteCandY>,
palette_uv: Option<&PaletteCandUv>,
pricing: Option<(&RateTwin, &SyntaxFrameParams)>,
) -> Result<SyntaxBlock, Error> {
let bw = NUM_4X4_BLOCKS_WIDE[b_size] * 4;
let bh = crate::cdf::NUM_4X4_BLOCKS_HIGH[b_size] * 4;
let row0 = (mi_r as usize) * 4;
let col0 = (mi_c as usize) * 4;
let width = recon.width;
let lossless = recon.lossless;
let qp = recon.qp;
let (y_mode, angle_delta_y, filter_intra_mode) = match palette_y {
Some(_) => (DC_PRED as u8, 0i8, None),
None => pick_y_mode(recon, input, row0, col0, bw, bh),
};
let mut tu_fork = match (&pricing, lossless) {
(Some((twin, _)), false) if !recon.search.tu_proxy_pricing => Some(twin.tu_fork()),
_ => None,
};
let tu_ctx = pricing.map(|(_, params)| TuCtx {
params,
mi_row: mi_r,
mi_col: mi_c,
mi_size: b_size,
base_x: col0 as u32,
base_y: row0 as u32,
is_inter: false,
segment_id: 0,
y_mode,
use_filter_intra: filter_intra_mode.is_some(),
filter_intra_mode,
});
let tx_shortlist: Option<Vec<usize>> = recon.search.prune_tx_types.then(|| {
let mut l = vec![DCT_DCT];
let default = if palette_y.is_some() || filter_intra_mode.is_some() {
DCT_DCT
} else {
MODE_TO_TXFM
.get(y_mode as usize)
.copied()
.unwrap_or(DCT_DCT)
};
if !l.contains(&default) {
l.push(default);
}
if recon.allow_screen_content_tools {
l.push(crate::cdf::IDTX);
}
l
});
let (ltw, lth) = (TX_WIDTH[luma_tx], TX_HEIGHT[luma_tx]);
let os_w = bw.min(width - col0);
let os_h = bh.min(recon.height - row0);
let mut residual_quant: Vec<Vec<i32>> = Vec::new();
let mut luma_tx_types: Vec<u8> = Vec::new();
let mut ty = 0usize;
while ty < bh {
let mut tx = 0usize;
while tx < bw {
if ty >= os_h || tx >= os_w {
tx += ltw;
continue;
}
let (tr, tc) = (row0 + ty, col0 + tx);
let pred = match palette_y {
Some(p) => {
let mut buf = vec![0u16; ltw * lth];
for i in 0..lth {
for j in 0..ltw {
let idx = p.map[(ty + i) * bw + (tx + j)] as usize;
buf[i * ltw + j] = p.colors[idx];
}
}
buf
}
None => predict_tu(
recon,
0,
tc,
tr,
ltw,
lth,
y_mode as usize,
angle_delta_y as i32,
filter_intra_mode.map(|f| f as usize),
),
};
let (q, tt) = if lossless {
(
residual_tx_avail(
&input.y,
&mut recon.y,
width,
tr,
tc,
luma_tx,
&pred,
0,
lossless,
DCT_DCT,
&qp,
os_w - tx,
os_h - ty,
crate::encoder::forward_quantize::QUANT_ROUND_HALF,
),
DCT_DCT as u8,
)
} else {
residual_tx_search_luma_avail(
&input.y,
&mut recon.y,
width,
tr,
tc,
luma_tx,
&pred,
&qp,
match (&mut tu_fork, &tu_ctx) {
(Some(f), Some(c)) => Some((f, c)),
_ => None,
},
os_w - tx,
os_h - ty,
tx_shortlist.as_deref(),
recon.search.quant_round_64,
)?
};
tu_bd_stamp(&mut recon.bd, 0, tc, tr, ltw, lth);
residual_quant.push(q);
luma_tx_types.push(tt);
tx += ltw;
}
ty += lth;
}
let bw4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let bh4 = crate::cdf::NUM_4X4_BLOCKS_HIGH[b_size] as u32;
let chroma_y_edge = bh4 == 1 && recon.subsampling_y != 0 && (mi_r & 1) == 0;
let chroma_x_edge = bw4 == 1 && recon.subsampling_x != 0 && (mi_c & 1) == 0;
let has_chroma = recon.num_planes > 1 && !chroma_y_edge && !chroma_x_edge;
let mut uv_mode: Option<u8> = None;
let mut angle_delta_uv = 0i8;
let mut cfl_alpha: Option<(i8, i8)> = None;
if has_chroma {
let (crow0, ccol0) = recon.chroma_origin(mi_r, mi_c);
let max_luma_w = col0 + bw;
let max_luma_h = row0 + bh;
let plane_sz = crate::cdf::get_plane_residual_size(
b_size,
1,
recon.subsampling_x,
recon.subsampling_y,
)
.unwrap_or(BLOCK_4X4);
let cfl_allowed = if os_w < bw || os_h < bh {
false
} else if lossless {
plane_sz == BLOCK_4X4
} else {
bw.max(bh) <= 32
};
let chroma_tx = if lossless {
TX_4X4
} else {
get_tx_size(1, luma_tx, b_size, recon.subsampling_x, recon.subsampling_y)
.unwrap_or(TX_4X4)
};
let (ctw, cth) = (TX_WIDTH[chroma_tx], TX_HEIGHT[chroma_tx]);
let cbw = NUM_4X4_BLOCKS_WIDE[plane_sz] * 4;
let cbh = crate::cdf::NUM_4X4_BLOCKS_HIGH[plane_sz] * 4;
let (m, delta_uv, alpha) = match palette_uv {
Some(_) => (DC_PRED as u8, 0i8, None),
None => pick_uv_mode(
recon,
input,
crow0,
ccol0,
cbw,
cbh,
cfl_allowed,
bw.min(bh),
max_luma_w,
max_luma_h,
(y_mode, angle_delta_y),
),
};
uv_mode = Some(m);
angle_delta_uv = delta_uv;
cfl_alpha = alpha;
let is_cfl = m as usize == UV_CFL_PRED;
let chroma_tx_type = chroma_tx_type_for(m, chroma_tx, lossless);
let cw = recon.chroma_w;
for plane in 1..usize::from(recon.num_planes) {
let alpha_p = match (is_cfl, plane, alpha) {
(true, 1, Some((au, _))) => Some(au),
(true, 2, Some((_, av))) => Some(av),
_ => None,
};
let os_cw = cbw.min(cw - ccol0);
let os_ch = cbh.min(recon.chroma_h - crow0);
let mut ty = 0usize;
while ty < cbh {
let mut tx = 0usize;
while tx < cbw {
if ty >= os_ch || tx >= os_cw {
tx += ctw;
continue;
}
let (tr, tc) = (crow0 + ty, ccol0 + tx);
let pred = if let Some(p) = palette_uv {
let colors = if plane == 1 { &p.colors_u } else { &p.colors_v };
let mut buf = vec![0u16; ctw * cth];
for i in 0..cth {
for j in 0..ctw {
let idx = p.map[(ty + i) * cbw + (tx + j)] as usize;
buf[i * ctw + j] = colors[idx];
}
}
buf
} else if is_cfl {
let dc = predict_tu(recon, plane, tc, tr, ctw, cth, DC_PRED, 0, None);
cfl_layer(
&dc,
&recon.y,
recon.width,
tc,
tr,
ctw,
cth,
alpha_p.unwrap_or(0),
max_luma_w,
max_luma_h,
recon.subsampling_x,
recon.subsampling_y,
recon.bit_depth,
)
} else {
predict_tu(
recon,
plane,
tc,
tr,
ctw,
cth,
m as usize,
delta_uv as i32,
None,
)
};
let plane_buf = if plane == 1 {
&mut recon.u
} else {
&mut recon.v
};
let q = residual_tx_avail(
if plane == 1 { &input.u } else { &input.v },
plane_buf,
cw,
tr,
tc,
chroma_tx,
&pred,
plane as u8,
lossless,
chroma_tx_type,
&qp,
os_cw - tx,
os_ch - ty,
recon.search.quant_round_64,
);
tu_bd_stamp(&mut recon.bd, plane, tc, tr, ctw, cth);
residual_quant.push(q);
tx += ctw;
}
ty += cth;
}
}
}
let all_zero = residual_quant.iter().all(|tu| tu.iter().all(|&q| q == 0));
let skip = u8::from(all_zero);
if all_zero {
residual_quant.clear();
luma_tx_types.clear();
}
if luma_tx_types.iter().all(|&t| t == DCT_DCT as u8) {
luma_tx_types.clear();
}
let (cfl_alpha_u, cfl_alpha_v) = match cfl_alpha {
Some((au, av)) => (Some(au), Some(av)),
None => (None, None),
};
let mut palette = crate::encoder::partition_tree::SyntaxPalette::default();
if let Some(p) = palette_y {
palette.size_y = p.colors.len() as u8;
palette.colors_y[..p.colors.len()].copy_from_slice(&p.colors);
palette.color_map_y = p.map.clone();
}
if let Some(p) = palette_uv {
palette.size_uv = p.colors_u.len() as u8;
palette.colors_u[..p.colors_u.len()].copy_from_slice(&p.colors_u);
palette.colors_v[..p.colors_v.len()].copy_from_slice(&p.colors_v);
palette.delta_encode_v = false;
palette.color_map_uv = p.map.clone();
}
Ok(SyntaxBlock {
skip,
segment_id: 0,
cdef_idx: 0,
reduced_delta_q_index: 0,
reduced_delta_lf: [0; crate::cdf::FRAME_LF_COUNT],
intrabc_mv: None,
y_mode,
uv_mode,
angle_delta_y,
angle_delta_uv,
cfl_alpha_u,
cfl_alpha_v,
use_filter_intra: u8::from(filter_intra_mode.is_some()),
filter_intra_mode,
palette,
residual_quant: residual_quant.into(),
tx_size: if !lossless && b_size > BLOCK_4X4 {
Some(luma_tx as u8)
} else {
None
},
residual_tx_type: luma_tx_types,
var_tx_trees: Vec::new(),
inter: None,
})
}
pub(crate) struct RegionSnapshot {
w: usize,
h: usize,
cw: usize,
ch: usize,
y: Vec<u16>,
u: Vec<u16>,
v: Vec<u16>,
bd: BlockDecodedMirror,
}
pub(crate) fn save_region(recon: &ReconState, r: u32, c: u32, n4: usize) -> RegionSnapshot {
save_region_wh(recon, r, c, n4, n4)
}
pub(crate) fn save_region_wh(
recon: &ReconState,
r: u32,
c: u32,
w4: usize,
h4: usize,
) -> RegionSnapshot {
let (row0, col0) = ((r as usize) * 4, (c as usize) * 4);
let (crow0, ccol0) = recon.chroma_origin(r, c);
let w = (w4 * 4).min(recon.width - col0);
let h = (h4 * 4).min(recon.height - row0);
let (cw, ch) = if recon.num_planes > 1 {
(
((w4 * 4) >> recon.subsampling_x).min(recon.chroma_w - ccol0),
((h4 * 4) >> recon.subsampling_y).min(recon.chroma_h - crow0),
)
} else {
(0, 0)
};
let mut y = vec![0u16; w * h];
let mut u = vec![0u16; cw * ch];
let mut v = vec![0u16; cw * ch];
for i in 0..h {
y[i * w..(i + 1) * w].copy_from_slice(&recon.y[(row0 + i) * recon.width + col0..][..w]);
}
for i in 0..ch {
u[i * cw..(i + 1) * cw]
.copy_from_slice(&recon.u[(crow0 + i) * recon.chroma_w + ccol0..][..cw]);
v[i * cw..(i + 1) * cw]
.copy_from_slice(&recon.v[(crow0 + i) * recon.chroma_w + ccol0..][..cw]);
}
RegionSnapshot {
w,
h,
cw,
ch,
y,
u,
v,
bd: recon.bd.clone(),
}
}
pub(crate) fn restore_region(recon: &mut ReconState, r: u32, c: u32, snap: &RegionSnapshot) {
let (w, h) = (snap.w, snap.h);
let (cw, ch) = (snap.cw, snap.ch);
let (row0, col0) = ((r as usize) * 4, (c as usize) * 4);
let (crow0, ccol0) = recon.chroma_origin(r, c);
for i in 0..h {
recon.y[(row0 + i) * recon.width + col0..][..w].copy_from_slice(&snap.y[i * w..][..w]);
}
for i in 0..ch {
recon.u[(crow0 + i) * recon.chroma_w + ccol0..][..cw]
.copy_from_slice(&snap.u[i * cw..][..cw]);
recon.v[(crow0 + i) * recon.chroma_w + ccol0..][..cw]
.copy_from_slice(&snap.v[i * cw..][..cw]);
}
recon.bd = snap.bd.clone();
}
pub(crate) fn region_distortion(
recon: &ReconState,
input: &YuvFrame,
r: u32,
c: u32,
n4: usize,
) -> u64 {
region_distortion_wh(recon, input, r, c, n4, n4)
}
pub(crate) fn region_distortion_luma(
recon: &ReconState,
input: &YuvFrame,
r: u32,
c: u32,
n4: usize,
) -> u64 {
let (row0, col0) = ((r as usize) * 4, (c as usize) * 4);
let w = (n4 * 4).min(recon.width.saturating_sub(col0));
let h = (n4 * 4).min(recon.height.saturating_sub(row0));
let mut d = 0u64;
for i in 0..h {
let base = (row0 + i) * recon.width + col0;
for (a, b) in recon.y[base..base + w].iter().zip(&input.y[base..base + w]) {
let diff = i64::from(*a) - i64::from(*b);
d += (diff * diff) as u64;
}
}
d
}
pub(crate) fn region_distortion_wh(
recon: &ReconState,
input: &YuvFrame,
r: u32,
c: u32,
w4: usize,
h4: usize,
) -> u64 {
let (row0, col0) = ((r as usize) * 4, (c as usize) * 4);
let (crow0, ccol0) = recon.chroma_origin(r, c);
let w = (w4 * 4).min(recon.width - col0);
let h = (h4 * 4).min(recon.height - row0);
let (cw, ch) = if recon.num_planes > 1 {
(
((w4 * 4) >> recon.subsampling_x).min(recon.chroma_w - ccol0),
((h4 * 4) >> recon.subsampling_y).min(recon.chroma_h - crow0),
)
} else {
(0, 0)
};
let mut ssd = 0u64;
for i in 0..h {
for j in 0..w {
let idx = (row0 + i) * recon.width + (col0 + j);
let d = i64::from(recon.y[idx]) - i64::from(input.y[idx]);
ssd += (d * d) as u64;
}
}
for i in 0..ch {
for j in 0..cw {
let idx = (crow0 + i) * recon.chroma_w + (ccol0 + j);
let du = i64::from(recon.u[idx]) - i64::from(input.u[idx]);
let dv = i64::from(recon.v[idx]) - i64::from(input.v[idx]);
ssd += (du * du + dv * dv) as u64;
}
}
ssd
}
pub(crate) fn chroma_partition_ok(recon: &ReconState, sub_size: usize) -> bool {
recon.num_planes == 1
|| crate::cdf::get_plane_residual_size(
sub_size,
1,
recon.subsampling_x,
recon.subsampling_y,
)
.is_some()
}
pub(crate) fn lambda_for(qp: &QuantizerParams) -> u64 {
let q = if qp.delta_q_present {
u64::from(qp.current_q_index)
} else {
u64::from(qp.base_q_idx)
};
let l8 = 1 + q * q / 32;
l8 << (2 * u32::from(qp.bit_depth.saturating_sub(8)))
}
pub(crate) fn leaf_rate(block: &SyntaxBlock) -> u64 {
let mut rate = 24u64;
for tu in &block.residual_quant {
for &(_, q) in tu.nonzeros() {
rate += 3 + u64::from(32 - q.unsigned_abs().leading_zeros());
}
}
if let Some(mv) = block.intrabc_mv {
let mag = |v: i32| u64::from(32 - (v.unsigned_abs() / 8 + 1).leading_zeros());
rate += 6 + mag(mv[0]) + mag(mv[1]);
}
for (size, map) in [
(block.palette.size_y, &block.palette.color_map_y),
(block.palette.size_uv, &block.palette.color_map_uv),
] {
if size > 0 {
rate += 10 + 8 * u64::from(size);
let mut transitions = 0u64;
for w in map.windows(2) {
if w[0] != w[1] {
transitions += 1;
}
}
rate += map.len() as u64 / 16 + transitions * 2;
}
}
rate
}
pub(crate) fn tree_rate(node: &SyntaxNode) -> u64 {
match node {
SyntaxNode::Leaf(b) => leaf_rate(b),
SyntaxNode::Split(children) => 4 + children.iter().map(|c| tree_rate(c)).sum::<u64>(),
SyntaxNode::Horz(blocks) | SyntaxNode::Vert(blocks) => {
4 + blocks.iter().map(|b| leaf_rate(b)).sum::<u64>()
}
SyntaxNode::HorzA(blocks)
| SyntaxNode::HorzB(blocks)
| SyntaxNode::VertA(blocks)
| SyntaxNode::VertB(blocks) => 5 + blocks.iter().map(|b| leaf_rate(b)).sum::<u64>(),
SyntaxNode::Horz4(blocks) | SyntaxNode::Vert4(blocks) => {
5 + blocks.iter().map(|b| leaf_rate(b)).sum::<u64>()
}
}
}
pub(crate) struct KeySegDemand<'a> {
pub(crate) mask: &'a [bool],
pub(crate) ll_seg: u8,
}
#[allow(clippy::too_many_arguments)]
fn encode_key_leaf(
r: u32,
c: u32,
b_size: usize,
input: &YuvFrame,
recon: &mut ReconState,
pricing: Option<(&RateTwin, &SyntaxFrameParams)>,
seg: Option<&KeySegDemand<'_>>,
dq_units: i32,
) -> Result<SyntaxBlock, Error> {
let demanded = seg.is_some_and(|d| {
let bh4 = crate::cdf::NUM_4X4_BLOCKS_HIGH[b_size] as u32;
let bw4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let r1 = (r + bh4).min(recon.mi_rows);
let c1 = (c + bw4).min(recon.mi_cols);
(r..r1).any(|rr| (c..c1).any(|cc| d.mask[(rr * recon.mi_cols + cc) as usize]))
});
let mut out = if !demanded {
encode_leaf_sq(r, c, b_size, input, recon, pricing)?
} else {
let ll_seg = seg.expect("demanded implies seg").ll_seg;
let saved_qp = recon.qp;
let saved_lossless = recon.lossless;
recon.qp = QuantizerParams::neutral(0, recon.bit_depth);
recon.lossless = true;
let o = encode_leaf_sq_seg(r, c, b_size, input, recon, pricing, Some(ll_seg));
recon.qp = saved_qp;
recon.lossless = saved_lossless;
o?
};
if dq_units != 0 {
let pending = pricing.is_some_and(|(twin, _)| twin.deltas_pending());
out.reduced_delta_q_index = if pending && !(b_size == BLOCK_64X64 && out.skip == 1) {
dq_units
} else {
0
};
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
fn build_search_tree(
r: u32,
c: u32,
b_size: usize,
input: &YuvFrame,
recon: &mut ReconState,
twin: &mut RateTwin,
params: &SyntaxFrameParams,
model: RateModel,
seg: Option<&KeySegDemand<'_>>,
dq_units: i32,
) -> Result<(SyntaxNode, u64), Error> {
if r >= recon.mi_rows || c >= recon.mi_cols {
return Ok((SyntaxNode::dummy_oob(), 0));
}
let n4 = NUM_4X4_BLOCKS_WIDE[b_size] as u32;
let fully_inside = r + n4 <= recon.mi_rows && c + n4 <= recon.mi_cols;
if b_size == BLOCK_4X4 || (fully_inside && b_size <= recon.search.min_leaf) {
let pricing = (model == RateModel::Twin).then_some((&*twin, params));
let leaf = encode_key_leaf(r, c, b_size, input, recon, pricing, seg, dq_units)?;
let node = SyntaxNode::Leaf(Box::new(leaf));
let cost = twin.commit_subtree(&node, r, c, b_size, params)?;
return Ok((node, cost));
}
let half = n4 >> 1;
let sub = crate::cdf::partition_subsize(crate::cdf::PARTITION_SPLIT, b_size)
.expect("PARTITION_SPLIT subsize exists for every b_size >= BLOCK_8X8");
if !fully_inside {
let has_rows = r + half < recon.mi_rows;
let has_cols = c + half < recon.mi_cols;
let lambda = lambda_for(&recon.qp);
let rect_part = if model != RateModel::Twin {
None
} else if b_size > BLOCK_8X8 && has_cols && !has_rows {
Some(crate::cdf::PARTITION_HORZ)
} else if b_size > BLOCK_8X8 && has_rows && !has_cols {
Some(crate::cdf::PARTITION_VERT)
} else {
None
};
let before = save_region_wh(recon, r, c, n4 as usize, n4 as usize);
let origin = twin.scope(r, c, b_size, params);
let mut rect_best: Option<(SyntaxNode, RegionSnapshot, u64, TwinScope, u64)> = None;
if let Some(part) = rect_part {
if let Some(psub) = crate::cdf::partition_subsize(part, b_size)
.filter(|&p| chroma_partition_ok(recon, p))
{
let mut cost_s = twin.commit_partition_symbol(part, r, c, b_size)?;
let pricing_s = (model == RateModel::Twin).then_some((&*twin, params));
let blk = encode_key_leaf(r, c, psub, input, recon, pricing_s, seg, dq_units)?;
let h_rate = 4 + leaf_rate(&blk);
cost_s += twin.commit_block(&blk, r, c, psub, params)?;
let d = region_distortion_wh(recon, input, r, c, n4 as usize, n4 as usize);
let placeholder = Box::new(SyntaxBlock::skip_leaf(0, None));
let node = if part == crate::cdf::PARTITION_HORZ {
SyntaxNode::Horz([Box::new(blk), placeholder])
} else {
SyntaxNode::Vert([Box::new(blk), placeholder])
};
let score = match model {
RateModel::Twin => score256(d, lambda, cost_s),
RateModel::Heuristic => score256(d, lambda, h_rate * 256),
};
rect_best = Some((
node,
save_region_wh(recon, r, c, n4 as usize, n4 as usize),
score,
twin.scope(r, c, b_size, params),
cost_s,
));
restore_region(recon, r, c, &before);
twin.restore(&origin);
}
}
let mut cost = twin.commit_partition_symbol(crate::cdf::PARTITION_SPLIT, r, c, b_size)?;
let (nw, c0) =
build_search_tree(r, c, sub, input, recon, twin, params, model, seg, dq_units)?;
let (ne, c1) = build_search_tree(
r,
c + half,
sub,
input,
recon,
twin,
params,
model,
seg,
dq_units,
)?;
let (sw, c2) = build_search_tree(
r + half,
c,
sub,
input,
recon,
twin,
params,
model,
seg,
dq_units,
)?;
let (se, c3) = build_search_tree(
r + half,
c + half,
sub,
input,
recon,
twin,
params,
model,
seg,
dq_units,
)?;
cost += c0 + c1 + c2 + c3;
let children = [Box::new(nw), Box::new(ne), Box::new(sw), Box::new(se)];
if let Some((node, after, rect_score, twin_s, rect_cost)) = rect_best {
let d_b = region_distortion_wh(recon, input, r, c, n4 as usize, n4 as usize);
let r_b = match model {
RateModel::Twin => cost,
RateModel::Heuristic => {
(children.iter().map(|ch| tree_rate(ch)).sum::<u64>() + 4) * 256
}
};
if rect_score <= score256(d_b, lambda, r_b) {
restore_region(recon, r, c, &after);
twin.restore(&twin_s);
return Ok((node, rect_cost));
}
}
return Ok((SyntaxNode::Split(children), cost));
}
let lambda = lambda_for(&recon.qp);
let before = save_region(recon, r, c, n4 as usize);
let pricing = (model == RateModel::Twin).then_some((&*twin, params));
let leaf = encode_key_leaf(r, c, b_size, input, recon, pricing, seg, dq_units)?;
let leaf_is_skip = leaf.skip == 1;
let node_a = SyntaxNode::Leaf(Box::new(leaf));
let d_a = region_distortion(recon, input, r, c, n4 as usize);
let origin = twin.scope(r, c, b_size, params);
let cost_a = twin.commit_subtree(&node_a, r, c, b_size, params)?;
if recon.search.skip_leaf_early_exit && leaf_is_skip {
return Ok((node_a, cost_a));
}
if recon.search.split_early_term_k64 > 0 {
let n = (n4 as u64) * 4;
let samples = n * n
+ if recon.num_planes > 1 {
2 * (n >> recon.subsampling_x) * (n >> recon.subsampling_y)
} else {
0
};
let step = u64::from(crate::cdf::get_ac_quant(&recon.qp, 0, 0).unsigned_abs());
let thresh = samples * step * step * u64::from(recon.search.split_early_term_k64) / 65536;
if d_a <= thresh {
return Ok((node_a, cost_a));
}
}
let twin_a = twin.scope(r, c, b_size, params);
twin.restore(&origin);
let after_a = save_region(recon, r, c, n4 as usize);
restore_region(recon, r, c, &before);
let score_a = {
let r_a = match model {
RateModel::Twin => cost_a,
RateModel::Heuristic => {
let lr = match &node_a {
SyntaxNode::Leaf(b) => leaf_rate(b),
_ => unreachable!("candidate A is always a leaf"),
};
lr * 256
}
};
score256(d_a, lambda, r_a)
};
let mut best: (SyntaxNode, RegionSnapshot, u64, TwinScope, u64) =
(node_a, after_a, score_a, twin_a, cost_a);
if model == RateModel::Twin && b_size >= crate::cdf::BLOCK_16X16 && recon.search.rect_partitions
{
for part in [crate::cdf::PARTITION_HORZ, crate::cdf::PARTITION_VERT] {
let Some(psub) = crate::cdf::partition_subsize(part, b_size) else {
continue;
};
if !chroma_partition_ok(recon, psub) {
continue;
}
let cells: [(u32, u32); 2] = if part == crate::cdf::PARTITION_HORZ {
[(r, c), (r + half, c)]
} else {
[(r, c), (r, c + half)]
};
let mut cost_s = twin.commit_partition_symbol(part, r, c, b_size)?;
let mut h_rate = 4u64;
let mut blocks: Vec<SyntaxBlock> = Vec::with_capacity(2);
for &(rr, cc) in &cells {
let pricing_s = (model == RateModel::Twin).then_some((&*twin, params));
let blk = encode_key_leaf(rr, cc, psub, input, recon, pricing_s, seg, dq_units)?;
h_rate += leaf_rate(&blk);
cost_s += twin.commit_block(&blk, rr, cc, psub, params)?;
blocks.push(blk);
}
let d = region_distortion(recon, input, r, c, n4 as usize);
let mut it = blocks.into_iter();
let pair = [
Box::new(it.next().expect("two leaves")),
Box::new(it.next().expect("two leaves")),
];
let node = if part == crate::cdf::PARTITION_HORZ {
SyntaxNode::Horz(pair)
} else {
SyntaxNode::Vert(pair)
};
let score = match model {
RateModel::Twin => score256(d, lambda, cost_s),
RateModel::Heuristic => score256(d, lambda, h_rate * 256),
};
if score < best.2 {
best = (
node,
save_region(recon, r, c, n4 as usize),
score,
twin.scope(r, c, b_size, params),
cost_s,
);
}
twin.restore(&origin);
restore_region(recon, r, c, &before);
}
}
let mut cost_b = twin.commit_partition_symbol(crate::cdf::PARTITION_SPLIT, r, c, b_size)?;
let quadrants = [(r, c), (r, c + half), (r + half, c), (r + half, c + half)];
let mut children: Vec<Box<SyntaxNode>> = Vec::with_capacity(4);
let mut partial_d = 0u64;
let mut partial_rate = 0u64;
let mut twin_cost = 0u64;
for (qi, &(qr, qc)) in quadrants.iter().enumerate() {
let (node, cost) = build_search_tree(
qr, qc, sub, input, recon, twin, params, model, seg, dq_units,
)?;
twin_cost += cost;
partial_rate += match model {
RateModel::Twin => cost,
RateModel::Heuristic => tree_rate(&node) * 256,
};
children.push(Box::new(node));
if qi == 3 {
break;
}
partial_d += if sub >= BLOCK_8X8 {
region_distortion(recon, input, qr, qc, half as usize)
} else {
region_distortion_luma(recon, input, qr, qc, half as usize)
};
let partial_rate_total = match model {
RateModel::Twin => cost_b + partial_rate,
RateModel::Heuristic => partial_rate + 4 * 256,
};
if best.2 < score256(partial_d, lambda, partial_rate_total) {
let (node, after, _, twin_best, cost) = best;
restore_region(recon, r, c, &after);
twin.restore(&twin_best);
return Ok((node, cost));
}
}
cost_b += twin_cost;
let children: [Box<SyntaxNode>; 4] = children
.try_into()
.unwrap_or_else(|_| unreachable!("four quadrants searched"));
let d_b = region_distortion(recon, input, r, c, n4 as usize);
let r_b = match model {
RateModel::Twin => cost_b,
RateModel::Heuristic => (children.iter().map(|ch| tree_rate(ch)).sum::<u64>() + 4) * 256,
};
let score_b = score256(d_b, lambda, r_b);
if best.2 <= score_b {
let (node, after, _, twin_best, cost) = best;
restore_region(recon, r, c, &after);
twin.restore(&twin_best);
Ok((node, cost))
} else {
Ok((SyntaxNode::Split(children), cost_b))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cdf::PALETTE_COLORS;
fn plane8(p: &[u16]) -> Vec<u8> {
p.iter().map(|&s| s as u8).collect()
}
fn search_ctx_for_tests(recon: &ReconState) -> (RateTwin, SyntaxFrameParams) {
let writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.init_coeff_cdfs(recon.qp.base_q_idx);
let state = PartitionSyntaxWriter::new(
recon.mi_rows,
recon.mi_cols,
TileGeometry {
mi_row_start: 0,
mi_row_end: recon.mi_rows,
mi_col_start: 0,
mi_col_end: recon.mi_cols,
},
)
.expect("test geometry is valid");
let params = SyntaxFrameParams {
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
seg_id_pre_skip: false,
segmentation_enabled: false,
seg_ref_frame: [None; crate::uncompressed_header_tail::MAX_SEGMENTS],
seg_skip: [false; crate::uncompressed_header_tail::MAX_SEGMENTS],
seg_globalmv: [false; crate::uncompressed_header_tail::MAX_SEGMENTS],
last_active_seg_id: 0,
lossless_array: [recon.lossless; crate::uncompressed_header_tail::MAX_SEGMENTS],
coded_lossless: recon.lossless,
enable_cdef: false,
allow_intrabc: recon.allow_intrabc,
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: recon.allow_screen_content_tools,
enable_filter_intra: true,
bit_depth: 8,
tx_mode_select: !recon.lossless,
quant: recon.qp,
reduced_tx_set: false,
inter: None,
};
(RateTwin::snapshot(&cdfs, &state, &writer), params)
}
fn dither4(w: u32, h: u32, seed: u32) -> YuvFrame {
const COLORS: [u16; 4] = [16, 80, 160, 240];
let (wu, hu) = (w as usize, h as usize);
let mut f = YuvFrame::filled(w, h, 8, ChromaFormat::Yuv420, 128);
let mut state = 0x1234_5678u32 ^ seed;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for i in 0..hu {
for j in 0..wu {
f.y[i * wu + j] = COLORS[(next() & 3) as usize];
}
}
let (cw, ch) = (wu / 2, hu / 2);
for i in 0..ch {
for j in 0..cw {
let par = ((i / 2) + (j / 2)) & 1;
f.u[i * cw + j] = if par == 0 { 96 } else { 168 };
f.v[i * cw + j] = if ((i / 4) & 1) == par { 108 } else { 152 };
}
}
f
}
#[test]
fn r421_twin_costs_sum_to_writer_emission() {
let input = dither4(128, 64, 77);
let (mi_rows, mi_cols) = (16u32, 32u32);
let mut recon = ReconState {
y: vec![0u16; 128 * 64],
u: vec![0u16; 64 * 32],
v: vec![0u16; 64 * 32],
width: 128,
height: 64,
chroma_w: 64,
chroma_h: 32,
mi_rows,
mi_cols,
lossless: false,
allow_screen_content_tools: true,
allow_intrabc: false,
qp: QuantizerParams::neutral(60, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.init_coeff_cdfs(60);
let mut state = PartitionSyntaxWriter::new(
mi_rows,
mi_cols,
TileGeometry {
mi_row_start: 0,
mi_row_end: mi_rows,
mi_col_start: 0,
mi_col_end: mi_cols,
},
)
.unwrap();
let (_, params) = search_ctx_for_tests(&recon);
let mut total_cost256 = 0u64;
for (sb_r, sb_c) in sb_grid_origins(mi_rows, mi_cols) {
recon.bd.clear_for_sb(sb_r, sb_c, mi_rows, mi_cols);
state.arm_read_deltas();
let mut twin = RateTwin::snapshot(&cdfs, &state, &writer);
twin.arm_read_deltas();
let (tree, cost) = build_search_tree(
sb_r,
sb_c,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
total_cost256 += cost;
write_partition_tree_syntax(
&mut writer,
&mut cdfs,
&mut state,
&tree,
sb_r,
sb_c,
BLOCK_64X64,
¶ms,
)
.unwrap();
assert!(
twin.matches(&cdfs, &writer),
"rate twin desynced after superblock ({sb_r},{sb_c})"
);
}
let tile_bits = writer.finish().len() as u64 * 8;
let predicted_bits = total_cost256 / 256;
assert!(
tile_bits.abs_diff(predicted_bits) <= 16,
"twin predicted {predicted_bits} bits, tile payload is {tile_bits} bits"
);
assert!(predicted_bits > 2000, "payload unexpectedly trivial");
}
fn count_palette_leaves(node: &SyntaxNode, pal: &mut u32, other: &mut u32) {
count_palette_leaves_by(node, pal, other, |b| b.palette.size_y > 0)
}
fn count_palette_uv_leaves(node: &SyntaxNode, pal: &mut u32, other: &mut u32) {
count_palette_leaves_by(node, pal, other, |b| b.palette.size_uv > 0)
}
fn count_palette_leaves_by(
node: &SyntaxNode,
pal: &mut u32,
other: &mut u32,
pred: fn(&SyntaxBlock) -> bool,
) {
let leafc = |b: &SyntaxBlock, pal: &mut u32, other: &mut u32| {
if pred(b) {
*pal += 1;
} else {
*other += 1;
}
};
match node {
SyntaxNode::Leaf(b) => leafc(b, pal, other),
SyntaxNode::Split(children) => {
for ch in children.iter() {
count_palette_leaves_by(ch, pal, other, pred);
}
}
rest => {
for b in rest.asymmetric_blocks().iter() {
leafc(b, pal, other);
}
}
}
}
#[test]
fn r418_search_selects_luma_palette_on_dither_content() {
for q in [60u8, 0] {
let input = dither4(64, 64, 40 + u32::from(q));
let mut recon = ReconState {
y: vec![0u16; 64 * 64],
u: vec![0u16; 32 * 32],
v: vec![0u16; 32 * 32],
width: 64,
height: 64,
chroma_w: 32,
chroma_h: 32,
mi_rows: 16,
mi_cols: 16,
lossless: q == 0,
allow_screen_content_tools: true,
allow_intrabc: false,
qp: QuantizerParams::neutral(q, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
recon.bd.clear_for_sb(0, 0, 16, 16);
let (mut twin, params) = search_ctx_for_tests(&recon);
let (tree, _) = build_search_tree(
0,
0,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
let (mut pal, mut other) = (0u32, 0u32);
count_palette_leaves(&tree, &mut pal, &mut other);
assert!(
pal > 0,
"q={q}: 4-colour dither content must commit PaletteSizeY > 0 leaves \
(got PALETTE={pal} OTHER={other})"
);
let (mut pal_uv, mut other_uv) = (0u32, 0u32);
count_palette_uv_leaves(&tree, &mut pal_uv, &mut other_uv);
assert!(
pal_uv > 0,
"q={q}: 2x2-cell chroma checker content must commit PaletteSizeUV > 0 \
leaves (got PALETTE_UV={pal_uv} OTHER={other_uv})"
);
fn check_sorted(node: &SyntaxNode) {
let leafc = |b: &SyntaxBlock| {
let k = b.palette.size_y as usize;
if k > 0 {
assert!((2..=PALETTE_COLORS).contains(&k));
for i in 1..k {
assert!(
b.palette.colors_y[i] > b.palette.colors_y[i - 1],
"§5.11.46 canonical form: colours strictly ascending"
);
}
assert!(b.palette.color_map_y.iter().all(|&m| (m as usize) < k));
}
let kuv = b.palette.size_uv as usize;
if kuv > 0 {
assert!((2..=PALETTE_COLORS).contains(&kuv));
for i in 1..kuv {
assert!(
b.palette.colors_u[i] >= b.palette.colors_u[i - 1],
"§5.11.46 canonical form: U colours non-strictly ascending"
);
}
assert!(b.palette.color_map_uv.iter().all(|&m| (m as usize) < kuv));
}
};
match node {
SyntaxNode::Leaf(b) => leafc(b),
SyntaxNode::Split(children) => children.iter().for_each(|ch| check_sorted(ch)),
rest => rest.asymmetric_blocks().iter().for_each(|b| leafc(b)),
}
}
check_sorted(&tree);
let enc = encode_key_frame_yuv_with_q(&input, q).unwrap();
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "q={q}: luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "q={q}: U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "q={q}: V");
if q == 0 {
assert_eq!(enc.recon_y, input.y, "lossless palette arm must be exact");
}
}
}
#[test]
fn r424_search_elects_signed_delta_v_palette_arm() {
let mut input = dither4(64, 64, 91);
for i in 0..32usize {
for j in 0..32usize {
let cell = ((i / 2) + (j / 2)) & 1;
input.u[i * 32 + j] = if cell == 0 { 64 } else { 192 };
input.v[i * 32 + j] = if cell == 0 { 100 } else { 106 };
}
}
let mut recon = ReconState {
y: vec![0u16; 64 * 64],
u: vec![0u16; 32 * 32],
v: vec![0u16; 32 * 32],
width: 64,
height: 64,
chroma_w: 32,
chroma_h: 32,
mi_rows: 16,
mi_cols: 16,
lossless: false,
allow_screen_content_tools: true,
allow_intrabc: false,
qp: QuantizerParams::neutral(60, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
recon.bd.clear_for_sb(0, 0, 16, 16);
let (mut twin, params) = search_ctx_for_tests(&recon);
let (tree, _) = build_search_tree(
0,
0,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
let (mut pal_uv, mut other_uv) = (0u32, 0u32);
count_palette_uv_leaves(&tree, &mut pal_uv, &mut other_uv);
assert!(pal_uv > 0, "the checker chroma must commit UV palettes");
let (mut delta_v, mut literal_v) = (0u32, 0u32);
count_palette_leaves_by(&tree, &mut delta_v, &mut literal_v, |b| {
b.palette.size_uv > 0 && b.palette.delta_encode_v
});
assert!(
delta_v > 0,
"tight V clustering must elect the §5.11.46 signed-delta arm \
(delta={delta_v} literal-or-other={literal_v})"
);
let enc = encode_key_frame_yuv_with_q(&input, 60).unwrap();
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "V");
}
#[test]
fn r418_search_selects_intrabc_on_tiled_content() {
let mut tile_y = [0u16; 64 * 64];
let mut tile_u = [0u16; 32 * 32];
let mut tile_v = [0u16; 32 * 32];
let mut state = 0x00C0_FFEEu32;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for p in tile_y.iter_mut() {
*p = (next() & 0xFF) as u16;
}
for p in tile_u.iter_mut() {
*p = (next() & 0xFF) as u16;
}
for p in tile_v.iter_mut() {
*p = (next() & 0xFF) as u16;
}
let (w, h) = (192u32, 192u32);
let mut input = YuvFrame::filled(w, h, 8, ChromaFormat::Yuv420, 0);
for i in 0..192usize {
for j in 0..192usize {
input.y[i * 192 + j] = tile_y[(i % 64) * 64 + (j % 64)];
}
}
for i in 0..96usize {
for j in 0..96usize {
input.u[i * 96 + j] = tile_u[(i % 32) * 32 + (j % 32)];
input.v[i * 96 + j] = tile_v[(i % 32) * 32 + (j % 32)];
}
}
for q in [60u8, 0] {
let enc = encode_key_frame_yuv_with_q(&input, q).unwrap();
assert!(
enc.fh.allow_intrabc,
"q={q}: duplicate-tile scan must open the §5.9.20 gate"
);
let mut recon = ReconState {
y: vec![0u16; 192 * 192],
u: vec![0u16; 96 * 96],
v: vec![0u16; 96 * 96],
width: 192,
height: 192,
chroma_w: 96,
chroma_h: 96,
mi_rows: 48,
mi_cols: 48,
lossless: q == 0,
allow_screen_content_tools: true,
allow_intrabc: true,
qp: QuantizerParams::neutral(q, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
let (mut ibc, mut other) = (0u32, 0u32);
let (mut twin, params) = search_ctx_for_tests(&recon);
for (sb_r, sb_c) in sb_grid_origins(48, 48) {
recon.bd.clear_for_sb(sb_r, sb_c, 48, 48);
twin.arm_read_deltas();
let (tree, _) = build_search_tree(
sb_r,
sb_c,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
count_palette_leaves_by(&tree, &mut ibc, &mut other, |b| b.intrabc_mv.is_some());
}
assert!(
ibc > 0,
"q={q}: tiled content must commit use_intrabc = 1 leaves \
(got INTRABC={ibc} OTHER={other})"
);
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "q={q}: luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "q={q}: U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "q={q}: V");
if q == 0 {
assert_eq!(enc.recon_y, input.y, "lossless intrabc arm must be exact");
assert_eq!(enc.recon_u, input.u);
assert_eq!(enc.recon_v, input.v);
}
}
}
#[test]
fn r425_hash_dv_search_finds_off_stride_glyph_copies() {
let (w, h) = (256usize, 192usize);
let mut input = YuvFrame::filled(w as u32, h as u32, 8, ChromaFormat::Yuv420, 128);
for i in 0..h {
for j in 0..w {
let sb = (i / 64) * 4 + j / 64;
input.y[i * w + j] = 60 + 3 * sb as u16;
}
}
let mut glyph = [0u16; 16 * 16];
let mut state = 0x0BAD_CAFEu32;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for p in glyph.iter_mut() {
*p = (next() & 0xFF) as u16;
}
let stamps: [(usize, usize); 5] = [(0, 0), (80, 80), (80, 160), (80, 224), (144, 32)];
for &(sy, sx) in &stamps {
for i in 0..16 {
for j in 0..16 {
input.y[(sy + i) * w + sx + j] = glyph[i * 16 + j];
}
}
}
let expected_dvs: Vec<[i32; 2]> = stamps[1..]
.iter()
.map(|&(dy, dx)| [-8 * (dy as i32), -8 * (dx as i32)])
.collect();
let mut bg_only = input.clone();
for &(sy, sx) in &stamps {
let sb = (sy / 64) * 4 + sx / 64;
for i in 0..16 {
for j in 0..16 {
bg_only.y[(sy + i) * w + sx + j] = 60 + 3 * sb as u16;
}
}
}
assert!(
!intrabc_beneficial(&bg_only),
"background alone opens the gate"
);
assert!(
intrabc_beneficial(&input),
"four reachable glyph duplicates must open the glyph tier"
);
for q in [60u8, 0] {
let enc = encode_key_frame_yuv_with_q(&input, q).unwrap();
assert!(enc.fh.allow_intrabc, "q={q}: glyph tier gate");
let mut recon = ReconState {
y: vec![0u16; w * h],
u: vec![0u16; (w / 2) * (h / 2)],
v: vec![0u16; (w / 2) * (h / 2)],
width: w,
height: h,
chroma_w: w / 2,
chroma_h: h / 2,
mi_rows: (h / 4) as u32,
mi_cols: (w / 4) as u32,
lossless: q == 0,
allow_screen_content_tools: true,
allow_intrabc: true,
qp: QuantizerParams::neutral(q, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: crate::encoder::dv_hash::DvHashIndex::build(&input.y, w, h),
};
let (mut twin, params) = search_ctx_for_tests(&recon);
let mut committed: Vec<[i32; 2]> = Vec::new();
fn collect_dvs(node: &SyntaxNode, out: &mut Vec<[i32; 2]>) {
match node {
SyntaxNode::Leaf(b) => {
if let Some(mv) = b.intrabc_mv {
out.push(mv);
}
}
SyntaxNode::Split(ch) => {
for c in ch.iter() {
collect_dvs(c, out);
}
}
_ => {}
}
}
for (sb_r, sb_c) in sb_grid_origins(recon.mi_rows, recon.mi_cols) {
recon
.bd
.clear_for_sb(sb_r, sb_c, recon.mi_rows, recon.mi_cols);
twin.arm_read_deltas();
let (tree, _) = build_search_tree(
sb_r,
sb_c,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
collect_dvs(&tree, &mut committed);
}
for dv in &expected_dvs {
assert!(
committed.contains(dv),
"q={q}: hash-seeded DV {dv:?} missing from committed set {committed:?}"
);
}
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "q={q}: luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "q={q}: U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "q={q}: V");
if q == 0 {
assert_eq!(enc.recon_y, input.y, "lossless glyph copies must be exact");
}
}
}
#[test]
fn r425_rect_palette_leaf_elected_on_split_content() {
let (w, h) = (64usize, 64usize);
const LEFT: [u16; 8] = [16, 48, 80, 112, 144, 176, 208, 240];
const RIGHT: [u16; 8] = [32, 64, 96, 128, 160, 192, 224, 255];
let mut input = YuvFrame::filled(w as u32, h as u32, 8, ChromaFormat::Yuv420, 128);
let mut state = 0x5EED_0425u32;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for i in 0..h {
for j in 0..w {
let pal = if j < 32 { &LEFT } else { &RIGHT };
input.y[i * w + j] = pal[(next() & 7) as usize];
}
}
for q in [20u8, 0] {
let mut recon = ReconState {
y: vec![0u16; w * h],
u: vec![0u16; (w / 2) * (h / 2)],
v: vec![0u16; (w / 2) * (h / 2)],
width: w,
height: h,
chroma_w: w / 2,
chroma_h: h / 2,
mi_rows: (h / 4) as u32,
mi_cols: (w / 4) as u32,
lossless: q == 0,
allow_screen_content_tools: true,
allow_intrabc: false,
qp: QuantizerParams::neutral(q, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
let (mut twin, params) = search_ctx_for_tests(&recon);
recon.bd.clear_for_sb(0, 0, recon.mi_rows, recon.mi_cols);
twin.arm_read_deltas();
let (tree, _) = build_search_tree(
0,
0,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
fn find_rect_palette(node: &SyntaxNode) -> bool {
match node {
SyntaxNode::Horz(blocks) | SyntaxNode::Vert(blocks) => {
blocks.iter().any(|b| b.palette.size_y > 0)
}
SyntaxNode::Split(ch) => ch.iter().any(|c| find_rect_palette(c)),
_ => false,
}
}
assert!(
find_rect_palette(&tree),
"q={q}: split-content superblock must elect a rect palette leaf: {tree:?}"
);
let enc = encode_key_frame_yuv_with_q(&input, q).unwrap();
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "q={q}: luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "q={q}: U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "q={q}: V");
if q == 0 {
assert_eq!(enc.recon_y, input.y, "lossless rect palette must be exact");
}
}
}
#[test]
fn r425_clipped_palette_leaf_on_bottom_edge() {
let (w, h) = (64usize, 80usize);
let mut input = YuvFrame::filled(w as u32, h as u32, 8, ChromaFormat::Yuv420, 128);
let mut state = 0xC11B_0425u32;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for p in input.y.iter_mut() {
*p = if next() & 1 == 0 { 40 } else { 200 };
}
for q in [60u8, 0] {
let mut recon = ReconState {
y: vec![0u16; w * h],
u: vec![0u16; (w / 2) * (h / 2)],
v: vec![0u16; (w / 2) * (h / 2)],
width: w,
height: h,
chroma_w: w / 2,
chroma_h: h / 2,
mi_rows: (h / 4) as u32,
mi_cols: (w / 4) as u32,
lossless: q == 0,
allow_screen_content_tools: true,
allow_intrabc: false,
qp: QuantizerParams::neutral(q, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
let (mut twin, params) = search_ctx_for_tests(&recon);
let mut found_clipped_palette = false;
for (sb_r, sb_c) in sb_grid_origins(recon.mi_rows, recon.mi_cols) {
recon
.bd
.clear_for_sb(sb_r, sb_c, recon.mi_rows, recon.mi_cols);
twin.arm_read_deltas();
let (tree, _) = build_search_tree(
sb_r,
sb_c,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
if sb_r == 16 {
if let SyntaxNode::Horz(blocks) = &tree {
found_clipped_palette = blocks[0].palette.size_y > 0;
}
}
}
assert!(
found_clipped_palette,
"q={q}: the straddling superblock must elect a clipped HORZ palette leaf"
);
let enc = encode_key_frame_yuv_with_q(&input, q).unwrap();
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "q={q}: luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "q={q}: U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "q={q}: V");
if q == 0 {
assert_eq!(
enc.recon_y, input.y,
"lossless clipped palette must be exact"
);
}
}
}
fn scc_tile_encode(input: &YuvFrame, q: u8, screen: bool, hash: bool) -> (usize, u64) {
let (w, h) = (input.width as usize, input.height as usize);
let allow_intrabc = screen && intrabc_beneficial(input);
let mut recon = ReconState {
y: vec![0u16; w * h],
u: vec![0u16; (w / 2) * (h / 2)],
v: vec![0u16; (w / 2) * (h / 2)],
width: w,
height: h,
chroma_w: w / 2,
chroma_h: h / 2,
mi_rows: (h / 4) as u32,
mi_cols: (w / 4) as u32,
lossless: q == 0,
allow_screen_content_tools: screen,
allow_intrabc,
qp: QuantizerParams::neutral(q, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: if hash && allow_intrabc {
crate::encoder::dv_hash::DvHashIndex::build(&input.y, w, h)
} else {
Default::default()
},
};
let (_, params) = search_ctx_for_tests(&recon);
let mut writer = SymbolWriter::new(false);
let mut cdfs = TileCdfContext::new_from_defaults();
cdfs.init_coeff_cdfs(q);
let mut state = PartitionSyntaxWriter::new(
recon.mi_rows,
recon.mi_cols,
TileGeometry {
mi_row_start: 0,
mi_row_end: recon.mi_rows,
mi_col_start: 0,
mi_col_end: recon.mi_cols,
},
)
.expect("geometry");
for (sb_r, sb_c) in sb_grid_origins(recon.mi_rows, recon.mi_cols) {
recon
.bd
.clear_for_sb(sb_r, sb_c, recon.mi_rows, recon.mi_cols);
state.arm_read_deltas();
let mut twin = RateTwin::snapshot(&cdfs, &state, &writer);
twin.arm_read_deltas();
let (tree, _) = build_search_tree(
sb_r,
sb_c,
BLOCK_64X64,
input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.expect("search");
crate::encoder::partition_tree::write_partition_tree_syntax(
&mut writer,
&mut cdfs,
&mut state,
&tree,
sb_r,
sb_c,
BLOCK_64X64,
¶ms,
)
.expect("write");
}
let bytes = writer.finish().len();
let mut ssd = 0u64;
for (a, b) in recon.y.iter().zip(input.y.iter()) {
let d = *a as i64 - *b as i64;
ssd += (d * d) as u64;
}
(bytes, ssd)
}
fn scc_glyph_text(w: usize, h: usize, seed: u32) -> YuvFrame {
let mut f = YuvFrame::filled(w as u32, h as u32, 8, ChromaFormat::Yuv420, 128);
for p in f.y.iter_mut() {
*p = 235;
}
let mut state = seed | 1;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
let glyphs: Vec<u64> = (0..6)
.map(|_| (u64::from(next()) << 32) | u64::from(next()))
.collect();
let mut row = 8usize;
while row + 8 <= h.saturating_sub(4) {
let mut col = 8usize;
while col + 8 <= w.saturating_sub(4) {
if next() % 5 == 0 {
col += 8; continue;
}
let g = glyphs[(next() % 6) as usize];
for i in 0..8 {
for j in 0..8 {
if (g >> (i * 8 + j)) & 1 == 1 {
f.y[(row + i) * w + col + j] = 32;
}
}
}
col += 8;
}
row += 12;
}
f
}
fn scc_ui_panel(w: usize, h: usize, seed: u32) -> YuvFrame {
let mut f = YuvFrame::filled(w as u32, h as u32, 8, ChromaFormat::Yuv420, 128);
let mut state = seed | 1;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for i in 0..h {
for j in 0..w {
f.y[i * w + j] = if i < 16 {
64
} else if i == 16 || j == 0 || j + 1 == w || i + 1 == h {
16
} else {
200
};
}
}
for i in (h / 2)..(h - 8) {
for j in 8..(w / 2) {
f.y[i * w + j] = if next() & 1 == 0 { 40 } else { 200 };
}
}
let icon: Vec<bool> = (0..256).map(|_| next() & 1 == 1).collect();
let mut anchors = vec![(24usize, 8usize)];
let mut x = 8 + 40;
while x + 16 < w - 8 {
anchors.push((24, x));
x += 40;
}
for &(ay, ax) in &anchors {
if ay + 16 <= h && ax + 16 <= w {
for i in 0..16 {
for j in 0..16 {
f.y[(ay + i) * w + ax + j] = if icon[i * 16 + j] { 96 } else { 224 };
}
}
}
}
f
}
fn scc_text_over_gradient(w: usize, h: usize, seed: u32) -> YuvFrame {
let mut f = scc_glyph_text(w, h, seed);
for i in 0..h {
for j in 0..w {
if f.y[i * w + j] == 235 {
f.y[i * w + j] = (48 + (i + j) * 160 / (w + h)) as u16;
}
}
}
f
}
#[test]
fn r425_scc_screen_tools_multiple_tripwire() {
let input = scc_glyph_text(192, 192, 0x7357_0425);
let (natural, _) = scc_tile_encode(&input, 60, false, false);
let (screen, ssd) = scc_tile_encode(&input, 60, true, true);
let (nohash, _) = scc_tile_encode(&input, 60, true, false);
assert_eq!(ssd, 0, "glyph text must code pixel-exact luma at q60");
assert!(
screen * 2 < natural,
"screen tools must at least halve glyph-text bytes (screen={screen} natural={natural})"
);
assert!(
screen <= nohash,
"hash-armed DV search must not lose to geometric-only (hash={screen} nohash={nohash})"
);
}
#[test]
fn r425_scc_measurement_matrix() {
let Ok(dir) = std::env::var("OXIDEAV_AV1_SCC_AB_DIR") else {
return;
};
std::fs::create_dir_all(&dir).unwrap();
let mut csv = String::from("content,w,h,q,natural,screen,nohash,ssd_screen\n");
let (mut nat_total, mut scr_total, mut nohash_total) = (0u64, 0u64, 0u64);
let mut exact_nat = 0u64;
let mut exact_scr = 0u64;
let mut exact_n = 0u32;
for &(name, which) in &[("glyph", 0u8), ("ui", 1), ("textgrad", 2)] {
for &(w, h) in &[(192usize, 192usize), (256, 144)] {
for &q in &[20u8, 60, 100] {
let input = match which {
0 => scc_glyph_text(w, h, 0x7357_0425),
1 => scc_ui_panel(w, h, 0x0425_0426),
_ => scc_text_over_gradient(w, h, 0x0425_0427),
};
let (nat, _) = scc_tile_encode(&input, q, false, false);
let (scr, ssd) = scc_tile_encode(&input, q, true, true);
let (noh, _) = scc_tile_encode(&input, q, true, false);
csv.push_str(&format!("{name},{w},{h},{q},{nat},{scr},{noh},{ssd}\n"));
nat_total += nat as u64;
scr_total += scr as u64;
nohash_total += noh as u64;
if ssd == 0 {
exact_nat += nat as u64;
exact_scr += scr as u64;
exact_n += 1;
}
}
}
}
csv.push_str(&format!(
"# totals natural={nat_total} screen={scr_total} nohash={nohash_total} \
multiple={:.2} nohash_multiple={:.2} exact_multiple={:.2} exact_configs={exact_n}\n",
nat_total as f64 / scr_total as f64,
nat_total as f64 / nohash_total as f64,
if exact_scr > 0 {
exact_nat as f64 / exact_scr as f64
} else {
0.0
},
));
std::fs::write(format!("{dir}/scc_ab_r425.csv"), &csv).unwrap();
eprintln!("{csv}");
}
fn scc_pin_page(w: usize, h: usize) -> YuvFrame {
let mut f = YuvFrame::filled(w as u32, h as u32, 8, ChromaFormat::Yuv420, 128);
for p in f.y.iter_mut() {
*p = 235;
}
let mut state = 0x0425_C0DEu32;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
let glyphs: Vec<[u16; 64]> = (0..6)
.map(|_| {
let mut g = [0u16; 64];
for p in g.iter_mut() {
*p = (next() & 0xFF) as u16;
}
g
})
.collect();
let footer_top = (h / 64) * 64;
let mut line = 0usize;
let mut row = 8usize;
while row + 8 + 4 <= footer_top {
for slot in 0..(w / 8).saturating_sub(2) {
let key = (line % 3) * 131 + slot;
if key % 5 == 0 {
continue;
}
let g = &glyphs[key % 6];
let col = 8 + slot * 8;
for i in 0..8 {
for j in 0..8 {
f.y[(row + i) * w + col + j] = g[i * 8 + j];
}
}
}
line += 1;
row += 16;
}
const LEFT: [u16; 8] = [16, 48, 80, 112, 144, 176, 208, 240];
const RIGHT: [u16; 8] = [20, 52, 84, 116, 148, 180, 212, 244];
let band_c = w - 64;
for i in 64..footer_top.min(128) {
for j in band_c..w {
let pal = if j < band_c + 32 { &LEFT } else { &RIGHT };
f.y[i * w + j] = pal[(next() & 7) as usize];
}
}
for i in footer_top..h {
for j in 0..w {
f.y[i * w + j] = if next() & 1 == 0 { 40 } else { 200 };
}
}
f
}
#[test]
fn r425_scc_pin_page_features() {
let (w, h) = (256usize, 144usize);
let input = scc_pin_page(w, h);
let q = 60u8;
let allow_intrabc = intrabc_beneficial(&input);
assert!(allow_intrabc, "the glyph page must open the §5.9.20 gate");
let mut recon = ReconState {
y: vec![0u16; w * h],
u: vec![0u16; (w / 2) * (h / 2)],
v: vec![0u16; (w / 2) * (h / 2)],
width: w,
height: h,
chroma_w: w / 2,
chroma_h: h / 2,
mi_rows: (h / 4) as u32,
mi_cols: (w / 4) as u32,
lossless: false,
allow_screen_content_tools: true,
allow_intrabc,
qp: QuantizerParams::neutral(q, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: crate::encoder::dv_hash::DvHashIndex::build(&input.y, w, h),
};
let (mut twin, params) = search_ctx_for_tests(&recon);
let (mut ibc, mut rect_pal, mut clipped_pal) = (0u32, 0u32, 0u32);
fn scan(node: &SyntaxNode, ibc: &mut u32, rect_pal: &mut u32) {
match node {
SyntaxNode::Leaf(b) if b.intrabc_mv.is_some() => *ibc += 1,
SyntaxNode::Leaf(_) => {}
SyntaxNode::Split(ch) => {
for c in ch.iter() {
scan(c, ibc, rect_pal);
}
}
SyntaxNode::Horz(blocks) | SyntaxNode::Vert(blocks) => {
for b in blocks.iter() {
if b.intrabc_mv.is_some() {
*ibc += 1;
}
if b.palette.size_y > 0 {
*rect_pal += 1;
}
}
}
_ => {}
}
}
for (sb_r, sb_c) in sb_grid_origins(recon.mi_rows, recon.mi_cols) {
recon
.bd
.clear_for_sb(sb_r, sb_c, recon.mi_rows, recon.mi_cols);
twin.arm_read_deltas();
let (tree, _) = build_search_tree(
sb_r,
sb_c,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
if sb_r == 32 {
if let SyntaxNode::Horz(blocks) = &tree {
if blocks[0].palette.size_y > 0 {
clipped_pal += 1;
}
}
}
scan(&tree, &mut ibc, &mut rect_pal);
}
assert!(ibc > 0, "pin page must commit intrabc leaves");
assert!(rect_pal > 0, "pin page must commit rect palette leaves");
assert!(
clipped_pal > 0,
"pin page must elect the clipped HORZ palette arm on the footer row"
);
let enc = encode_key_frame_yuv_with_q(&input, q).unwrap();
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "V");
}
#[test]
fn r425_scc_pin_dump() {
let Ok(dir) = std::env::var("OXIDEAV_AV1_SCC_FIXTURE_DIR") else {
return;
};
std::fs::create_dir_all(&dir).unwrap();
let input = scc_pin_page(256, 144);
let enc = encode_key_frame_yuv_with_q(&input, 60).unwrap();
std::fs::write(
format!("{dir}/self-kf-256x144-q60-screen-rect.ivf"),
&enc.ivf_bytes,
)
.unwrap();
let mut yuv = Vec::new();
yuv.extend_from_slice(&plane8(&enc.recon_y));
yuv.extend_from_slice(&plane8(&enc.recon_u));
yuv.extend_from_slice(&plane8(&enc.recon_v));
std::fs::write(format!("{dir}/self-kf-256x144-q60-screen-rect.yuv"), &yuv).unwrap();
let (w, h) = (192usize, 112usize);
let page = scc_pin_page(w, h + 16);
let frame_at = |scroll: usize| -> YuvFrame {
let mut f = YuvFrame::filled(w as u32, h as u32, 8, ChromaFormat::Yuv420, 128);
for i in 0..h {
for j in 0..w {
f.y[i * w + j] = page.y[(i + scroll) * w + j];
}
}
f
};
let gop: Vec<YuvFrame> = vec![frame_at(0), frame_at(8), frame_at(16)];
let enc = crate::encoder::inter_frame::encode_gop_yuv_with_q(&gop, 60).expect("gop encode");
std::fs::write(
format!("{dir}/self-gop-192x112-q60-screen-scroll.ivf"),
&enc.ivf_bytes,
)
.unwrap();
let mut yuv = Vec::new();
for fr in &enc.recon {
yuv.extend_from_slice(&plane8(&fr.y));
yuv.extend_from_slice(&plane8(&fr.u));
yuv.extend_from_slice(&plane8(&fr.v));
}
std::fs::write(
format!("{dir}/self-gop-192x112-q60-screen-scroll.yuv"),
&yuv,
)
.unwrap();
eprintln!("r425 scc pins dumped to {dir}");
}
#[test]
fn r418_search_selects_kmeans_palette_on_jittered_dither() {
const COLORS: [u16; 4] = [16, 80, 160, 240];
let mut input = YuvFrame::filled(64, 64, 8, ChromaFormat::Yuv420, 128);
let mut state = 0x0A11_CE55u32;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for p in input.y.iter_mut() {
let r = next();
let base = COLORS[(r & 3) as usize];
let jitter = ((r >> 2) % 3) as i32 - 1;
*p = (i32::from(base) + jitter).clamp(0, 255) as u16;
}
let mut seen = [false; 256];
for &v in &input.y {
seen[v as usize] = true;
}
assert!(seen.iter().filter(|&&b| b).count() > PALETTE_COLORS);
let mut recon = ReconState {
y: vec![0u16; 64 * 64],
u: vec![0u16; 32 * 32],
v: vec![0u16; 32 * 32],
width: 64,
height: 64,
chroma_w: 32,
chroma_h: 32,
mi_rows: 16,
mi_cols: 16,
lossless: false,
allow_screen_content_tools: true,
allow_intrabc: false,
qp: QuantizerParams::neutral(60, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
recon.bd.clear_for_sb(0, 0, 16, 16);
let (mut twin, params) = search_ctx_for_tests(&recon);
let (tree, _) = build_search_tree(
0,
0,
BLOCK_64X64,
&input,
&mut recon,
&mut twin,
¶ms,
RateModel::Twin,
None,
0,
)
.unwrap();
let (mut pal, mut other) = (0u32, 0u32);
count_palette_leaves(&tree, &mut pal, &mut other);
assert!(
pal > 0,
"jittered dither must commit clustered PaletteSizeY > 0 leaves \
(got PALETTE={pal} OTHER={other})"
);
let enc = encode_key_frame_yuv_with_q(&input, 60).unwrap();
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y));
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u));
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v));
}
#[test]
fn r418_screen_and_palette_fixture_streams() {
const COLORS: [u16; 4] = [12, 92, 172, 244];
let mut tile = vec![0u16; 64 * 64];
let mut state = 0x5EED_0001u32;
let mut next = move || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state
};
for p in tile.iter_mut() {
*p = COLORS[(next() & 3) as usize];
}
let mut screen = YuvFrame::filled(192, 192, 8, ChromaFormat::Yuv420, 128);
for i in 0..192usize {
for j in 0..192usize {
screen.y[i * 192 + j] = tile[(i % 64) * 64 + (j % 64)];
}
}
for i in 0..96usize {
for j in 0..96usize {
let par = ((i / 2) + (j / 2)) & 1;
screen.u[i * 96 + j] = if par == 0 { 96 } else { 168 };
screen.v[i * 96 + j] = if ((i / 4) & 1) == par { 108 } else { 152 };
}
}
let enc_screen = encode_key_frame_yuv_with_q(&screen, 60).unwrap();
assert!(enc_screen.fh.allow_intrabc);
assert!(enc_screen.fh.allow_screen_content_tools);
const COLORS8: [u16; 8] = [8, 40, 80, 120, 160, 200, 230, 250];
let mut state8 = 0x1234_5678u32 ^ 0x2C9;
let mut next8 = move || {
state8 ^= state8 << 13;
state8 ^= state8 >> 17;
state8 ^= state8 << 5;
state8
};
let mut pal = YuvFrame::filled(96, 80, 8, ChromaFormat::Yuv420, 128);
for i in 0..80usize {
for j in 0..96usize {
pal.y[i * 96 + j] = COLORS8[(next8() as usize) % 8];
}
}
for i in 0..40usize {
for j in 0..48usize {
let par = ((i / 2) + (j / 2)) & 1;
pal.u[i * 48 + j] = if par == 0 { 96 } else { 168 };
pal.v[i * 48 + j] = if ((i / 4) & 1) == par { 108 } else { 152 };
}
}
let enc_pal = encode_key_frame_yuv_with_q(&pal, 100).unwrap();
assert!(!enc_pal.fh.allow_intrabc, "no duplicate 64x64 tile pair");
for (name, enc) in [
("self-kf-192x192-q60-screen", &enc_screen),
("self-kf-96x80-q100-palette", &enc_pal),
] {
let frames = crate::decoder::decode_av1_spec(&enc.ivf_bytes).unwrap();
assert_eq!(frames.len(), 1, "{name}");
assert_eq!(frames[0].planes[0], plane8(&enc.recon_y), "{name}: luma");
assert_eq!(frames[0].planes[1], plane8(&enc.recon_u), "{name}: U");
assert_eq!(frames[0].planes[2], plane8(&enc.recon_v), "{name}: V");
if let Ok(dir) = std::env::var("OXIDEAV_AV1_SCREEN_FIXDIR") {
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(format!("{dir}/{name}.ivf"), &enc.ivf_bytes).unwrap();
let mut yuv = Vec::new();
yuv.extend_from_slice(&plane8(&enc.recon_y));
yuv.extend_from_slice(&plane8(&enc.recon_u));
yuv.extend_from_slice(&plane8(&enc.recon_v));
std::fs::write(format!("{dir}/{name}.yuv"), &yuv).unwrap();
}
}
}
#[test]
fn r418_intrabc_dv_validity() {
let (mr, mc) = (48u32, 48u32);
assert!(!intrabc_dv_valid(16, 16, BLOCK_64X64, -64, -64, mr, mc));
assert!(intrabc_dv_valid(32, 32, BLOCK_64X64, -128, -128, mr, mc));
assert!(intrabc_dv_valid(16, 32, BLOCK_64X64, -64, -128, mr, mc));
assert!(!intrabc_dv_valid(32, 32, BLOCK_64X64, -256, 0, mr, mc));
assert!(!intrabc_dv_valid(32, 32, BLOCK_64X64, 64, -128, mr, mc));
assert!(!intrabc_dv_valid(32, 32, BLOCK_8X8, -64, -64, mr, mc));
assert!(intrabc_dv_valid(32, 32, BLOCK_8X8, -128, -64, mr, mc));
}
#[test]
fn r418_palette_candidate_gates() {
let flat = YuvFrame::filled(64, 64, 8, ChromaFormat::Yuv420, 77);
let recon = ReconState {
y: vec![0u16; 64 * 64],
u: vec![0u16; 32 * 32],
v: vec![0u16; 32 * 32],
width: 64,
height: 64,
chroma_w: 32,
chroma_h: 32,
mi_rows: 16,
mi_cols: 16,
lossless: false,
allow_screen_content_tools: true,
allow_intrabc: false,
qp: QuantizerParams::neutral(60, 8),
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
num_planes: 3,
search: SearchLimits::default(),
bd: BlockDecodedMirror::new(1, 1, 3),
dv_hash: Default::default(),
};
assert!(
palette_candidate_y(&flat, &recon, 0, 0, BLOCK_64X64).is_none(),
"1 distinct colour: no palette candidate"
);
let mut many = YuvFrame::filled(64, 64, 8, ChromaFormat::Yuv420, 0);
for (i, p) in many.y.iter_mut().enumerate() {
*p = (i % 251) as u16;
}
assert!(
palette_candidate_y(&many, &recon, 0, 0, BLOCK_64X64).is_none(),
"> PALETTE_COLORS distinct colours: no candidate (r418 scope)"
);
let two = {
let mut f = YuvFrame::filled(64, 64, 8, ChromaFormat::Yuv420, 0);
for (i, p) in f.y.iter_mut().enumerate() {
*p = if (i / 3) % 2 == 0 { 10 } else { 200 };
}
f
};
let cand = palette_candidate_y(&two, &recon, 0, 0, BLOCK_64X64).expect("2-colour block");
assert_eq!(cand.colors, vec![10, 200]);
assert_eq!(cand.map.len(), 64 * 64);
assert!(palette_candidate_y(&two, &recon, 0, 0, BLOCK_4X4).is_none());
}
}