use crate::{
cabac::{
CabacEncoder, CabacWriter, ContextSet, IntraModeContexts, advance_residual_contexts,
encode_cbf_chroma, encode_cbf_luma, encode_residual, estimate_residual_bits,
},
dct,
error::EncodeError,
intra,
yuv::Yuv,
};
#[derive(Clone, Debug)]
pub(crate) struct Nalu {
pub(crate) _nal_type: u8,
pub(crate) data: Vec<u8>,
}
pub(crate) struct NaluStream {
pub(crate) nalus: Vec<Nalu>,
}
impl NaluStream {
pub(crate) fn to_length_prefixed_slices(&self) -> Vec<u8> {
let mut out = Vec::new();
for nalu in &self.nalus {
let nal_type = (nalu.data[0] >> 1) & 0x3f;
if matches!(nal_type, 32..=34) {
continue;
}
let mut escaped: Vec<u8> = Vec::with_capacity(nalu.data.len() + 8);
let mut prev = [0xffu8; 2];
for &b in &nalu.data {
if prev[0] == 0 && prev[1] == 0 && b <= 3 {
escaped.push(0x03);
prev = [prev[1], 0x03];
}
escaped.push(b);
prev = [prev[1], b];
}
out.extend_from_slice(&(escaped.len() as u32).to_be_bytes());
out.extend_from_slice(&escaped);
}
out
}
}
pub(crate) struct BitWriter {
buf: Vec<u8>,
bit_pos: u32,
cur_byte: u8,
}
impl BitWriter {
pub(crate) fn new() -> Self {
Self {
buf: Vec::new(),
bit_pos: 0,
cur_byte: 0,
}
}
pub(crate) fn write_bits(&mut self, v: u32, n: u32) {
for i in (0..n).rev() {
let bit = ((v >> i) & 1) as u8;
self.cur_byte = (self.cur_byte << 1) | bit;
self.bit_pos += 1;
if self.bit_pos == 8 {
self.buf.push(self.cur_byte);
self.cur_byte = 0;
self.bit_pos = 0;
}
}
}
pub(crate) fn write_bit(&mut self, v: bool) {
self.write_bits(v as u32, 1);
}
pub(crate) fn write_ue(&mut self, mut v: u32) {
v += 1;
let bits = 32 - v.leading_zeros();
self.write_bits(0, bits - 1);
self.write_bits(v, bits);
}
pub(crate) fn write_se(&mut self, v: i32) {
let u = if v > 0 {
2 * v as u32 - 1
} else {
(-2 * v) as u32
};
self.write_ue(u);
}
pub(crate) fn rbsp_trailing_bits(&mut self) {
self.write_bit(true);
while self.bit_pos != 0 {
self.write_bit(false);
}
}
pub(crate) fn finish(mut self) -> Vec<u8> {
if self.bit_pos > 0 {
self.buf.push(self.cur_byte << (8 - self.bit_pos));
}
self.buf
}
}
fn nalu_header(bw: &mut BitWriter, nal_type: u8) {
bw.write_bit(false); bw.write_bits(nal_type as u32, 6); bw.write_bits(0, 6); bw.write_bits(1, 3); }
pub(crate) fn level_idc_for(w: u32, h: u32) -> u8 {
let ps = (w as u64) * (h as u64);
static TABLE: &[(u64, u8)] = &[
(36864, 30),
(122880, 60),
(245760, 63),
(552960, 90),
(983040, 93),
(2228224, 120),
(8912896, 150),
(35651584, 180),
];
for &(maxps, lvl) in TABLE {
if ps <= maxps {
return lvl;
}
}
186 }
#[inline]
fn uses_rext_profile(
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
lossless: bool,
) -> bool {
let is_420 = matches!(
chroma,
crate::fmt::ChromaFormat::Yuv420 | crate::fmt::ChromaFormat::Monochrome
);
lossless || !is_420 || bit_depth.bits() > 10
}
fn write_profile_tier_level(
bw: &mut BitWriter,
level_idc: u8,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
lossless: bool,
) {
let is_420 = matches!(
chroma,
crate::fmt::ChromaFormat::Yuv420 | crate::fmt::ChromaFormat::Monochrome
);
let bits = bit_depth.bits();
let is_rext = uses_rext_profile(chroma, bit_depth, lossless);
let (profile_idc, compat): (u32, u32) = if is_rext {
(4, 0x0800_0000) } else if bits <= 8 {
(3, 0x7000_0000) } else {
(2, 0x2000_0000) };
bw.write_bits(0, 2); bw.write_bit(false); bw.write_bits(profile_idc, 5); bw.write_bits(compat, 32);
bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.write_bit(true);
if is_rext {
let is_444 = matches!(chroma, crate::fmt::ChromaFormat::Yuv444);
let is_mono = matches!(chroma, crate::fmt::ChromaFormat::Monochrome);
let constraint_444 = lossless || is_444;
bw.write_bit(bits <= 12); bw.write_bit(bits <= 10); bw.write_bit(bits <= 8); bw.write_bit(!constraint_444 && (!is_444 || is_mono)); bw.write_bit(!constraint_444 && (is_420 || is_mono)); bw.write_bit(!constraint_444 && is_mono); bw.write_bit(true); bw.write_bit(false); bw.write_bit(true); bw.write_bit(bits <= 14); bw.write_bits(0, 32);
bw.write_bits(0, 2); } else {
bw.write_bits(0, 32);
bw.write_bits(0, 12);
}
bw.write_bits(level_idc as u32, 8);
}
pub(crate) fn build_vps(
width: u32,
height: u32,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
lossless: bool,
) -> Nalu {
let coded_w = (width + 63) & !63;
let coded_h = (height + 63) & !63;
let level = level_idc_for(coded_w, coded_h);
let mut bw = BitWriter::new();
nalu_header(&mut bw, 32);
bw.write_bits(0, 4); bw.write_bit(true); bw.write_bit(true); bw.write_bits(0, 6); bw.write_bits(0, 3); bw.write_bit(true); bw.write_bits(0xFFFF, 16);
write_profile_tier_level(&mut bw, level, chroma, bit_depth, lossless);
bw.write_bit(false);
bw.write_ue(0); bw.write_ue(0); bw.write_ue(0);
bw.write_bits(0, 6); bw.write_ue(0);
bw.write_bit(false); bw.write_bit(false);
bw.rbsp_trailing_bits();
Nalu {
_nal_type: 32,
data: bw.finish(),
}
}
fn write_sps_range_extension(bw: &mut BitWriter, lossless: bool) {
bw.write_bit(false); bw.write_bit(false); bw.write_bit(lossless); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); }
pub(crate) fn build_sps(
width: u32,
height: u32,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
lossless: bool,
color: Option<&crate::color::Cicp>,
) -> Nalu {
let mut bw = BitWriter::new();
nalu_header(&mut bw, 33);
bw.write_bits(0, 4); bw.write_bits(0, 3); bw.write_bit(true);
let sps_level = level_idc_for((width + 63) & !63, (height + 63) & !63);
write_profile_tier_level(&mut bw, sps_level, chroma, bit_depth, lossless);
bw.write_ue(0);
bw.write_ue(chroma.idc()); if chroma.idc() == 3 {
bw.write_bit(false); }
let coded_w = (width + 63) & !63;
let coded_h = (height + 63) & !63;
bw.write_ue(coded_w);
bw.write_ue(coded_h);
let sub_w = chroma.sub_w() as u32;
let sub_h = chroma.sub_h() as u32;
let crop_right = (coded_w - width) / sub_w;
let crop_bottom = (coded_h - height) / sub_h;
let need_window = crop_right > 0 || crop_bottom > 0;
bw.write_bit(need_window);
if need_window {
bw.write_ue(0); bw.write_ue(crop_right); bw.write_ue(0); bw.write_ue(crop_bottom); }
bw.write_ue(bit_depth.minus8() as u32); bw.write_ue(bit_depth.minus8() as u32);
bw.write_ue(4);
bw.write_bit(false);
bw.write_ue(0); bw.write_ue(0); bw.write_ue(0);
bw.write_ue(0);
bw.write_ue(3);
bw.write_ue(0);
bw.write_ue(3);
bw.write_ue(0);
bw.write_ue(0);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(true); bw.write_bit(false);
bw.write_ue(0); bw.write_bit(false); bw.write_bit(true); bw.write_bit(false);
bw.write_bit(true); write_vui(&mut bw, color);
let need_range_ext = uses_rext_profile(chroma, bit_depth, lossless);
bw.write_bit(need_range_ext); if need_range_ext {
bw.write_bit(true); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bits(0, 4); write_sps_range_extension(&mut bw, lossless);
}
bw.rbsp_trailing_bits();
Nalu {
_nal_type: 33,
data: bw.finish(),
}
}
fn write_vui(bw: &mut BitWriter, color: Option<&crate::color::Cicp>) {
bw.write_bit(false); bw.write_bit(false);
bw.write_bit(true);
bw.write_bits(5, 3); bw.write_bit(color.map(|c| c.full_range).unwrap_or(true)); match color {
Some(c) => {
bw.write_bit(true); bw.write_bits(c.primaries as u32, 8); bw.write_bits(c.transfer as u32, 8); bw.write_bits(c.matrix as u32, 8); }
None => {
bw.write_bit(false); }
}
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); }
pub(crate) fn build_pps(qp: u8, lossless: bool) -> Nalu {
let mut bw = BitWriter::new();
nalu_header(&mut bw, 34);
bw.write_ue(0); bw.write_ue(0); bw.write_bit(false); bw.write_bit(false); bw.write_bits(0, 3); bw.write_bit(!lossless); bw.write_bit(false); bw.write_ue(0); bw.write_ue(0); bw.write_se(qp as i32 - 26); bw.write_bit(false); bw.write_bit(false);
bw.write_bit(false);
bw.write_se(0); bw.write_se(0);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_bit(lossless); bw.write_bit(false); bw.write_bit(false); bw.write_bit(true);
bw.write_bit(false); bw.write_bit(false); bw.write_bit(false); bw.write_ue(0); bw.write_bit(false); bw.write_bit(false);
bw.rbsp_trailing_bits();
Nalu {
_nal_type: 34,
data: bw.finish(),
}
}
pub(crate) fn encode_intra(
yuv: &Yuv,
width: u32,
height: u32,
quality: u8,
lossless: bool,
color: Option<crate::color::Cicp>,
) -> Result<NaluStream, EncodeError> {
let vps = build_vps(width, height, yuv.chroma, yuv.bit_depth, lossless);
let sps = build_sps(
width,
height,
yuv.chroma,
yuv.bit_depth,
lossless,
color.as_ref(),
);
let qp_val: u8 = ((100 - quality.clamp(1, 100) as u32) * 41 / 99 + 10).min(51) as u8;
let pps = build_pps(qp_val, lossless);
let (idr, _ry, _rcb, _rcr) = build_idr_slice(yuv, width, height, quality, lossless)?;
Ok(NaluStream {
nalus: vec![vps, sps, pps, idr],
})
}
#[allow(clippy::type_complexity)]
fn build_idr_slice(
yuv: &Yuv,
width: u32,
height: u32,
quality: u8,
lossless: bool,
) -> Result<(Nalu, Vec<u16>, Vec<u16>, Vec<u16>), EncodeError> {
let qp_val: u8 = ((100 - quality.clamp(1, 100) as u32) * 41 / 99 + 10).min(51) as u8;
let _ = quality;
let sub_w = yuv.chroma.sub_w();
let sub_h = yuv.chroma.sub_h();
let w = ((width + 63) & !63) as usize;
let h = ((height + 63) & !63) as usize;
let cw = w / sub_w;
let ch = h / sub_h;
let src_yw = yuv.width as usize;
let src_yh = yuv.height as usize;
let src_cw = (yuv.width as usize).div_ceil(sub_w);
let src_ch = (yuv.height as usize).div_ceil(sub_h);
let mut hdr = BitWriter::new();
nalu_header(&mut hdr, 20);
hdr.write_bit(true); hdr.write_bit(false); hdr.write_ue(0); hdr.write_ue(2); hdr.write_bit(true); if !yuv.chroma.is_monochrome() {
hdr.write_bit(true); }
hdr.write_se(0); hdr.write_bit(true); hdr.rbsp_trailing_bits();
let header_bytes = hdr.finish();
let qp: u8 = qp_val;
let mut cab = CabacEncoder::new();
let mut ctx = ContextSet::init_islice(qp);
let mut ictx = IntraModeContexts::init_islice(qp);
let lambda = 0.57_f32 * 2f32.powf((qp as f32 - 12.0) / 3.0);
let mut cu_depth = vec![0u8; (w / 8) * (h / 8)];
let blk_stride = w / 8;
let mut mode_map = vec![0u8; (w / 8) * (h / 8)];
let mut scratch = Box::new(CompressionContext::new());
let mut rec_y = pad_plane(&yuv.y, src_yw, src_yh, w, h);
let (mut rec_cb, mut rec_cr) = if yuv.chroma.is_monochrome() {
(Vec::new(), Vec::new())
} else {
(
pad_plane(&yuv.cb, src_cw, src_ch, cw, ch),
pad_plane(&yuv.cr, src_cw, src_ch, cw, ch),
)
};
let ctb_size_y = 64usize;
let ctus_x = w / ctb_size_y;
let ctus_y = h / ctb_size_y;
let total_ctus = ctus_x * ctus_y;
let strides = PlaneStrides {
w,
src_yw,
src_yh,
cw,
src_cw,
src_ch,
sub_w,
sub_h,
};
let mut ctu_idx = 0usize;
for ctu_row in 0..ctus_y {
for ctu_col in 0..ctus_x {
let lu_row0 = ctu_row * ctb_size_y;
let lu_col0 = ctu_col * ctb_size_y;
if ctu_col > 0 {
cab.encode_bin(0, &mut ctx.sao_merge_flag);
}
if ctu_row > 0 {
cab.encode_bin(0, &mut ctx.sao_merge_flag);
}
cab.encode_bin(0, &mut ctx.sao_type_idx);
if !yuv.chroma.is_monochrome() {
cab.encode_bin(0, &mut ctx.sao_type_idx);
}
let root_ctx = (ctu_col > 0) as usize + (ctu_row > 0) as usize;
cab.encode_bin(1, &mut ctx.split_cu_flag[root_ctx]);
let mut tree = CuTreeState {
yuv,
rec_y: &mut rec_y,
rec_cb: &mut rec_cb,
rec_cr: &mut rec_cr,
strides,
qp,
lambda,
mode_map: &mut mode_map,
cu_depth: &mut cu_depth,
blk_stride,
lossless,
scratch: &mut scratch,
};
for (dy, dx) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)] {
let row = lu_row0 + dy * 32;
let col = lu_col0 + dx * 32;
let plan = fast_cu32_plan(&tree, row, col);
commit_cu32_plan(&mut cab, &mut ctx, &mut ictx, &mut tree, row, col, 1, plan);
}
let is_last_ctu = ctu_idx == total_ctus - 1;
cab.encode_terminate(if is_last_ctu { 1 } else { 0 });
ctu_idx += 1;
}
}
let cabac_bytes = cab.finish();
let mut nalu_data = header_bytes;
nalu_data.extend_from_slice(&cabac_bytes);
if !lossless {
if yuv.chroma.is_monochrome() {
crate::deblock::deblock_luma_only(&mut rec_y, w, h, qp_val, yuv.bit_depth);
} else {
crate::deblock::deblock(
&mut rec_y,
w,
h,
&mut rec_cb,
&mut rec_cr,
cw,
ch,
qp_val,
yuv.bit_depth,
);
}
}
Ok((
Nalu {
_nal_type: 20,
data: nalu_data,
},
rec_y,
rec_cb,
rec_cr,
))
}
fn pad_plane(src: &[u16], src_w: usize, src_h: usize, dst_w: usize, dst_h: usize) -> Vec<u16> {
let mut out = vec![128u16; dst_w * dst_h];
for r in 0..dst_h {
let sr = r.min(src_h - 1);
let src_row = &src[sr * src_w..sr * src_w + src_w];
let dst_row = &mut out[r * dst_w..r * dst_w + dst_w];
dst_row[..src_w].copy_from_slice(src_row);
let edge = src_row[src_w - 1];
dst_row[src_w..].fill(edge);
}
out
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn satd_block_n<const N: usize>(orig: &[u16], pred: &[u16]) -> u32 {
let mut total = 0u32;
let mut diff = [0i32; 16];
for (orig_band, pred_band) in orig[..N * N]
.chunks_exact(N * 4)
.zip(pred[..N * N].chunks_exact(N * 4))
{
for bx in (0..N).step_by(4) {
for ((dst_row, orig_row), pred_row) in diff
.as_chunks_mut::<4>()
.0
.iter_mut()
.zip(orig_band.as_chunks::<N>().0.iter())
.zip(pred_band.as_chunks::<N>().0.iter())
{
for ((dst, &orig), &pred) in dst_row
.iter_mut()
.zip(&orig_row[bx..bx + 4])
.zip(&pred_row[bx..bx + 4])
{
*dst = orig as i32 - pred as i32;
}
}
for row in diff.as_chunks_mut::<4>().0 {
let a0 = row[0] + row[2];
let a1 = row[1] + row[3];
let a2 = row[0] - row[2];
let a3 = row[1] - row[3];
row[0] = a0 + a1;
row[1] = a0 - a1;
row[2] = a2 + a3;
row[3] = a2 - a3;
}
for col in 0..4 {
let a0 = diff[col] + diff[8 + col];
let a1 = diff[4 + col] + diff[12 + col];
let a2 = diff[col] - diff[8 + col];
let a3 = diff[4 + col] - diff[12 + col];
diff[col] = a0 + a1;
diff[4 + col] = a0 - a1;
diff[8 + col] = a2 + a3;
diff[12 + col] = a2 - a3;
}
let sum = diff[0].unsigned_abs()
+ diff[1].unsigned_abs()
+ diff[2].unsigned_abs()
+ diff[3].unsigned_abs()
+ diff[4].unsigned_abs()
+ diff[5].unsigned_abs()
+ diff[6].unsigned_abs()
+ diff[7].unsigned_abs()
+ diff[8].unsigned_abs()
+ diff[9].unsigned_abs()
+ diff[10].unsigned_abs()
+ diff[11].unsigned_abs()
+ diff[12].unsigned_abs()
+ diff[13].unsigned_abs()
+ diff[14].unsigned_abs()
+ diff[15].unsigned_abs();
total += (sum + 1) >> 1;
}
}
total
}
#[inline]
fn satd_block(orig: &[u16], pred: &[u16], n: usize) -> u32 {
match n {
4 => satd_block_n::<4>(orig, pred),
8 => satd_block_n::<8>(orig, pred),
16 => satd_block_n::<16>(orig, pred),
32 => satd_block_n::<32>(orig, pred),
_ => panic!("unsupported SATD block size {n}"),
}
}
#[derive(Clone, Copy)]
struct IntraModeCandidate {
mode: u8,
cost: f32,
}
#[inline]
fn update_intra_candidate(candidates: &mut [IntraModeCandidate], mode: u8, cost: f32) {
let Some(pos) = candidates
.iter()
.position(|candidate| cost < candidate.cost)
else {
return;
};
for index in (pos + 1..candidates.len()).rev() {
candidates[index] = candidates[index - 1];
}
candidates[pos] = IntraModeCandidate { mode, cost };
}
#[inline]
fn estimated_luma_mode_bins(mode: u8, mpm: &[u8; 3]) -> u32 {
match mpm.iter().position(|&candidate| candidate == mode) {
Some(0) => 2, Some(1) | Some(2) => 3, None => 6, Some(_) => unreachable!(),
}
}
#[inline]
fn estimate_luma_mode_bits(ictx: &mut IntraModeContexts, mode: u8, mpm: &[u8; 3]) -> f32 {
if let Some(idx) = mpm.iter().position(|&candidate| candidate == mode) {
ictx.prev_intra_luma_pred_flag.estimate_and_update(1) + if idx == 0 { 1.0 } else { 2.0 }
} else {
ictx.prev_intra_luma_pred_flag.estimate_and_update(0) + 5.0
}
}
#[inline]
fn push_sorted_unique_candidate(
candidates: &mut [IntraModeCandidate],
len: &mut usize,
candidate: IntraModeCandidate,
) {
debug_assert!(*len < candidates.len());
if candidates[..*len]
.iter()
.any(|entry| entry.mode == candidate.mode)
{
return;
}
let pos = candidates[..*len]
.iter()
.position(|entry| candidate.cost < entry.cost)
.unwrap_or(*len);
for index in (pos..*len).rev() {
candidates[index + 1] = candidates[index];
}
candidates[pos] = candidate;
*len += 1;
}
#[inline]
fn full_rdo_candidate_count(candidates: &[IntraModeCandidate], lu: usize) -> usize {
let min_count = 2.min(candidates.len());
let max_count = (if lu == 8 { 3 } else { 2 }).min(candidates.len());
if min_count == max_count {
return min_count;
}
let limit = candidates[0].cost * 1.20;
let mut count = min_count;
while count < max_count && candidates[count].cost <= limit {
count += 1;
}
count
}
fn encode_luma_mode<W: CabacWriter>(
enc: &mut W,
ictx: &mut IntraModeContexts,
mode: u8,
mpm: &[u8; 3],
) {
if let Some(idx) = mpm.iter().position(|&candidate| candidate == mode) {
enc.encode_bin(1, &mut ictx.prev_intra_luma_pred_flag);
match idx {
0 => enc.encode_bypass(0),
1 => {
enc.encode_bypass(1);
enc.encode_bypass(0);
}
_ => {
enc.encode_bypass(1);
enc.encode_bypass(1);
}
}
} else {
enc.encode_bin(0, &mut ictx.prev_intra_luma_pred_flag);
let mut rem = mode as i32;
for &candidate in mpm {
if candidate < mode {
rem -= 1;
}
}
for bit in (0..5).rev() {
enc.encode_bypass(((rem >> bit) & 1) as u8);
}
}
}
#[inline]
fn block_sse(orig: &[u16], rec: &[u16], n: usize) -> f32 {
orig[..n * n]
.iter()
.zip(&rec[..n * n])
.map(|(&a, &b)| {
let d = a as i64 - b as i64;
d * d
})
.sum::<i64>() as f32
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ImplicitRdpcm {
Off,
Horizontal,
Vertical,
}
#[inline]
fn implicit_rdpcm_mode(intra_mode: u8) -> ImplicitRdpcm {
match intra_mode {
10 => ImplicitRdpcm::Horizontal,
26 => ImplicitRdpcm::Vertical,
_ => ImplicitRdpcm::Off,
}
}
#[inline]
fn forward_lossless_rdpcm_into(
residual: &[i32],
n: usize,
mode: ImplicitRdpcm,
levels: &mut [i16],
) {
debug_assert!(residual.len() >= n * n);
debug_assert!(levels.len() >= n * n);
match mode {
ImplicitRdpcm::Off => {
for (dst, &src) in levels[..n * n].iter_mut().zip(&residual[..n * n]) {
*dst = src as i16;
}
}
ImplicitRdpcm::Horizontal => {
for (src_row, dst_row) in residual[..n * n]
.chunks_exact(n)
.zip(levels[..n * n].chunks_exact_mut(n))
{
let mut previous = 0i32;
for (&sample, dst) in src_row.iter().zip(dst_row) {
*dst = (sample - previous) as i16;
previous = sample;
}
}
}
ImplicitRdpcm::Vertical => {
let (src_first, src_rest) = residual[..n * n].split_at(n);
let (dst_first, dst_rest) = levels[..n * n].split_at_mut(n);
for (dst, &sample) in dst_first.iter_mut().zip(src_first) {
*dst = sample as i16;
}
for (current, (previous, dst)) in src_rest.chunks_exact(n).zip(
residual[..n * (n - 1)]
.chunks_exact(n)
.zip(dst_rest.chunks_exact_mut(n)),
) {
for ((&sample, &above), out) in current.iter().zip(previous).zip(dst) {
*out = (sample - above) as i16;
}
}
}
}
}
#[cfg(test)]
fn inverse_lossless_rdpcm_into(
levels: &[i16],
n: usize,
mode: ImplicitRdpcm,
residual: &mut [i32],
) {
debug_assert!(levels.len() >= n * n);
debug_assert!(residual.len() >= n * n);
match mode {
ImplicitRdpcm::Off => {
for (dst, &src) in residual[..n * n].iter_mut().zip(&levels[..n * n]) {
*dst = src as i32;
}
}
ImplicitRdpcm::Horizontal => {
for (src_row, dst_row) in levels[..n * n]
.chunks_exact(n)
.zip(residual[..n * n].chunks_exact_mut(n))
{
let mut accumulator = 0i32;
for (&delta, dst) in src_row.iter().zip(dst_row) {
accumulator += delta as i32;
*dst = accumulator;
}
}
}
ImplicitRdpcm::Vertical => {
for col in 0..n {
let mut accumulator = 0i32;
for (src_row, dst_row) in levels[..n * n]
.chunks_exact(n)
.zip(residual[..n * n].chunks_exact_mut(n))
{
accumulator += src_row[col] as i32;
dst_row[col] = accumulator;
}
}
}
}
}
const CHROMA_DM_SYNTAX_IDX: u8 = 4;
#[derive(Clone, Copy, Debug)]
struct ChromaModeCandidate {
pred_mode: u8,
syntax_idx: u8,
cost: f32,
}
#[inline]
fn chroma_mode_candidates(
luma_mode: u8,
chroma: crate::fmt::ChromaFormat,
) -> [ChromaModeCandidate; 5] {
let mut explicit = [0u8, 26, 10, 1];
for mode in &mut explicit {
if *mode == luma_mode {
*mode = 34;
}
}
let map_mode = |mode: u8| {
if matches!(chroma, crate::fmt::ChromaFormat::Yuv422) {
MODE_422_MAP[mode as usize]
} else {
mode
}
};
let dm_mode = map_mode(luma_mode);
let explicit = explicit.map(map_mode);
[
ChromaModeCandidate {
pred_mode: explicit[0],
syntax_idx: 0,
cost: f32::MAX,
},
ChromaModeCandidate {
pred_mode: explicit[1],
syntax_idx: 1,
cost: f32::MAX,
},
ChromaModeCandidate {
pred_mode: explicit[2],
syntax_idx: 2,
cost: f32::MAX,
},
ChromaModeCandidate {
pred_mode: explicit[3],
syntax_idx: 3,
cost: f32::MAX,
},
ChromaModeCandidate {
pred_mode: dm_mode,
syntax_idx: CHROMA_DM_SYNTAX_IDX,
cost: f32::MAX,
},
]
}
#[inline]
fn estimated_chroma_mode_bins(syntax_idx: u8) -> u32 {
if syntax_idx == CHROMA_DM_SYNTAX_IDX {
1
} else {
3
}
}
#[inline]
fn estimate_chroma_mode_bits(ictx: &mut IntraModeContexts, syntax_idx: u8) -> f32 {
if syntax_idx == CHROMA_DM_SYNTAX_IDX {
ictx.intra_chroma_pred_mode.estimate_and_update(0)
} else {
ictx.intra_chroma_pred_mode.estimate_and_update(1) + 2.0
}
}
fn encode_chroma_mode<W: CabacWriter>(enc: &mut W, ictx: &mut IntraModeContexts, syntax_idx: u8) {
if syntax_idx == CHROMA_DM_SYNTAX_IDX {
enc.encode_bin(0, &mut ictx.intra_chroma_pred_mode);
} else {
debug_assert!(syntax_idx < CHROMA_DM_SYNTAX_IDX);
enc.encode_bin(1, &mut ictx.intra_chroma_pred_mode);
enc.encode_bypass((syntax_idx >> 1) & 1);
enc.encode_bypass(syntax_idx & 1);
}
}
#[inline]
fn update_chroma_candidate(
candidates: &mut [ChromaModeCandidate; 5],
mut candidate: ChromaModeCandidate,
) {
let Some(pos) = candidates
.iter()
.position(|entry| candidate.cost < entry.cost)
else {
return;
};
for index in (pos + 1..candidates.len()).rev() {
candidates[index] = candidates[index - 1];
}
core::mem::swap(&mut candidates[pos], &mut candidate);
}
#[inline]
fn full_rdo_chroma_count(
candidates: &[ChromaModeCandidate; 5],
chroma: crate::fmt::ChromaFormat,
) -> usize {
let threshold = match chroma {
crate::fmt::ChromaFormat::Yuv444 => 1.08,
crate::fmt::ChromaFormat::Yuv422 => 1.05,
crate::fmt::ChromaFormat::Yuv420 => 1.03,
crate::fmt::ChromaFormat::Monochrome => return 1,
};
if candidates[1].cost <= candidates[0].cost * threshold {
2
} else {
1
}
}
static MODE_422_MAP: [u8; 35] = [
0, 1, 2, 2, 2, 2, 3, 5, 7, 8, 10, 12, 13, 15, 17, 18, 19, 20, 21, 22, 23, 23, 24, 24, 25, 25,
26, 27, 27, 28, 28, 29, 29, 30, 31,
];
fn mpm_list(cand_a: u8, cand_b: u8) -> [u8; 3] {
const PLANAR: u8 = 0;
const DC: u8 = 1;
const ANG26: u8 = 26;
if cand_a == cand_b {
if cand_a < 2 {
[PLANAR, DC, ANG26]
} else {
let m1 = 2 + ((cand_a as i32 - 2 - 1 + 32) % 32) as u8;
let m2 = 2 + ((cand_a as i32 - 2 + 1) % 32) as u8;
[cand_a, m1, m2]
}
} else {
let third = if cand_a != PLANAR && cand_b != PLANAR {
PLANAR
} else if cand_a != DC && cand_b != DC {
DC
} else {
ANG26
};
[cand_a, cand_b, third]
}
}
fn is_block_decoded(
nr: usize,
nc: usize,
cur_r: usize,
cur_c: usize,
ctb: usize,
width: usize,
) -> bool {
if nc >= width {
return false;
}
let blk = 8usize;
let ctus_x = width / ctb;
let grid = ctb / blk; let order = |r: usize, c: usize| -> i64 {
let ci = (r / ctb) * ctus_x + (c / ctb);
let mut sr = ((r % ctb) / blk) as u64;
let mut sc = ((c % ctb) / blk) as u64;
let mut z: u64 = 0;
let mut bit = 0;
let mut g = grid;
while g > 1 {
z |= (sc & 1) << (2 * bit);
z |= (sr & 1) << (2 * bit + 1);
sr >>= 1;
sc >>= 1;
bit += 1;
g >>= 1;
}
let cells = (grid * grid) as i64;
ci as i64 * cells + z as i64
};
order(nr, nc) < order(cur_r, cur_c)
}
fn chroma_qp_for(qp: u8, chroma: crate::fmt::ChromaFormat) -> u8 {
let qpi = (qp as i32).clamp(0, 57);
match chroma {
crate::fmt::ChromaFormat::Yuv420 => {
static QP_C: [u8; 14] = [29, 30, 31, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37];
if qpi < 30 {
qpi as u8
} else if qpi > 43 {
(qpi - 6) as u8
} else {
QP_C[(qpi - 30) as usize]
}
}
crate::fmt::ChromaFormat::Monochrome => qpi.min(51) as u8,
crate::fmt::ChromaFormat::Yuv422 | crate::fmt::ChromaFormat::Yuv444 => qpi.min(51) as u8,
}
}
#[inline]
fn chroma_lambda_scale(qp_y: u8, chroma: crate::fmt::ChromaFormat) -> f32 {
if !matches!(chroma, crate::fmt::ChromaFormat::Yuv420) {
return 1.0;
}
const DELTA_SCALE: [f32; 7] = [
1.0,
0.793_700_5,
0.629_960_54,
0.5,
0.396_850_26,
0.314_980_27,
0.25,
];
let qp_c = chroma_qp_for(qp_y, chroma);
let delta = qp_y.saturating_sub(qp_c).min(6) as usize;
DELTA_SCALE[delta]
}
struct CuGeometry {
lu_row: usize,
lu_col: usize,
ch_row: usize,
ch_col: usize,
yw_stride: usize,
src_yh: usize,
cw_stride: usize,
src_cw: usize,
src_ch: usize,
blk_stride: usize,
}
struct Entropy<'a, W: CabacWriter> {
enc: &'a mut W,
ctx: &'a mut ContextSet,
ictx: &'a mut IntraModeContexts,
}
struct CuSrcPlanes<'a> {
y: &'a [u16],
cb: &'a [u16],
cr: &'a [u16],
src_yw: usize,
}
struct CuRecPlanes<'a> {
y: &'a mut [u16],
cb: &'a mut [u16],
cr: &'a mut [u16],
}
struct CuParams {
qp: u8,
chroma: crate::fmt::ChromaFormat,
bit_depth: crate::fmt::BitDepth,
lu: usize,
lambda: f32,
lossless: bool,
}
struct ChromaTb {
cb_zz: [i16; 1024],
cb_nz: bool,
cr_zz: [i16; 1024],
cr_nz: bool,
}
impl ChromaTb {
const fn new() -> Self {
Self {
cb_zz: [0; 1024],
cb_nz: false,
cr_zz: [0; 1024],
cr_nz: false,
}
}
}
#[repr(align(64))]
struct CompressionContext {
orig: [u16; 1024],
chroma_orig_cb: [u16; 1024],
chroma_orig_cr: [u16; 1024],
pred: [u16; 1024],
best_pred: [u16; 1024],
reconstructed: [u16; 1024],
residual: [i32; 1024],
best_residual: [i32; 1024],
coeff: [i32; 1024],
best_coeff: [i32; 1024],
dequant: [i32; 1024],
inverse: [i32; 1024],
transform_tmp: [i32; 1024],
levels: [i16; 1024],
scanned: [i16; 1024],
angular: intra::AngularScratch,
chroma_tbs: [ChromaTb; 2],
rdoq: crate::hevc_transform::RdoqScratch,
}
impl CompressionContext {
fn new() -> Self {
Self {
orig: [0; 1024],
chroma_orig_cb: [0; 1024],
chroma_orig_cr: [0; 1024],
pred: [0; 1024],
best_pred: [0; 1024],
reconstructed: [0; 1024],
residual: [0; 1024],
best_residual: [0; 1024],
coeff: [0; 1024],
best_coeff: [0; 1024],
dequant: [0; 1024],
inverse: [0; 1024],
transform_tmp: [0; 1024],
levels: [0; 1024],
scanned: [0; 1024],
angular: intra::AngularScratch::new(),
chroma_tbs: [ChromaTb::new(), ChromaTb::new()],
rdoq: crate::hevc_transform::RdoqScratch::new(),
}
}
}
#[derive(Clone, Copy)]
struct PlaneStrides {
w: usize,
src_yw: usize,
src_yh: usize,
cw: usize,
src_cw: usize,
src_ch: usize,
sub_w: usize,
sub_h: usize,
}
struct CuTreeState<'a> {
yuv: &'a Yuv,
rec_y: &'a mut [u16],
rec_cb: &'a mut [u16],
rec_cr: &'a mut [u16],
strides: PlaneStrides,
qp: u8,
lambda: f32,
mode_map: &'a mut [u8],
cu_depth: &'a mut [u8],
blk_stride: usize,
lossless: bool,
scratch: &'a mut CompressionContext,
}
#[inline]
fn split_cu_context(depths: &[u8], row: usize, col: usize, depth: u8, stride: usize) -> usize {
let br = row / 8;
let bc = col / 8;
let left_deeper = bc > 0 && depths[br * stride + bc - 1] > depth;
let above_deeper = br > 0 && depths[(br - 1) * stride + bc] > depth;
left_deeper as usize + above_deeper as usize
}
#[inline]
fn fill_cu_depth(depths: &mut [u8], row: usize, col: usize, size: usize, depth: u8, stride: usize) {
let side = size / 8;
let br0 = row / 8;
let bc0 = col / 8;
for r in 0..side {
depths[(br0 + r) * stride + bc0..(br0 + r) * stride + bc0 + side].fill(depth);
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn encode_cu_leaf(
cab: &mut CabacEncoder,
ctx: &mut ContextSet,
ictx: &mut IntraModeContexts,
state: &mut CuTreeState<'_>,
row: usize,
col: usize,
size: usize,
depth: u8,
) {
code_one_cu(
Entropy {
enc: cab,
ctx,
ictx,
},
state.yuv,
&mut *state.rec_y,
&mut *state.rec_cb,
&mut *state.rec_cr,
row,
col,
size,
state.strides,
state.qp,
state.lambda,
&mut *state.mode_map,
state.blk_stride,
state.lossless,
&mut *state.scratch,
);
fill_cu_depth(
&mut *state.cu_depth,
row,
col,
size,
depth,
state.blk_stride,
);
}
#[inline]
fn fast_cu_split_score(state: &CuTreeState<'_>, row: usize, col: usize, size: usize) -> f32 {
let shift = state.yuv.bit_depth.bits().saturating_sub(8) as u32;
let half = size / 2;
let mut sum = 0u64;
let mut sum_sq = 0u64;
let mut q_sum = [0u64; 4];
let mut q_sum_sq = [0u64; 4];
let mut min_sample = u16::MAX;
let mut max_sample = 0u16;
let mut grad_sum = 0u64;
let mut signed_grad_x = 0i64;
let mut signed_grad_y = 0i64;
let mut midline_sse = 0u64;
for r in 0..size {
let sy = (row + r).min(state.strides.src_yh - 1);
for c in 0..size {
let sx = (col + c).min(state.strides.src_yw - 1);
let sample = state.yuv.y[sy * state.strides.src_yw + sx] >> shift;
let v = sample as u64;
sum += v;
sum_sq += v * v;
min_sample = min_sample.min(sample);
max_sample = max_sample.max(sample);
let q = (r >= half) as usize * 2 + (c >= half) as usize;
q_sum[q] += v;
q_sum_sq[q] += v * v;
if c > 0 {
let left_x = (col + c - 1).min(state.strides.src_yw - 1);
let left = state.yuv.y[sy * state.strides.src_yw + left_x] >> shift;
let diff = sample as i32 - left as i32;
grad_sum += diff.unsigned_abs() as u64;
signed_grad_x += diff as i64;
if c == half {
midline_sse += (diff * diff) as u64;
}
}
if r > 0 {
let above_y = (row + r - 1).min(state.strides.src_yh - 1);
let above = state.yuv.y[above_y * state.strides.src_yw + sx] >> shift;
let diff = sample as i32 - above as i32;
grad_sum += diff.unsigned_abs() as u64;
signed_grad_y += diff as i64;
if r == half {
midline_sse += (diff * diff) as u64;
}
}
}
}
let pixels = (size * size) as f32;
let mean = sum as f32 / pixels;
let variance = (sum_sq as f32 / pixels - mean * mean).max(0.0);
let q_pixels = (half * half) as f32;
let mut within_variance = 0.0f32;
for q in 0..4 {
let q_mean = q_sum[q] as f32 / q_pixels;
within_variance += (q_sum_sq[q] as f32 / q_pixels - q_mean * q_mean).max(0.0);
}
within_variance *= 0.25;
let range = max_sample - min_sample;
let qp = state.qp as f32;
let flat_range = 2 + (state.qp / 18) as u16;
let flat_variance = 1.5 + qp * 0.12;
if range <= flat_range && variance <= flat_variance {
return 0.0;
}
let between_energy = (variance - within_variance).max(0.0) * pixels;
let coherence = if grad_sum == 0 {
1.0
} else {
((signed_grad_x.unsigned_abs() + signed_grad_y.unsigned_abs()) as f32 / grad_sum as f32)
.min(1.0)
};
let incoherent = 1.0 - coherence;
let predicted_gain = between_energy * incoherent * incoherent
+ midline_sse as f32 * if size == 32 { 0.35 } else { 0.50 };
let extra_bits = if size == 32 { 52.0 } else { 76.0 };
let distortion_scale = (1u32 << (2 * shift)) as f32;
let lambda = (if state.lossless {
state.lambda.max(1.0)
} else {
state.lambda
}) / distortion_scale;
let rate_penalty = (lambda * extra_bits).max(1.0 / distortion_scale);
let mut score = predicted_gain / rate_penalty;
let avg_gradient = grad_sum as f32 / (2 * size * (size - 1)).max(1) as f32;
let texture_limit = if size == 32 {
48.0 + qp * 2.0
} else {
96.0 + qp * 2.8
};
let gradient_limit = if size == 32 {
6.0 + qp * 0.10
} else {
9.0 + qp * 0.12
};
if within_variance >= texture_limit && avg_gradient >= gradient_limit && coherence < 0.55 {
score = score.max(1.25);
}
score
}
fn fast_cu32_plan(state: &CuTreeState<'_>, row: usize, col: usize) -> Cu32Plan {
let parent_score = fast_cu_split_score(state, row, col, 32);
if parent_score < 0.20 {
return Cu32Plan::default();
}
let mut child_scores = [0.0f32; 4];
for (index, (dy, dx)) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)]
.into_iter()
.enumerate()
{
child_scores[index] = fast_cu_split_score(state, row + dy * 16, col + dx * 16, 16);
}
let split_children = child_scores.iter().filter(|&&score| score >= 1.0).count();
let strongest_child = child_scores.iter().copied().fold(0.0f32, f32::max);
let split_32 = parent_score >= 1.0
|| split_children >= 2
|| (strongest_child >= 2.0 && parent_score >= 0.45);
if !split_32 {
return Cu32Plan::default();
}
Cu32Plan {
split_32: true,
split_16: child_scores.map(|score| score >= 1.0),
}
}
#[derive(Clone, Copy, Default)]
struct Cu32Plan {
split_32: bool,
split_16: [bool; 4],
}
#[allow(clippy::too_many_arguments)]
fn commit_cu32_plan(
cab: &mut CabacEncoder,
ctx: &mut ContextSet,
ictx: &mut IntraModeContexts,
state: &mut CuTreeState<'_>,
row: usize,
col: usize,
depth: u8,
plan: Cu32Plan,
) {
let split_ctx = split_cu_context(state.cu_depth, row, col, depth, state.blk_stride);
if !plan.split_32 {
cab.encode_bin(0, &mut ctx.split_cu_flag[split_ctx]);
encode_cu_leaf(cab, ctx, ictx, state, row, col, 32, depth);
return;
}
cab.encode_bin(1, &mut ctx.split_cu_flag[split_ctx]);
for (index, (dy, dx)) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)]
.into_iter()
.enumerate()
{
let child_row = row + dy * 16;
let child_col = col + dx * 16;
let child_depth = depth + 1;
let child_ctx = split_cu_context(
state.cu_depth,
child_row,
child_col,
child_depth,
state.blk_stride,
);
if plan.split_16[index] {
cab.encode_bin(1, &mut ctx.split_cu_flag[child_ctx]);
for (cy, cx) in [(0usize, 0usize), (0, 1), (1, 0), (1, 1)] {
encode_cu_leaf(
cab,
ctx,
ictx,
state,
child_row + cy * 8,
child_col + cx * 8,
8,
child_depth + 1,
);
}
} else {
cab.encode_bin(0, &mut ctx.split_cu_flag[child_ctx]);
encode_cu_leaf(cab, ctx, ictx, state, child_row, child_col, 16, child_depth);
}
}
}
fn encode_cu<W: CabacWriter>(
ent: Entropy<'_, W>,
src: &CuSrcPlanes<'_>,
rec: &mut CuRecPlanes<'_>,
geo: &CuGeometry,
par: &CuParams,
mode_map: &mut [u8],
scratch: &mut CompressionContext,
) {
let Entropy { enc, ctx, ictx } = ent;
let CuGeometry {
lu_row,
lu_col,
ch_row,
ch_col,
yw_stride,
src_yh,
cw_stride,
src_cw,
src_ch,
blk_stride,
} = *geo;
let CuSrcPlanes {
y: src_y,
cb: src_cb,
cr: src_cr,
src_yw,
} = *src;
let CuRecPlanes {
y: rec_y,
cb: rec_cb,
cr: rec_cr,
} = rec;
let CuParams {
qp,
chroma,
bit_depth,
lu,
lambda,
lossless,
} = *par;
let neutral: u16 = bit_depth.neutral(); let max_val: u16 = bit_depth.max_val(); let qp_bd_offset = bit_depth.qp_bd_offset();
let qp_slice = qp; let qp = qp_slice + qp_bd_offset; let n_chroma_tb = chroma.chroma_tbs_per_cu();
let coded_yh = rec_y.len() / yw_stride;
let coded_ch_h = if cw_stride > 0 {
rec_cb.len() / cw_stride.max(1)
} else {
0
};
const PLANAR: u8 = 0;
const DC: u8 = 1;
let ctb = 64usize;
let avail_left =
lu_col > 0 && is_block_decoded(lu_row, lu_col - 1, lu_row, lu_col, ctb, yw_stride);
let above_in_same_ctb = lu_row > 0 && ((lu_row - 1) >= (lu_row / ctb) * ctb);
let avail_above = lu_row > 0
&& above_in_same_ctb
&& is_block_decoded(lu_row - 1, lu_col, lu_row, lu_col, ctb, yw_stride);
let mode_at = |r: usize, c: usize| mode_map[(r / 8) * blk_stride + c / 8];
let cand_a = if avail_left {
mode_at(lu_row, lu_col - 1)
} else {
DC
};
let cand_b = if avail_above {
mode_at(lu_row - 1, lu_col)
} else {
DC
};
let mpm = mpm_list(cand_a, cand_b);
let (yc0, ya, yl) = intra::get_reference_samples(
rec_y,
intra::LumaRefGeometry {
stride: yw_stride,
block_row: lu_row,
block_col: lu_col,
height: coded_yh,
n: lu,
ctu: 64,
ctus_x: yw_stride / 64,
neutral,
},
);
let num_luma = lu * lu;
match lu {
32 => extract_block_n_into::<32>(src_y, src_yw, src_yh, lu_row, lu_col, &mut scratch.orig),
16 => extract_block_n_into::<16>(src_y, src_yw, src_yh, lu_row, lu_col, &mut scratch.orig),
_ => extract_block_n_into::<8>(src_y, src_yw, src_yh, lu_row, lu_col, &mut scratch.orig),
}
let lambda_mode = lambda.sqrt();
let (fa, fl) = intra::filter_references(yc0, &ya, &yl, lu);
let cf = ((ya[0] as i32 + 2 * yc0 as i32 + yl[0] as i32 + 2) >> 2) as u16;
let predict_luma = |mode: u8, pred: &mut [u16; 1024], angular: &mut intra::AngularScratch| {
let (corner, above, left) = if intra::should_filter_refs(mode, lu) {
(cf, &fa[..], &fl[..])
} else {
(yc0, &ya[..], &yl[..])
};
match mode {
PLANAR => intra::predict_planar_into(above, left, lu, pred),
DC => intra::predict_dc_into(above, left, lu, true, pred),
_ => intra::predict_angular_into(
corner,
above,
left,
lu,
mode,
true,
max_val as i32,
pred,
angular,
),
}
};
const MAX_RMD_MODES: usize = 8;
const MAX_RD_MODES: usize = 13;
let fast_mode_count = if lu == 8 { 8 } else { 3 };
let mut rmd = [IntraModeCandidate {
mode: PLANAR,
cost: f32::MAX,
}; MAX_RMD_MODES];
let mut mode_costs = [f32::MAX; 35];
for mode in 0u8..35 {
predict_luma(mode, &mut scratch.pred, &mut scratch.angular);
let satd = satd_block(&scratch.orig[..num_luma], &scratch.pred[..num_luma], lu) as f32;
let cost = satd + lambda_mode * estimated_luma_mode_bins(mode, &mpm) as f32;
mode_costs[mode as usize] = cost;
update_intra_candidate(&mut rmd[..fast_mode_count], mode, cost);
}
let mut rd_candidates = [IntraModeCandidate {
mode: PLANAR,
cost: f32::MAX,
}; MAX_RD_MODES];
let mut rd_mode_count = 0usize;
for &candidate in &rmd[..fast_mode_count] {
push_sorted_unique_candidate(&mut rd_candidates, &mut rd_mode_count, candidate);
}
for &mode in &mpm {
push_sorted_unique_candidate(
&mut rd_candidates,
&mut rd_mode_count,
IntraModeCandidate {
mode,
cost: mode_costs[mode as usize],
},
);
}
if lossless {
for mode in [10u8, 26] {
push_sorted_unique_candidate(
&mut rd_candidates,
&mut rd_mode_count,
IntraModeCandidate {
mode,
cost: mode_costs[mode as usize],
},
);
}
}
let mut full_rd_count = full_rdo_candidate_count(&rd_candidates[..rd_mode_count], lu);
if lossless {
for mode in [10u8, 26] {
if rd_candidates[..full_rd_count]
.iter()
.any(|candidate| candidate.mode == mode)
{
continue;
}
if let Some(index) = rd_candidates[..rd_mode_count]
.iter()
.position(|candidate| candidate.mode == mode)
{
rd_candidates.swap(full_rd_count, index);
full_rd_count += 1;
}
}
}
let luma_log2_ts = lu.trailing_zeros();
let mut luma_mode = rd_candidates[0].mode;
let mut best_rd_cost = f32::MAX;
for candidate in &rd_candidates[..full_rd_count] {
let mode = candidate.mode;
let mut trial_ctx = ctx.clone();
let mut trial_ictx = ictx.clone();
let mut rate = 0.0f32;
if lossless {
rate += trial_ctx.cu_transquant_bypass_flag.estimate_and_update(1);
}
if lu == 8 {
rate += trial_ictx.part_mode.estimate_and_update(1);
}
rate += estimate_luma_mode_bits(&mut trial_ictx, mode, &mpm);
predict_luma(mode, &mut scratch.pred, &mut scratch.angular);
intra::compute_residual_i32_into(
&scratch.orig[..num_luma],
&scratch.pred[..num_luma],
lu,
&mut scratch.residual,
);
let scan_idx = dct::scan_idx_for(mode, luma_log2_ts, true, false);
let scan = dct::coeff_scan(luma_log2_ts, scan_idx);
if lossless {
forward_lossless_rdpcm_into(
&scratch.residual[..num_luma],
lu,
implicit_rdpcm_mode(mode),
&mut scratch.levels,
);
} else {
crate::hevc_transform::fwd_transform_into(
&scratch.residual[..num_luma],
lu,
bit_depth.bits(),
&mut scratch.coeff,
&mut scratch.transform_tmp,
);
crate::hevc_transform::quantize_with_sign_hiding_into(
&scratch.coeff,
lu,
qp,
bit_depth.bits(),
scan,
&mut scratch.levels,
);
}
let mut nonzero = false;
for (dst, &(row, col)) in scratch.scanned[..num_luma].iter_mut().zip(scan) {
let level = scratch.levels[row * lu + col];
*dst = level;
nonzero |= level != 0;
}
rate += trial_ctx.cbf_luma[1].estimate_and_update(nonzero as u8);
if nonzero {
rate += estimate_residual_bits(
&mut trial_ctx,
&scratch.scanned[..num_luma],
luma_log2_ts,
true,
scan_idx,
!lossless,
);
}
let rate_cost = lambda * rate;
if rate_cost >= best_rd_cost {
continue;
}
let cost = if lossless {
rate_cost
} else {
crate::hevc_transform::dequantize_into(
&scratch.levels,
lu,
qp,
bit_depth.bits(),
&mut scratch.dequant,
);
crate::hevc_transform::inv_transform_into(
&scratch.dequant,
lu,
bit_depth.bits(),
&mut scratch.inverse,
&mut scratch.transform_tmp,
);
intra::reconstruct_into(
&scratch.pred[..num_luma],
&scratch.inverse[..num_luma],
lu,
max_val,
&mut scratch.reconstructed,
);
block_sse(
&scratch.orig[..num_luma],
&scratch.reconstructed[..num_luma],
lu,
) + rate_cost
};
if cost < best_rd_cost {
best_rd_cost = cost;
luma_mode = mode;
core::mem::swap(&mut scratch.pred, &mut scratch.best_pred);
if lossless {
core::mem::swap(&mut scratch.residual, &mut scratch.best_residual);
} else {
core::mem::swap(&mut scratch.coeff, &mut scratch.best_coeff);
}
}
}
if lossless {
enc.encode_bin(1, &mut ctx.cu_transquant_bypass_flag);
}
if lu == 8 {
enc.encode_bin(1, &mut ictx.part_mode);
}
let _ = &ictx.part_mode;
encode_luma_mode(enc, ictx, luma_mode, &mpm);
for br in 0..(lu / 8) {
for bc in 0..(lu / 8) {
mode_map[((lu_row / 8) + br) * blk_stride + (lu_col / 8) + bc] = luma_mode;
}
}
let luma_log2_ts = lu.trailing_zeros();
let luma_scan_idx = dct::scan_idx_for(luma_mode, luma_log2_ts, true, false);
let luma_scan = dct::coeff_scan(luma_log2_ts, luma_scan_idx);
if lossless {
forward_lossless_rdpcm_into(
&scratch.best_residual[..num_luma],
lu,
implicit_rdpcm_mode(luma_mode),
&mut scratch.levels,
);
} else {
crate::hevc_transform::rdoq_luma_with_sign_hiding_into(
&scratch.best_coeff,
lu,
qp,
bit_depth.bits(),
luma_scan,
luma_scan_idx,
lambda,
ctx,
&mut scratch.levels,
&mut scratch.rdoq,
);
}
let mut y_nz = false;
for (dst, &(row, col)) in scratch.scanned[..num_luma].iter_mut().zip(luma_scan) {
let level = scratch.levels[row * lu + col];
*dst = level;
y_nz |= level != 0;
}
if lossless {
for (dst, &residual) in scratch.inverse[..num_luma]
.iter_mut()
.zip(&scratch.best_residual[..num_luma])
{
*dst = residual;
}
} else {
crate::hevc_transform::dequantize_into(
&scratch.levels,
lu,
qp,
bit_depth.bits(),
&mut scratch.dequant,
);
crate::hevc_transform::inv_transform_into(
&scratch.dequant,
lu,
bit_depth.bits(),
&mut scratch.inverse,
&mut scratch.transform_tmp,
);
}
intra::reconstruct_into(
&scratch.best_pred[..num_luma],
&scratch.inverse[..num_luma],
lu,
max_val,
&mut scratch.reconstructed,
);
for (src_row, dst_row) in scratch.reconstructed[..num_luma]
.chunks_exact(lu)
.zip(rec_y[lu_row * yw_stride + lu_col..].chunks_mut(yw_stride))
{
dst_row[..lu].copy_from_slice(src_row);
}
let chroma_qp = chroma_qp_for(qp_slice, chroma) + qp_bd_offset;
let chroma_lambda = lambda * chroma_lambda_scale(qp_slice, chroma);
let sub_w = chroma.sub_w();
let sub_h = chroma.sub_h();
let luma_ctus_x = yw_stride / 64;
let ctb = lu / sub_w; let log2_ctb = ctb.trailing_zeros();
let is_444 = matches!(chroma, crate::fmt::ChromaFormat::Yuv444);
let n_ch = ctb * ctb;
let mut chroma_tb_scan_idx = 0u8;
if !chroma.is_monochrome() {
if n_chroma_tb > 1 {
let seed_upper_tb = |src_plane: &[u16], rec_plane: &mut [u16]| {
for r in 0..ctb {
let sy = (ch_row + r).min(src_ch - 1);
let src_start = sy * src_cw + ch_col.min(src_cw - 1);
let available = src_cw.saturating_sub(ch_col).min(ctb);
let dst_start = (ch_row + r) * cw_stride + ch_col;
let dst = &mut rec_plane[dst_start..dst_start + ctb];
if available != 0 {
dst[..available]
.copy_from_slice(&src_plane[src_start..src_start + available]);
let last = dst[available - 1];
dst[available..].fill(last);
} else {
dst.fill(src_plane[sy * src_cw + src_cw - 1]);
}
}
};
seed_upper_tb(src_cb, rec_cb);
seed_upper_tb(src_cr, rec_cr);
}
debug_assert!(n_chroma_tb * n_ch <= 1024);
for t in 0..n_chroma_tb {
let sub_ch_row = ch_row + t * ctb;
let offset = t * n_ch;
extract_block_dyn_into(
src_cb,
src_cw,
src_ch,
sub_ch_row,
ch_col,
ctb,
&mut scratch.chroma_orig_cb[offset..offset + n_ch],
);
extract_block_dyn_into(
src_cr,
src_cw,
src_ch,
sub_ch_row,
ch_col,
ctb,
&mut scratch.chroma_orig_cr[offset..offset + n_ch],
);
}
let all_candidates = chroma_mode_candidates(luma_mode, chroma);
let chroma_satd_lambda = chroma_lambda.sqrt();
let first_proxy_refs = intra::get_reference_samples_chroma_pair(
rec_cb,
rec_cr,
intra::ChromaRefGeometry {
stride: cw_stride,
block_row: ch_row,
block_col: ch_col,
chroma_h: coded_ch_h,
n: ctb,
sub_w,
sub_h,
luma_w: yw_stride,
luma_h: coded_yh,
luma_ctus_x,
cur_luma_row: lu_row,
cur_luma_col: lu_col,
neutral,
},
);
let mut proxy_refs = [first_proxy_refs; 2];
if n_chroma_tb > 1 {
proxy_refs[1] = intra::get_reference_samples_chroma_pair(
rec_cb,
rec_cr,
intra::ChromaRefGeometry {
stride: cw_stride,
block_row: ch_row + ctb,
block_col: ch_col,
chroma_h: coded_ch_h,
n: ctb,
sub_w,
sub_h,
luma_w: yw_stride,
luma_h: coded_yh,
luma_ctus_x,
cur_luma_row: lu_row,
cur_luma_col: lu_col,
neutral,
},
);
}
let mut ranked = [ChromaModeCandidate {
pred_mode: 0,
syntax_idx: 0,
cost: f32::MAX,
}; 5];
if lossless {
for (dst, candidate_index) in ranked.iter_mut().zip([4usize, 0, 3, 1, 2]) {
*dst = all_candidates[candidate_index];
dst.cost = 0.0;
}
} else {
for candidate_index in [4usize, 0, 3, 1, 2] {
let mut candidate = all_candidates[candidate_index];
let mode = candidate.pred_mode;
let mut proxy_cost =
chroma_satd_lambda * estimated_chroma_mode_bins(candidate.syntax_idx) as f32;
#[allow(clippy::needless_range_loop)]
'proxy_tbs: for t in 0..n_chroma_tb {
let filt = ctb > 4 && intra::should_filter_refs(mode, ctb);
let ((bc0, ba, bl), (rc0, ra, rl)) = &proxy_refs[t];
let cb_filtered = if filt {
Some(intra::filter_references(*bc0, ba, bl, ctb))
} else {
None
};
let cr_filtered = if filt {
Some(intra::filter_references(*rc0, ra, rl, ctb))
} else {
None
};
let bcf = if filt {
((ba[0] as i32 + 2 * (*bc0 as i32) + bl[0] as i32 + 2) >> 2) as u16
} else {
*bc0
};
let rcf = if filt {
((ra[0] as i32 + 2 * (*rc0 as i32) + rl[0] as i32 + 2) >> 2) as u16
} else {
*rc0
};
let (baf, blf) = match &cb_filtered {
Some((above, left)) => (&above[..], &left[..]),
None => (&ba[..], &bl[..]),
};
let (raf, rlf) = match &cr_filtered {
Some((above, left)) => (&above[..], &left[..]),
None => (&ra[..], &rl[..]),
};
let source_offset = t * n_ch;
for component in 0..2 {
let (orig, corner, above, left) = if component == 0 {
(
&scratch.chroma_orig_cb[source_offset..source_offset + n_ch],
bcf,
baf,
blf,
)
} else {
(
&scratch.chroma_orig_cr[source_offset..source_offset + n_ch],
rcf,
raf,
rlf,
)
};
intra::predict_chroma_tb_into(
mode,
corner,
above,
left,
ctb,
max_val as i32,
&mut scratch.pred,
&mut scratch.angular,
);
proxy_cost += satd_block(orig, &scratch.pred[..n_ch], ctb) as f32;
if proxy_cost >= ranked[1].cost {
break 'proxy_tbs;
}
}
}
candidate.cost = proxy_cost;
update_chroma_candidate(&mut ranked, candidate);
}
}
let full_rd_count = if lossless {
ranked.len()
} else {
full_rdo_chroma_count(&ranked, chroma)
};
let mut residual_ctx_after_luma = ctx.clone();
if y_nz {
advance_residual_contexts(
&mut residual_ctx_after_luma,
&scratch.scanned[..num_luma],
luma_log2_ts,
true,
luma_scan_idx,
!lossless,
);
}
let mut evaluate_chroma = |candidate: ChromaModeCandidate,
estimate_rate: bool,
winner_rdoq: bool,
cost_limit: f32|
-> f32 {
let mode = candidate.pred_mode;
let scan_idx = dct::scan_idx_for(mode, log2_ctb, false, is_444);
let scan = dct::coeff_scan(log2_ctb, scan_idx);
let mut distortion = 0.0f32;
for t in 0..n_chroma_tb {
let sub_ch_row = ch_row + t * ctb;
let filt = ctb > 4 && intra::should_filter_refs(mode, ctb);
let ((bc0, ba, bl), (rc0, ra, rl)) = intra::get_reference_samples_chroma_pair(
rec_cb,
rec_cr,
intra::ChromaRefGeometry {
stride: cw_stride,
block_row: sub_ch_row,
block_col: ch_col,
chroma_h: coded_ch_h,
n: ctb,
sub_w,
sub_h,
luma_w: yw_stride,
luma_h: coded_yh,
luma_ctus_x,
cur_luma_row: lu_row,
cur_luma_col: lu_col,
neutral,
},
);
let (baf, blf, bcf) = if filt {
let (above, left) = intra::filter_references(bc0, &ba, &bl, ctb);
let corner = ((ba[0] as i32 + 2 * bc0 as i32 + bl[0] as i32 + 2) >> 2) as u16;
(above, left, corner)
} else {
(ba, bl, bc0)
};
let (raf, rlf, rcf) = if filt {
let (above, left) = intra::filter_references(rc0, &ra, &rl, ctb);
let corner = ((ra[0] as i32 + 2 * rc0 as i32 + rl[0] as i32 + 2) >> 2) as u16;
(above, left, corner)
} else {
(ra, rl, rc0)
};
let source_offset = t * n_ch;
for component in 0..2 {
let (orig, rec_plane, corner, above, left) = if component == 0 {
(
&scratch.chroma_orig_cb[source_offset..source_offset + n_ch],
&mut rec_cb[..],
bcf,
&baf[..],
&blf[..],
)
} else {
(
&scratch.chroma_orig_cr[source_offset..source_offset + n_ch],
&mut rec_cr[..],
rcf,
&raf[..],
&rlf[..],
)
};
intra::predict_chroma_tb_into(
mode,
corner,
above,
left,
ctb,
max_val as i32,
&mut scratch.pred,
&mut scratch.angular,
);
intra::compute_residual_i32_into(
orig,
&scratch.pred[..n_ch],
ctb,
&mut scratch.residual,
);
if lossless {
forward_lossless_rdpcm_into(
&scratch.residual[..n_ch],
ctb,
implicit_rdpcm_mode(mode),
&mut scratch.levels,
);
} else {
crate::hevc_transform::fwd_transform_into(
&scratch.residual[..n_ch],
ctb,
bit_depth.bits(),
&mut scratch.coeff,
&mut scratch.transform_tmp,
);
if winner_rdoq {
crate::hevc_transform::rdoq_chroma_with_sign_hiding_into(
&scratch.coeff,
ctb,
chroma_qp,
bit_depth.bits(),
scan,
scan_idx,
chroma_lambda,
&residual_ctx_after_luma,
&mut scratch.levels,
&mut scratch.rdoq,
);
} else {
crate::hevc_transform::quantize_with_sign_hiding_into(
&scratch.coeff,
ctb,
chroma_qp,
bit_depth.bits(),
scan,
&mut scratch.levels,
);
}
}
let tb = &mut scratch.chroma_tbs[t];
let (zigzag, nonzero) = if component == 0 {
(&mut tb.cb_zz, &mut tb.cb_nz)
} else {
(&mut tb.cr_zz, &mut tb.cr_nz)
};
*nonzero = false;
for (dst, &(scan_row, scan_col)) in zigzag[..n_ch].iter_mut().zip(scan) {
let level = scratch.levels[scan_row * ctb + scan_col];
*dst = level;
*nonzero |= level != 0;
}
if lossless {
for (src_row, dst_row) in orig
.chunks_exact(ctb)
.zip(rec_plane[sub_ch_row * cw_stride + ch_col..].chunks_mut(cw_stride))
{
dst_row[..ctb].copy_from_slice(src_row);
}
} else {
crate::hevc_transform::dequantize_into(
&scratch.levels,
ctb,
chroma_qp,
bit_depth.bits(),
&mut scratch.dequant,
);
crate::hevc_transform::inv_transform_into(
&scratch.dequant,
ctb,
bit_depth.bits(),
&mut scratch.inverse,
&mut scratch.transform_tmp,
);
intra::reconstruct_into(
&scratch.pred[..n_ch],
&scratch.inverse[..n_ch],
ctb,
max_val,
&mut scratch.reconstructed,
);
distortion += block_sse(orig, &scratch.reconstructed[..n_ch], ctb);
if estimate_rate && distortion >= cost_limit {
return distortion;
}
for (src_row, dst_row) in scratch.reconstructed[..n_ch]
.chunks_exact(ctb)
.zip(rec_plane[sub_ch_row * cw_stride + ch_col..].chunks_mut(cw_stride))
{
dst_row[..ctb].copy_from_slice(src_row);
}
}
}
}
if !estimate_rate || distortion >= cost_limit {
return distortion;
}
let mut trial_ctx = residual_ctx_after_luma.clone();
let mut trial_ictx = ictx.clone();
let mut rate = estimate_chroma_mode_bits(&mut trial_ictx, candidate.syntax_idx);
for tb in &scratch.chroma_tbs[..n_chroma_tb] {
rate += trial_ctx.cbf_chroma[0].estimate_and_update(tb.cb_nz as u8);
}
for tb in &scratch.chroma_tbs[..n_chroma_tb] {
rate += trial_ctx.cbf_chroma[0].estimate_and_update(tb.cr_nz as u8);
}
for tb in &scratch.chroma_tbs[..n_chroma_tb] {
if tb.cb_nz {
rate += estimate_residual_bits(
&mut trial_ctx,
&tb.cb_zz[..n_ch],
log2_ctb,
false,
scan_idx,
!lossless,
);
}
}
for tb in &scratch.chroma_tbs[..n_chroma_tb] {
if tb.cr_nz {
rate += estimate_residual_bits(
&mut trial_ctx,
&tb.cr_zz[..n_ch],
log2_ctb,
false,
scan_idx,
!lossless,
);
}
}
distortion + chroma_lambda * rate
};
let best_chroma = if full_rd_count == 1 {
let winner = ranked[0];
let _ = evaluate_chroma(winner, false, true, f32::MAX);
winner
} else {
let mut winner = ranked[0];
let mut best_cost = f32::MAX;
for &candidate in &ranked[..full_rd_count] {
let cost = evaluate_chroma(candidate, true, false, best_cost);
if cost < best_cost {
best_cost = cost;
winner = candidate;
}
}
let _ = evaluate_chroma(winner, false, true, f32::MAX);
winner
};
chroma_tb_scan_idx = dct::scan_idx_for(best_chroma.pred_mode, log2_ctb, false, is_444);
encode_chroma_mode(enc, ictx, best_chroma.syntax_idx);
}
for t in &scratch.chroma_tbs[..n_chroma_tb] {
encode_cbf_chroma(enc, ctx, t.cb_nz, 0);
}
for t in &scratch.chroma_tbs[..n_chroma_tb] {
encode_cbf_chroma(enc, ctx, t.cr_nz, 0);
}
encode_cbf_luma(enc, ctx, y_nz, 0);
if y_nz {
encode_residual(
enc,
ctx,
&scratch.scanned[..num_luma],
luma_log2_ts,
true,
luma_scan_idx,
!lossless,
);
}
let chroma_scan_idx = chroma_tb_scan_idx;
for t in &scratch.chroma_tbs[..n_chroma_tb] {
if t.cb_nz {
encode_residual(
enc,
ctx,
&t.cb_zz[..ctb * ctb],
log2_ctb,
false,
chroma_scan_idx,
!lossless,
);
}
}
for t in &scratch.chroma_tbs[..n_chroma_tb] {
if t.cr_nz {
encode_residual(
enc,
ctx,
&t.cr_zz[..ctb * ctb],
log2_ctb,
false,
chroma_scan_idx,
!lossless,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn code_one_cu<W: CabacWriter>(
ent: Entropy<'_, W>,
yuv: &Yuv,
rec_y: &mut [u16],
rec_cb: &mut [u16],
rec_cr: &mut [u16],
lu_row: usize,
lu_col: usize,
lu: usize,
strides: PlaneStrides,
qp: u8,
lambda: f32,
mode_map: &mut [u8],
blk_stride: usize,
lossless: bool,
scratch: &mut CompressionContext,
) {
let PlaneStrides {
w,
src_yw,
src_yh,
cw,
src_cw,
src_ch,
sub_w,
sub_h,
} = strides;
let ch_row = lu_row / sub_h;
let ch_col = lu_col / sub_w;
let geo = CuGeometry {
lu_row,
lu_col,
ch_row,
ch_col,
yw_stride: w,
src_yh,
cw_stride: cw,
src_cw,
src_ch,
blk_stride,
};
let src = CuSrcPlanes {
y: &yuv.y,
cb: &yuv.cb,
cr: &yuv.cr,
src_yw,
};
let mut rec = CuRecPlanes {
y: rec_y,
cb: rec_cb,
cr: rec_cr,
};
let par = CuParams {
qp,
chroma: yuv.chroma,
bit_depth: yuv.bit_depth,
lu,
lambda,
lossless,
};
encode_cu(ent, &src, &mut rec, &geo, &par, mode_map, scratch);
}
#[inline]
fn extract_block_n_into<const N: usize>(
plane: &[u16],
src_w: usize,
src_h: usize,
row: usize,
col: usize,
out: &mut [u16],
) {
debug_assert!(out.len() >= N * N);
for (r, dst) in out[..N * N].as_chunks_mut::<N>().0.iter_mut().enumerate() {
let src_row = (row + r).min(src_h - 1);
let available = src_w.saturating_sub(col).min(N);
if available != 0 {
let start = src_row * src_w + col.min(src_w - 1);
dst[..available].copy_from_slice(&plane[start..start + available]);
let last = dst[available - 1];
dst[available..].fill(last);
} else {
dst.fill(plane[src_row * src_w + src_w - 1]);
}
}
}
#[inline]
fn extract_block_dyn_into(
plane: &[u16],
src_w: usize,
src_h: usize,
row: usize,
col: usize,
n: usize,
out: &mut [u16],
) {
debug_assert!(out.len() >= n * n);
for (r, dst) in out[..n * n].chunks_exact_mut(n).enumerate() {
let src_row = (row + r).min(src_h - 1);
let available = src_w.saturating_sub(col).min(n);
if available != 0 {
let start = src_row * src_w + col.min(src_w - 1);
dst[..available].copy_from_slice(&plane[start..start + available]);
let last = dst[available - 1];
dst[available..].fill(last);
} else {
dst.fill(plane[src_row * src_w + src_w - 1]);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intra_candidate_list_keeps_best_costs_sorted() {
let mut candidates = [IntraModeCandidate {
mode: u8::MAX,
cost: f32::MAX,
}; 3];
update_intra_candidate(&mut candidates, 10, 10.0);
update_intra_candidate(&mut candidates, 20, 4.0);
update_intra_candidate(&mut candidates, 30, 7.0);
update_intra_candidate(&mut candidates, 40, 12.0);
update_intra_candidate(&mut candidates, 50, 5.0);
assert_eq!(candidates.map(|candidate| candidate.mode), [20, 50, 30]);
assert!(
candidates
.array_windows::<2>()
.all(|pair| pair[0].cost <= pair[1].cost)
);
}
#[test]
fn full_rdo_candidate_budget_is_bounded() {
let close = [
IntraModeCandidate {
mode: 0,
cost: 100.0,
},
IntraModeCandidate {
mode: 1,
cost: 105.0,
},
IntraModeCandidate {
mode: 2,
cost: 115.0,
},
IntraModeCandidate {
mode: 3,
cost: 150.0,
},
];
assert_eq!(full_rdo_candidate_count(&close, 8), 3);
assert_eq!(full_rdo_candidate_count(&close, 16), 2);
let separated = [
IntraModeCandidate {
mode: 0,
cost: 100.0,
},
IntraModeCandidate {
mode: 1,
cost: 130.0,
},
IntraModeCandidate {
mode: 2,
cost: 140.0,
},
];
assert_eq!(full_rdo_candidate_count(&separated, 8), 2);
}
#[test]
fn cu_depth_map_drives_split_context() {
let stride = 8;
let mut depths = [0u8; 64];
fill_cu_depth(&mut depths, 0, 0, 32, 1, stride);
assert!(depths[..4].iter().all(|&depth| depth == 1));
fill_cu_depth(&mut depths, 0, 0, 16, 2, stride);
assert_eq!(split_cu_context(&depths, 0, 16, 1, stride), 1);
assert_eq!(split_cu_context(&depths, 16, 0, 1, stride), 1);
}
#[test]
fn luma_mode_bin_estimate_matches_mpm_binarization() {
let mpm = [0, 1, 26];
assert_eq!(estimated_luma_mode_bins(0, &mpm), 2);
assert_eq!(estimated_luma_mode_bins(1, &mpm), 3);
assert_eq!(estimated_luma_mode_bins(26, &mpm), 3);
assert_eq!(estimated_luma_mode_bins(17, &mpm), 6);
}
#[test]
fn chroma_candidates_replace_the_dm_duplicate() {
let modes = chroma_mode_candidates(26, crate::fmt::ChromaFormat::Yuv444);
assert_eq!(
modes.map(|candidate| candidate.pred_mode),
[0, 34, 10, 1, 26]
);
assert_eq!(modes.map(|candidate| candidate.syntax_idx), [0, 1, 2, 3, 4]);
let mapped = chroma_mode_candidates(26, crate::fmt::ChromaFormat::Yuv422);
assert_eq!(mapped[1].pred_mode, MODE_422_MAP[34]);
assert_eq!(mapped[4].pred_mode, MODE_422_MAP[26]);
for i in 0..mapped.len() {
for j in i + 1..mapped.len() {
assert_ne!(mapped[i].pred_mode, mapped[j].pred_mode);
}
}
}
#[test]
fn chroma_mode_bin_estimate_matches_binarization() {
assert_eq!(estimated_chroma_mode_bins(CHROMA_DM_SYNTAX_IDX), 1);
for syntax_idx in 0..CHROMA_DM_SYNTAX_IDX {
assert_eq!(estimated_chroma_mode_bins(syntax_idx), 3);
}
}
#[test]
fn chroma_mode_binarization_uses_dm_flag_then_two_bypass_bits() {
#[derive(Default)]
struct Recorder {
regular: Vec<u8>,
bypass: Vec<u8>,
}
impl CabacWriter for Recorder {
fn encode_bin(&mut self, bin_val: u8, ctx: &mut crate::cabac::engine::CtxModel) {
self.regular.push(bin_val);
let _ = ctx.estimate_and_update(bin_val);
}
fn encode_bypass(&mut self, bin_val: u8) {
self.bypass.push(bin_val);
}
}
let mut ictx = IntraModeContexts::init_islice(26);
let mut dm = Recorder::default();
encode_chroma_mode(&mut dm, &mut ictx, CHROMA_DM_SYNTAX_IDX);
assert_eq!(dm.regular, [0]);
assert!(dm.bypass.is_empty());
let mut ictx = IntraModeContexts::init_islice(26);
let mut explicit = Recorder::default();
encode_chroma_mode(&mut explicit, &mut ictx, 2);
assert_eq!(explicit.regular, [1]);
assert_eq!(explicit.bypass, [1, 0]);
}
#[test]
fn chroma_full_rdo_is_only_expanded_for_close_proxy_modes() {
let make = |first: f32, second: f32| {
let mut candidates = [ChromaModeCandidate {
pred_mode: 0,
syntax_idx: 0,
cost: f32::MAX,
}; 5];
candidates[0].cost = first;
candidates[1].cost = second;
candidates
};
assert_eq!(
full_rdo_chroma_count(&make(100.0, 102.0), crate::fmt::ChromaFormat::Yuv420),
2
);
assert_eq!(
full_rdo_chroma_count(&make(100.0, 104.0), crate::fmt::ChromaFormat::Yuv420),
1
);
assert_eq!(
full_rdo_chroma_count(&make(100.0, 107.0), crate::fmt::ChromaFormat::Yuv444),
2
);
assert_eq!(
full_rdo_chroma_count(&make(100.0, 109.0), crate::fmt::ChromaFormat::Yuv444),
1
);
}
#[test]
fn chroma_lambda_tracks_the_420_qp_mapping() {
assert_eq!(
chroma_lambda_scale(29, crate::fmt::ChromaFormat::Yuv420),
1.0
);
assert_eq!(
chroma_lambda_scale(43, crate::fmt::ChromaFormat::Yuv420),
0.25
);
assert_eq!(
chroma_lambda_scale(51, crate::fmt::ChromaFormat::Yuv420),
0.25
);
assert_eq!(
chroma_lambda_scale(43, crate::fmt::ChromaFormat::Yuv444),
1.0
);
}
#[test]
fn lossless_profile_enables_rext() {
let mut lossy = BitWriter::new();
write_profile_tier_level(
&mut lossy,
93,
crate::fmt::ChromaFormat::Yuv420,
crate::fmt::BitDepth::Eight,
false,
);
let lossy = lossy.finish();
assert_eq!(lossy[0] & 0x1f, 3);
let mut lossless = BitWriter::new();
write_profile_tier_level(
&mut lossless,
93,
crate::fmt::ChromaFormat::Yuv420,
crate::fmt::BitDepth::Eight,
true,
);
let lossless = lossless.finish();
assert_eq!(lossless[0] & 0x1f, 4);
assert_eq!(lossless[5] & 0x01, 0); assert_eq!(lossless[6] & 0xc0, 0); assert_ne!(lossless[6] & 0x20, 0); }
#[test]
fn lossless_range_extension_sets_only_implicit_rdpcm() {
let mut enabled = BitWriter::new();
write_sps_range_extension(&mut enabled, true);
assert_eq!(enabled.finish().as_slice(), &[0x20, 0x00]);
let mut disabled = BitWriter::new();
write_sps_range_extension(&mut disabled, false);
assert_eq!(disabled.finish().as_slice(), &[0x00, 0x00]);
}
#[test]
fn implicit_rdpcm_mode_is_inferred_from_final_intra_mode() {
assert_eq!(implicit_rdpcm_mode(10), ImplicitRdpcm::Horizontal);
assert_eq!(implicit_rdpcm_mode(26), ImplicitRdpcm::Vertical);
assert_eq!(implicit_rdpcm_mode(0), ImplicitRdpcm::Off);
assert_eq!(implicit_rdpcm_mode(34), ImplicitRdpcm::Off);
}
#[test]
fn implicit_rdpcm_roundtrips_all_supported_tb_sizes() {
for n in [4usize, 8, 16, 32] {
let mut residual = [0i32; 1024];
for (index, sample) in residual[..n * n].iter_mut().enumerate() {
let row = index / n;
let col = index % n;
*sample = (((row * 977 + col * 613 + row * col * 29) % 8191) as i32) - 4095;
}
for mode in [
ImplicitRdpcm::Off,
ImplicitRdpcm::Horizontal,
ImplicitRdpcm::Vertical,
] {
let mut levels = [0i16; 1024];
let mut decoded = [0i32; 1024];
forward_lossless_rdpcm_into(&residual, n, mode, &mut levels);
inverse_lossless_rdpcm_into(&levels, n, mode, &mut decoded);
assert_eq!(
&decoded[..n * n],
&residual[..n * n],
"roundtrip failed for {n}x{n} {mode:?}"
);
assert!(
levels[..n * n]
.iter()
.all(|&level| (-8190..=8190).contains(&(level as i32)))
);
}
}
}
#[test]
fn implicit_rdpcm_differences_the_expected_axis() {
let residual = [
1, 3, 6, 10, 2, 5, 9, 14, 4, 8, 13, 19, 7, 12, 18, 25,
];
let mut levels = [0i16; 1024];
forward_lossless_rdpcm_into(&residual, 4, ImplicitRdpcm::Horizontal, &mut levels);
assert_eq!(
&levels[..16],
&[1, 2, 3, 4, 2, 3, 4, 5, 4, 4, 5, 6, 7, 5, 6, 7]
);
forward_lossless_rdpcm_into(&residual, 4, ImplicitRdpcm::Vertical, &mut levels);
assert_eq!(
&levels[..16],
&[1, 3, 6, 10, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6]
);
}
#[test]
fn bit_writer_basic() {
let mut bw = BitWriter::new();
bw.write_bits(0b10110, 5);
bw.rbsp_trailing_bits();
assert_eq!(bw.finish()[0], 0b1011_0100);
}
#[test]
fn ue_coding() {
let mut bw = BitWriter::new();
bw.write_ue(0); bw.rbsp_trailing_bits();
assert_eq!(bw.finish()[0] >> 7, 1);
}
#[test]
fn vps_starts_with_nalu_header() {
let vps = build_vps(
256,
256,
crate::fmt::ChromaFormat::Yuv420,
crate::fmt::BitDepth::Eight,
false,
);
assert_eq!(vps.data[0], 0x40, "VPS first byte should be 0x40");
}
#[test]
fn sps_conformance_window() {
let sps = build_sps(
64,
48,
crate::fmt::ChromaFormat::Yuv420,
crate::fmt::BitDepth::Eight,
false,
Some(&crate::color::Cicp::srgb()),
);
assert!(sps.data.len() > 10);
}
#[test]
fn pps_builds_cleanly() {
let pps = build_pps(30, false);
assert_eq!(pps.data[0], 0x44, "PPS first byte should be 0x44");
}
}