use crate::av2::helpers::{
cfl_sse_i32, coeff_rate_f32, dc_pred, dc_pred_cfl_subsampled, pixel_sse_rounded,
};
use crate::av2::mhccp;
use crate::av2::proj::Basis;
use crate::av2::{itx422, tables};
use crate::util::FastRound;
pub(crate) const CFL_ADD_BITS_ALPHA: i32 = 5;
pub(crate) const CFL_ALPHABET_SIZE: u8 = 8;
#[inline(always)]
fn round_pow2_signed(v: i32, n: u32) -> i32 {
let s = v >> 31;
let av = (v ^ s) - s;
let q = (av + (1i32 << (n - 1))) >> n;
(q ^ s) - s
}
const MHCCP_MIN_REF_VAR: f64 = 64.0;
fn mhccp_ref_luma_variance(l: &mhccp::MhccpRefBuf, above: usize, left: usize) -> f64 {
let stride = mhccp::MHCCP_REF_STRIDE;
let mut sum = 0.0f64;
let mut sum2 = 0.0f64;
let mut n = 0.0f64;
for j in 1..l.ref_height.saturating_sub(1) {
for i in 1..l.ref_width.saturating_sub(1) {
if i >= left && j >= above {
continue;
}
let v = (l.data[j * stride + i] >> 3) as f64;
sum += v;
sum2 += v * v;
n += 1.0;
}
}
if n < 1.0 {
return f64::INFINITY;
}
(sum2 - sum * sum / n) / n
}
#[inline(always)]
pub(crate) fn scaled_luma_q0(alpha_q3: i32, ac_q3: i32) -> i32 {
round_pow2_signed(alpha_q3 * ac_q3, 6 + CFL_ADD_BITS_ALPHA as u32)
}
#[inline(always)]
fn scaled_luma_q0_i32(alpha_q3: i32, ac_q3: i32) -> i32 {
let p = alpha_q3 * ac_q3;
let s = p >> 31;
let av = (p ^ s) - s;
let q = (av + (1 << 10)) >> 11; (q ^ s) - s
}
#[inline]
pub(crate) fn idx_to_alpha_q3(sign: u8, mag: u8) -> i32 {
let a = match sign {
1 => -(mag as i32 + 1),
2 => mag as i32 + 1,
_ => 0,
};
a * (1 << CFL_ADD_BITS_ALPHA)
}
pub(crate) fn subsample_luma_q3(
luma: &[i32],
lstride: usize,
cw: usize,
ch: usize,
ssx: bool,
ssy: bool,
) -> Vec<i32> {
let mut out = vec![0i32; cw * ch];
match (ssx, ssy) {
(true, true) => {
for (y, orow) in out.chunks_exact_mut(cw).enumerate() {
let top = &luma[y * 2 * lstride..y * 2 * lstride + 2 * cw];
let bot = &luma[(y * 2 + 1) * lstride..(y * 2 + 1) * lstride + 2 * cw];
for (o, (t, b)) in orow.iter_mut().zip(
top.as_chunks::<2>()
.0
.iter()
.zip(bot.as_chunks::<2>().0.iter()),
) {
*o = (t[0] + t[1] + b[0] + b[1]) << 1;
}
}
}
(true, false) => {
for (y, orow) in out.chunks_exact_mut(cw).enumerate() {
let row = &luma[y * lstride..y * lstride + 2 * cw];
for (o, p) in orow.iter_mut().zip(row.as_chunks::<2>().0.iter()) {
*o = (p[0] + p[1]) << 2;
}
}
}
(false, false) => {
for (y, orow) in out.chunks_exact_mut(cw).enumerate() {
let row = &luma[y * lstride..y * lstride + cw];
for (o, &v) in orow.iter_mut().zip(row) {
*o = v << 3;
}
}
}
_ => unreachable!("ssy without ssx is not a supported chroma layout"),
}
out
}
#[derive(Clone, Copy)]
pub(crate) struct LumaRingSpec {
pub(crate) stride: usize,
pub(crate) luma_y: usize,
pub(crate) luma_x: usize,
pub(crate) chroma_width: usize,
pub(crate) chroma_height: usize,
pub(crate) subsample_x: bool,
pub(crate) subsample_y: bool,
pub(crate) have_top: bool,
pub(crate) have_left: bool,
}
#[allow(dead_code)]
pub(crate) fn subsample_luma_ring_q3(recy: &[f32], spec: &LumaRingSpec) -> Vec<i32> {
let LumaRingSpec {
stride: pw,
luma_y: ly,
luma_x: lx,
chroma_width: cw,
chroma_height: ch,
subsample_x: ssx,
subsample_y: ssy,
have_top,
have_left,
} = *spec;
let sx = ssx as usize;
let sy = ssy as usize;
let stride = cw + 1;
let mut ring = vec![0i32; (cw + 1) * (ch + 1)];
let px = |y: usize, x: usize| -> i32 { recy[y * pw + x].fast_round() as i32 };
for cy in -1i32..ch as i32 {
for cx in -1i32..cw as i32 {
let lyy = ly as i32 + (cy << sy);
let lxx = lx as i32 + (cx << sx);
let get = |dy: i32, dx: i32| -> i32 {
let yy = (lyy + dy).max(0) as usize;
let xx = (lxx + dx).max(0) as usize;
px(yy, xx)
};
let q3 = match (ssx, ssy) {
(true, true) => (get(0, 0) + get(0, 1) + get(1, 0) + get(1, 1)) << 1,
(true, false) => (get(0, 0) + get(0, 1)) << 2,
(false, false) => get(0, 0) << 3,
_ => unreachable!(),
};
let idx = ((cy + 1) as usize) * stride + (cx + 1) as usize;
ring[idx] = q3;
}
}
if !have_top {
for x in 0..=cw {
ring[x] = ring[stride + x];
}
}
if !have_left {
for y in 0..=ch {
ring[y * stride] = ring[y * stride + 1];
}
}
ring
}
pub(crate) fn compute_ac(recon_q3: &[i32], w: usize, h: usize) -> (Vec<i32>, i32) {
let npel = w * h;
let log2 = npel.trailing_zeros();
let sum: i32 = recon_q3.iter().copied().sum::<i32>() + ((npel as i32) >> 1);
let avg = sum >> log2;
(recon_q3.iter().map(|&v| v - avg).collect(), avg)
}
pub(crate) fn cfl_predict(dc: i32, ac: &[i32], alpha_q3: i32, bitdepth: i32) -> Vec<i32> {
let maxv = (1i32 << bitdepth) - 1;
ac.iter()
.map(|&a| (dc + scaled_luma_q0_i32(alpha_q3, a)).clamp(0, maxv))
.collect()
}
pub(crate) fn pick_alpha_plane(src: &[i32], dc: i32, ac: &[i32], bitdepth: i32) -> (u8, u8, f32) {
let maxv = (1i32 << bitdepth) - 1;
let sse_of = |alpha_q3: i32| cfl_sse_i32(src, ac, alpha_q3, dc, maxv);
let base_sse = sse_of(0);
let mut best = (0u8, 0u8, base_sse);
for sign in [1u8, 2u8] {
for mag in 0u8..CFL_ALPHABET_SIZE {
let sse = sse_of(idx_to_alpha_q3(sign, mag));
if sse < best.2 {
best = (sign, mag, sse);
}
}
}
best
}
pub(crate) static CFL_IS_CDF: [u16; 3] = [12327, 21158, 28125];
pub(crate) const CFL_INDEX_CDF: u16 = 20261;
pub(crate) const CFL_MHCCP_SWITCH_CDF: u16 = 17269;
pub(crate) static FILTER_DIR_CDF: [[u16; 3]; 4] = [
[21845, 10923, 0],
[23973, 17663, 0],
[22335, 16794, 0],
[15683, 11079, 0],
];
pub(crate) static FILTER_DIR_PARA: [(u8, u8, u8); 4] = [(2, 3, 4), (2, 2, 3), (1, 2, 2), (1, 2, 2)];
pub(crate) const CFL_MHCCP_SWITCH_PARA: (i8, i8, i8) = (-1, -1, 0);
pub(crate) static CFL_SIGN_ICDF: [u16; 8] = [30347, 28436, 21512, 20002, 11382, 4043, 681, 0];
pub(crate) static CFL_ALPHA_ICDF: [[u16; 8]; 6] = [
[11089, 7463, 2122, 1256, 231, 122, 72, 0],
[24506, 16466, 8686, 3346, 1370, 482, 243, 0],
[15533, 6602, 2390, 1463, 395, 219, 100, 0],
[15150, 7036, 4903, 2430, 1643, 1246, 530, 0],
[15226, 9702, 4861, 4040, 2066, 1603, 1333, 0],
[15093, 7966, 2300, 1985, 927, 504, 346, 0],
];
#[inline]
pub(crate) fn cfl_sign_u(js: u8) -> u8 {
(((js as u32 + 1) * 11) >> 5) as u8
}
#[inline]
pub(crate) fn cfl_sign_v(js: u8) -> u8 {
(js + 1) - 3 * cfl_sign_u(js)
}
#[derive(Clone)]
pub(crate) struct CflChoice {
pub(crate) js: u8,
pub(crate) sign_u: u8,
pub(crate) sign_v: u8,
pub(crate) mag_u: u8,
pub(crate) mag_v: u8,
pub(crate) ctx_u: usize,
pub(crate) ctx_v: usize,
pub(crate) pred_u: Vec<i32>,
pub(crate) pred_v: Vec<i32>,
pub(crate) mhccp: Option<MhccpDecision>,
}
#[derive(Clone, Copy)]
pub(crate) struct MhccpDecision {
pub(crate) mh_dir: u8,
pub(crate) size_group: u8,
}
pub(crate) fn cfl_candidate(
src_u: &[i32],
src_v: &[i32],
ac: &[i32],
dc_u: i32,
dc_v: i32,
bitdepth: i32,
) -> Option<CflChoice> {
let (sign_u, mag_u, _) = pick_alpha_plane(src_u, dc_u, ac, bitdepth);
let (sign_v, mag_v, _) = pick_alpha_plane(src_v, dc_v, ac, bitdepth);
if sign_u == 0 && sign_v == 0 {
return None;
}
let js = 3 * sign_u + sign_v - 1;
let ctx_u = if sign_u != 0 { (js - 2) as usize } else { 0 };
let ctx_v = if sign_v != 0 {
(sign_v * 3 + sign_u - 3) as usize
} else {
0
};
let pred_u = cfl_predict(dc_u, ac, idx_to_alpha_q3(sign_u, mag_u), bitdepth);
let pred_v = cfl_predict(dc_v, ac, idx_to_alpha_q3(sign_v, mag_v), bitdepth);
Some(CflChoice {
js,
sign_u,
sign_v,
mag_u,
mag_v,
ctx_u,
ctx_v,
pred_u,
pred_v,
mhccp: None,
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn cfl_avg_l(
recy: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
cw: usize,
ch: usize,
ssx: bool,
ssy: bool,
bd: i32,
) -> i32 {
let have_top = sb_y > 0;
let have_left = sb_x > 0;
let ss_h = if cw > 32 { 2 } else { 1 };
let ss_v = if ch > 32 { 2 } else { 1 };
let l = |y: usize, x: usize| -> i32 { recy[y * pw + x].fast_round() as i32 };
let mut sum: i32 = 0;
let mut count: i32 = 0;
if have_top {
let mut kk = 0;
while kk < cw {
let s = match (ssx, ssy) {
(false, false) => l(sb_y - 1, sb_x + kk) << 3,
(true, true) => {
let a = l(sb_y - 1, sb_x + 2 * kk);
let b = l(sb_y - 1, sb_x + 2 * kk + 1);
(a + b + a + b) << 1
}
(true, false) => (l(sb_y - 1, sb_x + 2 * kk) + l(sb_y - 1, sb_x + 2 * kk + 1)) << 2,
_ => unreachable!(),
};
sum += s;
count += 1;
kk += ss_h;
}
}
if have_left {
let mut jj = 0;
while jj < ch {
let s = match (ssx, ssy) {
(false, false) => l(sb_y + jj, sb_x - 1) << 3,
(true, true) => {
let a = l(sb_y + 2 * jj, sb_x - 2);
let b = l(sb_y + 2 * jj, sb_x - 1);
let c = l(sb_y + 2 * jj + 1, sb_x - 2);
let d = l(sb_y + 2 * jj + 1, sb_x - 1);
(a + b + c + d) << 1
}
(true, false) => (l(sb_y + jj, sb_x - 2) + l(sb_y + jj, sb_x - 1)) << 2,
_ => unreachable!(),
};
sum += s;
count += 1;
jj += ss_v;
}
}
if count > 0 {
let val = (sum + count / 2) / count;
let max_v = (1 << (bd + 3)) - 1;
val.min(max_v)
} else {
8 << (bd - 1)
}
}
#[derive(Clone, Copy)]
pub(crate) struct MhccpBounds {
pub(crate) luma_width: usize,
pub(crate) luma_height: usize,
pub(crate) chroma_width: usize,
pub(crate) chroma_height: usize,
}
impl MhccpBounds {
#[inline]
pub(crate) fn from_luma(width: usize, height: usize, ssx: bool, ssy: bool) -> Self {
let luma_width = (width + 7) & !7;
let luma_height = (height + 7) & !7;
Self {
luma_width,
luma_height,
chroma_width: luma_width >> (ssx as usize),
chroma_height: luma_height >> (ssy as usize),
}
}
}
pub(crate) struct MhccpCtx<'a> {
pub(crate) recy: &'a [f32],
pub(crate) pw: usize,
pub(crate) recu: &'a [f32],
pub(crate) recv: &'a [f32],
pub(crate) pcw: usize,
pub(crate) bounds: MhccpBounds,
pub(crate) ly: usize,
pub(crate) lx: usize,
pub(crate) cy: usize,
pub(crate) cx: usize,
pub(crate) ssx: bool,
pub(crate) ssy: bool,
pub(crate) have_top: bool,
pub(crate) have_left: bool,
pub(crate) is_top_sb_boundary: bool,
pub(crate) size_group: u8,
pub(crate) coded: &'a [u8],
}
fn build_luma_ref(
ctx: &MhccpCtx,
width: usize,
height: usize,
above: usize,
left: usize,
tr_ext: usize,
bl_ext: usize,
) -> mhccp::MhccpRefBuf {
let ref_width = left + width + tr_ext;
let ref_height = above + height + bl_ext;
let mut buf = mhccp::MhccpRefBuf::new(
ref_width,
ref_height,
above,
left,
width,
height,
ctx.is_top_sb_boundary,
);
let sx = ctx.ssx as usize;
let sy = ctx.ssy as usize;
let lx0 = ctx.lx as i32 - ((left as i32) << sx);
let ly0 = ctx.ly as i32 - ((above as i32) << sy);
let stride = ctx.pw as i32;
let plane_height = ctx.recy.len() / ctx.pw;
let bound_width = ctx.bounds.luma_width.min(ctx.pw).max(1) as i32;
let bound_height = ctx.bounds.luma_height.min(plane_height).max(1) as i32;
let lp = |y: i32, x: i32| -> i32 {
let yy = y.clamp(0, bound_height - 1);
let xx = x.clamp(0, bound_width - 1);
ctx.recy[(yy * stride + xx) as usize].fast_round() as i32
};
let above_luma = (above as i32) << sy;
let line1_luma = ((mhccp::LINE_NUM as i32) + 1) << (sy as i32);
for j in 0..ref_height {
for i in 0..ref_width {
let ly = ly0 + ((j as i32) << sy);
let lx = lx0 + ((i as i32) << sx);
let h = (j as i32) << sy; let (cent_off, bot_off) =
if above_luma == line1_luma && ctx.is_top_sb_boundary && h < above_luma {
(line1_luma - (h + 1), line1_luma - (h + 2))
} else {
(0, 0)
};
let q3 = match (ctx.ssx, ctx.ssy) {
(true, true) => {
(lp(ly + cent_off, lx)
+ lp(ly + cent_off, lx + 1)
+ lp(ly + 1 + bot_off, lx)
+ lp(ly + 1 + bot_off, lx + 1))
<< 1
}
(true, false) => (lp(ly + cent_off, lx) + lp(ly + cent_off, lx + 1)) << 2,
(false, false) => lp(ly + cent_off, lx) << 3,
_ => unreachable!(),
};
buf.set(i, j, q3);
}
}
buf
}
#[derive(Clone, Copy)]
struct ChromaRefSpec {
stride: usize,
bound_width: usize,
bound_height: usize,
y: usize,
x: usize,
width: usize,
height: usize,
above: usize,
left: usize,
top_right_extension: usize,
bottom_left_extension: usize,
is_top_sb: bool,
}
fn build_chroma_ref(rec: &[f32], spec: &ChromaRefSpec) -> mhccp::MhccpRefBuf {
let ChromaRefSpec {
stride: pcw,
bound_width,
bound_height,
y: cy,
x: cx,
width,
height,
above,
left,
top_right_extension: tr_ext,
bottom_left_extension: bl_ext,
is_top_sb,
} = *spec;
let ref_width = left + width + tr_ext;
let ref_height = above + height + bl_ext;
let mut buf =
mhccp::MhccpRefBuf::new(ref_width, ref_height, above, left, width, height, is_top_sb);
let stride = pcw as i32;
let plane_height = rec.len() / pcw;
let bound_width = bound_width.min(pcw).max(1) as i32;
let bound_height = bound_height.min(plane_height).max(1) as i32;
let cp = |y: i32, x: i32| -> i32 {
let yy = y.clamp(0, bound_height - 1);
let xx = x.clamp(0, bound_width - 1);
rec[(yy * stride + xx) as usize].round() as i32
};
let x0 = cx as i32 - left as i32;
let y0 = cy as i32 - above as i32;
let line_num1 = mhccp::LINE_NUM as i32 + 1;
for j in 0..ref_height as i32 {
for i in 0..ref_width as i32 {
let mut ref_h = 0i32;
let mut ref_w = 0i32;
if above as i32 == line_num1 {
if is_top_sb && j < above as i32 {
ref_h = line_num1 - (j + 1);
} else if j == 0 {
ref_h = 1;
}
}
if left as i32 == line_num1 && i == 0 {
ref_w = 1;
}
let v = cp(y0 + j + ref_h, x0 + i + ref_w);
buf.set(i as usize, j as usize, v);
}
}
buf
}
#[derive(Clone, Copy)]
pub(crate) struct ChromaRdSpec<'a> {
pub(crate) basis: &'a Basis,
pub(crate) qstep: i32,
pub(crate) lambda: f32,
pub(crate) bit_depth: i32,
}
#[derive(Clone, Copy)]
pub(crate) struct CflDecisionInput<'a> {
pub(crate) reconstructed_luma: &'a [f32],
pub(crate) luma_stride: usize,
pub(crate) luma_y: usize,
pub(crate) luma_x: usize,
pub(crate) source_u: &'a [f32],
pub(crate) source_v: &'a [f32],
pub(crate) dc_u: f32,
pub(crate) dc_v: f32,
pub(crate) width: usize,
pub(crate) height: usize,
pub(crate) subsample_x: bool,
pub(crate) subsample_y: bool,
pub(crate) luma_average_q3: i32,
}
#[derive(Clone, Copy)]
pub(crate) struct PredictorScoreInput<'a> {
pub(crate) source_u: &'a [f32],
pub(crate) source_v: &'a [f32],
pub(crate) predictor_u: &'a [i32],
pub(crate) predictor_v: &'a [i32],
pub(crate) width: usize,
pub(crate) height: usize,
pub(crate) mode_bits: f32,
}
pub(crate) fn cfl_decide(input: &CflDecisionInput<'_>, rd: &ChromaRdSpec<'_>) -> Option<CflChoice> {
let CflDecisionInput {
reconstructed_luma: recy,
luma_stride: pw,
luma_y: sb_y,
luma_x: sb_x,
source_u: src_u,
source_v: src_v,
dc_u: dc_u_f,
dc_v: dc_v_f,
width: cw,
height: ch,
subsample_x: ssx,
subsample_y: ssy,
luma_average_q3: avg_l,
} = *input;
let ChromaRdSpec {
basis: chroma,
qstep,
lambda,
bit_depth: bd,
} = *rd;
let n = cw * ch;
let lw = cw << (ssx as usize);
let lh = ch << (ssy as usize);
let mut luma = vec![0i32; lw * lh];
for r in 0..lh {
let b = (sb_y + r) * pw + sb_x;
let luma_s = &mut luma[r * lw..];
let recy_s = &recy[b..];
for (l, &r) in luma_s[..lw].iter_mut().zip(recy_s.iter()) {
*l = r.fast_round() as i32;
}
}
let luma_q3 = subsample_luma_q3(&luma, lw, cw, ch, ssx, ssy);
let ac: Vec<i32> = luma_q3.iter().map(|&v| v - avg_l).collect();
let su: Vec<i32> = src_u.iter().map(|&s| s.fast_round() as i32).collect();
let sv: Vec<i32> = src_v.iter().map(|&s| s.fast_round() as i32).collect();
let dc_u = dc_u_f.fast_round() as i32;
let dc_v = dc_v_f.fast_round() as i32;
let cand = cfl_candidate(&su, &sv, &ac, dc_u, dc_v, bd)?;
let scan = &tables::SCAN;
let res_dc_u: Vec<f32> = src_u.iter().map(|&s| s - dc_u_f).collect();
let res_dc_v: Vec<f32> = src_v.iter().map(|&s| s - dc_v_f).collect();
let lev_dc_u = chroma.project(&res_dc_u, 0.0);
let lev_dc_v = chroma.project(&res_dc_v, 0.0);
let rec_dc_u = itx422::reconstruct_chroma(dc_u_f, &lev_dc_u, qstep, scan, cw, ch, bd);
let rec_dc_v = itx422::reconstruct_chroma(dc_v_f, &lev_dc_v, qstep, scan, cw, ch, bd);
let j_dc = (pixel_sse_rounded(src_u, &rec_dc_u) + pixel_sse_rounded(src_v, &rec_dc_v))
+ lambda * (coeff_rate_f32(&lev_dc_u) + coeff_rate_f32(&lev_dc_v));
let res_cf_u: Vec<f32> = src_u[..n]
.iter()
.zip(cand.pred_u[..n].iter())
.map(|(&s, &c)| s - c as f32)
.collect();
let res_cf_v: Vec<f32> = src_v[..n]
.iter()
.zip(cand.pred_v[..n].iter())
.map(|(&s, &v)| s - v as f32)
.collect();
let lev_cf_u = chroma.project(&res_cf_u, 0.0);
let lev_cf_v = chroma.project(&res_cf_v, 0.0);
let rec_cf_u = itx422::reconstruct_chroma_cfl(&cand.pred_u, &lev_cf_u, qstep, scan, cw, ch, bd);
let rec_cf_v = itx422::reconstruct_chroma_cfl(&cand.pred_v, &lev_cf_v, qstep, scan, cw, ch, bd);
let alpha_bits = 2.0
+ 3.0
+ if cand.sign_u != 0 { 3.0 } else { 0.0 }
+ if cand.sign_v != 0 { 3.0 } else { 0.0 };
let j_cfl = (pixel_sse_rounded(src_u, &rec_cf_u) + pixel_sse_rounded(src_v, &rec_cf_v)) as f32
+ lambda * ((coeff_rate_f32(&lev_cf_u) + coeff_rate_f32(&lev_cf_v)) as f32 + alpha_bits);
if j_cfl < j_dc { Some(cand) } else { None }
}
pub(crate) fn score_predictor(input: &PredictorScoreInput<'_>, rd: &ChromaRdSpec<'_>) -> f32 {
let PredictorScoreInput {
source_u: src_u,
source_v: src_v,
predictor_u: pred_u,
predictor_v: pred_v,
width: cw,
height: ch,
mode_bits,
} = *input;
let ChromaRdSpec {
basis: chroma,
qstep,
lambda,
bit_depth: bd,
} = *rd;
let n = cw * ch;
let scan = &tables::SCAN;
let res_u: Vec<f32> = src_u[..n]
.iter()
.zip(pred_u.iter())
.map(|(&s, &c)| s - c as f32)
.collect();
let res_v: Vec<f32> = src_v[..n]
.iter()
.zip(pred_v.iter())
.map(|(&s, &c)| s - c as f32)
.collect();
let lev_u = chroma.project(&res_u, 0.0);
let lev_v = chroma.project(&res_v, 0.0);
let rec_u = itx422::reconstruct_chroma_cfl(pred_u, &lev_u, qstep, scan, cw, ch, bd);
let rec_v = itx422::reconstruct_chroma_cfl(pred_v, &lev_v, qstep, scan, cw, ch, bd);
(pixel_sse_rounded(src_u, &rec_u) + pixel_sse_rounded(src_v, &rec_v))
+ lambda * ((coeff_rate_f32(&lev_u) + coeff_rate_f32(&lev_v)) + mode_bits)
}
pub(crate) fn bsize_from_wh4(bw4: usize, bh4: usize) -> Option<usize> {
let w = bw4 * 4;
let h = bh4 * 4;
let idx = match (w, h) {
(4, 4) => 0,
(4, 8) => 1,
(8, 4) => 2,
(8, 8) => 3,
(8, 16) => 4,
(16, 8) => 5,
(16, 16) => 6,
(16, 32) => 7,
(32, 16) => 8,
(32, 32) => 9,
(32, 64) => 10,
(64, 32) => 11,
(64, 64) => 12,
(4, 16) => 19,
(16, 4) => 20,
(8, 32) => 21,
(32, 8) => 22,
(16, 64) => 23,
(64, 16) => 24,
(4, 32) => 25,
(32, 4) => 26,
(8, 64) => 27,
(64, 8) => 28,
_ => return None,
};
Some(idx)
}
pub(crate) fn mhccp_size_group_wh4(bw4: usize, bh4: usize) -> u8 {
bsize_from_wh4(bw4, bh4).map(mhccp_size_group).unwrap_or(3)
}
pub(crate) fn is_mhccp_allowed(bw4: usize, bh4: usize, ssx: bool, ssy: bool) -> bool {
let luma_w = bw4 * 4;
let luma_h = bh4 * 4;
let cw = luma_w >> (ssx as usize);
let ch = luma_h >> (ssy as usize);
if cw == 4 && ch == 4 {
return false;
}
if cw > 32 || ch > 32 {
return false;
}
luma_w <= 64 && luma_h <= 64
}
#[inline]
pub(crate) fn mhccp_size_group(bsize: usize) -> u8 {
mhccp::SIZE_GROUP_LOOKUP.get(bsize).copied().unwrap_or(3)
}
#[inline]
pub(crate) fn mhccp_size_group_32() -> u8 {
mhccp_size_group(9)
}
#[derive(Clone, Copy)]
pub(crate) struct MhccpDecisionInput<'a> {
pub(crate) source_u: &'a [f32],
pub(crate) source_v: &'a [f32],
pub(crate) width: usize,
pub(crate) height: usize,
pub(crate) rd: ChromaRdSpec<'a>,
pub(crate) scan: &'a [u16],
pub(crate) baseline_j: f32,
}
pub(crate) fn mhccp_decide(ctx: &MhccpCtx, input: &MhccpDecisionInput<'_>) -> Option<CflChoice> {
let MhccpDecisionInput {
source_u: src_u,
source_v: src_v,
width: cw,
height: ch,
rd,
scan,
baseline_j,
} = *input;
let ChromaRdSpec {
basis: chroma,
qstep,
lambda,
bit_depth: bd,
} = rd;
let n = cw * ch;
let line_num1 = mhccp::LINE_NUM + 1;
let above = if ctx.have_top { line_num1 } else { 0 };
let left = if ctx.have_left { line_num1 } else { 0 };
if above == 0 && left == 0 {
return None; }
if ctx.cx.saturating_add(cw) > ctx.bounds.chroma_width
|| ctx.cy.saturating_add(ch) > ctx.bounds.chroma_height
{
return None;
}
let sx = ctx.ssx as usize;
let sy = ctx.ssy as usize;
const SB_MI: usize = 16;
let sb_r = ((ctx.ly & 63) >> 2) as i32; let sb_c = ((ctx.lx & 63) >> 2) as i32;
let w_mi = ((cw << sx) >> 2) as i32; let h_mi = ((ch << sy) >> 2) as i32; let coded_at = |r: i32, c: i32| -> bool {
r >= 0
&& c >= 0
&& (r as usize) < SB_MI
&& (c as usize) < SB_MI
&& ctx
.coded
.get(r as usize * SB_MI + c as usize)
.copied()
.unwrap_or(0)
!= 0
};
let has_tr = ctx.have_top && {
let tr_row = sb_r - 1;
let tr_col = sb_c + w_mi;
if tr_row < 0 {
true } else if tr_col >= SB_MI as i32 {
false } else {
coded_at(tr_row, tr_col)
}
};
let has_bl = ctx.have_left && {
let bl_row = sb_r + h_mi;
let bl_col = sb_c - 1;
if bl_col < 0 {
((sb_r + h_mi) as usize) < SB_MI
} else if bl_row >= SB_MI as i32 {
false } else {
coded_at(bl_row, bl_col)
}
};
let tr_ext = if has_tr && cw > 4 {
let cap_c = 128usize >> sx;
let cap_room = cap_c.saturating_sub(left + cw);
let tile_room = ctx
.bounds
.chroma_width
.saturating_sub(ctx.cx.saturating_add(cw));
cap_room.min(tile_room).min(cw)
} else {
0
};
let bl_ext = if has_bl && ch > 4 {
let cap_c = 128usize >> sy;
let cap_room = cap_c.saturating_sub(above + ch);
let tile_room = ctx
.bounds
.chroma_height
.saturating_sub(ctx.cy.saturating_add(ch));
cap_room.min(tile_room).min(ch)
} else {
0
};
let luma_ref = build_luma_ref(ctx, cw, ch, above, left, tr_ext, bl_ext);
let cref_u = build_chroma_ref(
ctx.recu,
&ChromaRefSpec {
stride: ctx.pcw,
bound_width: ctx.bounds.chroma_width,
bound_height: ctx.bounds.chroma_height,
y: ctx.cy,
x: ctx.cx,
width: cw,
height: ch,
above,
left,
top_right_extension: tr_ext,
bottom_left_extension: bl_ext,
is_top_sb: ctx.is_top_sb_boundary,
},
);
let cref_v = build_chroma_ref(
ctx.recv,
&ChromaRefSpec {
stride: ctx.pcw,
bound_width: ctx.bounds.chroma_width,
bound_height: ctx.bounds.chroma_height,
y: ctx.cy,
x: ctx.cx,
width: cw,
height: ch,
above,
left,
top_right_extension: tr_ext,
bottom_left_extension: bl_ext,
is_top_sb: ctx.is_top_sb_boundary,
},
);
let switch_bits = 1.0_f32;
if mhccp_ref_luma_variance(&luma_ref, above, left) < MHCCP_MIN_REF_VAR {
return None;
}
let mut best: Option<(f32, CflChoice)> = None;
for dir in 0u8..mhccp::MHCCP_MODE_NUM as u8 {
let pu = mhccp::derive_params(&luma_ref, &cref_u, dir, bd);
let pv = mhccp::derive_params(&luma_ref, &cref_v, dir, bd);
let mut pred_u = vec![0i32; n];
let mut pred_v = vec![0i32; n];
mhccp::predict_block(
&luma_ref,
&pu,
dir,
ctx.have_top,
ctx.have_left,
bd,
&mut pred_u,
);
mhccp::predict_block(
&luma_ref,
&pv,
dir,
ctx.have_top,
ctx.have_left,
bd,
&mut pred_v,
);
let res_u: Vec<f32> = src_u[..n]
.iter()
.zip(pred_u.iter())
.map(|(&s, &c)| s - c as f32)
.collect();
let res_v: Vec<f32> = src_v[..n]
.iter()
.zip(pred_v.iter())
.map(|(&s, &c)| s - c as f32)
.collect();
let lev_u = chroma.project_scan(&res_u, 0.0, scan);
let lev_v = chroma.project_scan(&res_v, 0.0, scan);
let rec_u = itx422::reconstruct_chroma_cfl(&pred_u, &lev_u, qstep, scan, cw, ch, bd);
let rec_v = itx422::reconstruct_chroma_cfl(&pred_v, &lev_v, qstep, scan, cw, ch, bd);
let dir_bits = filter_dir_bits(ctx.size_group as usize, dir);
let j = (pixel_sse_rounded(src_u, &rec_u) + pixel_sse_rounded(src_v, &rec_v)) as f32
+ lambda
* ((coeff_rate_f32(&lev_u) + coeff_rate_f32(&lev_v) + switch_bits + dir_bits)
as f32);
if best.as_ref().map(|(bj, _)| j < *bj).unwrap_or(true) {
best = Some((
j,
CflChoice {
js: 0,
sign_u: 0,
sign_v: 0,
mag_u: 0,
mag_v: 0,
ctx_u: 0,
ctx_v: 0,
pred_u,
pred_v,
mhccp: Some(MhccpDecision {
mh_dir: dir,
size_group: ctx.size_group,
}),
},
));
}
}
match best {
Some((j, choice)) => {
if j < baseline_j { Some(choice) } else { None }
}
_ => None,
}
}
#[derive(Clone, Copy)]
pub(crate) struct MhccpLeafInput<'a> {
pub(crate) reconstructed_luma: &'a [f32],
pub(crate) luma_stride: usize,
pub(crate) reconstructed_u: &'a [f32],
pub(crate) reconstructed_v: &'a [f32],
pub(crate) chroma_stride: usize,
pub(crate) bounds: MhccpBounds,
pub(crate) luma_y: usize,
pub(crate) luma_x: usize,
pub(crate) chroma_y: usize,
pub(crate) chroma_x: usize,
pub(crate) width: usize,
pub(crate) height: usize,
pub(crate) subsample_x: bool,
pub(crate) subsample_y: bool,
pub(crate) have_top: bool,
pub(crate) have_left: bool,
pub(crate) source_u: &'a [f32],
pub(crate) source_v: &'a [f32],
pub(crate) dc_u: f32,
pub(crate) dc_v: f32,
pub(crate) rd: ChromaRdSpec<'a>,
pub(crate) scan: &'a [u16],
pub(crate) coded_mask: &'a [u8],
}
pub(crate) fn mhccp_eval_leaf(input: &MhccpLeafInput<'_>) -> Option<CflChoice> {
let MhccpLeafInput {
reconstructed_luma: recy,
luma_stride: pw,
reconstructed_u: recu,
reconstructed_v: recv,
chroma_stride: pcw,
bounds,
luma_y: ly,
luma_x: lx,
chroma_y: cy,
chroma_x: cx,
width: cw,
height: ch,
subsample_x: ssx,
subsample_y: ssy,
have_top,
have_left,
source_u: src_u,
source_v: src_v,
dc_u,
dc_v,
rd,
scan,
coded_mask,
} = *input;
let ChromaRdSpec {
basis: chroma,
qstep,
lambda,
bit_depth: bd,
} = rd;
let n = cw * ch;
let bres_u: Vec<f32> = src_u[..n].iter().map(|&s| s - dc_u).collect();
let bres_v: Vec<f32> = src_v[..n].iter().map(|&s| s - dc_v).collect();
let blev_u = chroma.project_scan(&bres_u, 0.0, scan);
let blev_v = chroma.project_scan(&bres_v, 0.0, scan);
let brec_u = itx422::reconstruct_chroma(dc_u, &blev_u, qstep, scan, cw, ch, bd);
let brec_v = itx422::reconstruct_chroma(dc_v, &blev_v, qstep, scan, cw, ch, bd);
let sse = (pixel_sse_rounded(&src_u[..n], &brec_u) + pixel_sse_rounded(&src_v[..n], &brec_v))
+ lambda * (coeff_rate_f32(&blev_u) + coeff_rate_f32(&blev_v));
let bw4 = (cw << (ssx as usize)) / 4;
let bh4 = (ch << (ssy as usize)) / 4;
let ctx = MhccpCtx {
recy,
pw,
recu,
recv,
pcw,
bounds,
ly,
lx,
cy,
cx,
ssx,
ssy,
have_top,
have_left,
is_top_sb_boundary: (ly & 63) == 0,
size_group: mhccp_size_group_wh4(bw4, bh4),
coded: coded_mask,
};
mhccp_decide(
&ctx,
&MhccpDecisionInput {
source_u: src_u,
source_v: src_v,
width: cw,
height: ch,
rd,
scan,
baseline_j: sse,
},
)
}
fn filter_dir_bits(size_group: usize, dir: u8) -> f32 {
const CUM: [[u32; 2]; 4] = [
[10923, 21845],
[8795, 15105],
[10433, 15974],
[17085, 21689],
];
let g = size_group.min(3);
let (c0, c1) = (CUM[g][0], CUM[g][1]);
let p = match dir {
0 => c0,
1 => c1 - c0,
_ => 32768 - c1,
} as f32
/ 32768.0;
-(p.max(1e-6)).log2()
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn cfl_prediction<const N: usize>(
pcw: usize,
up: &[f32],
vp: &[f32],
cy: usize,
cx: usize,
ch: &&CflChoice,
ru: &mut [f32],
rv: &mut [f32],
) {
for (r, (ru, rv)) in ru
.as_chunks_mut::<N>()
.0
.iter_mut()
.zip(rv.as_chunks_mut::<N>().0.iter_mut())
.enumerate()
{
let b = (cy + r) * pcw + cx;
let up_s = &up[b..b + N];
let vp_s = &vp[b..b + N];
let pred_u = &ch.pred_u[r * N..r * N + N];
let pred_v = &ch.pred_v[r * N..r * N + N];
for (((((ru, rv), &up), &vp), &pred_u), &pred_v) in ru
.iter_mut()
.zip(rv.iter_mut())
.zip(up_s.iter())
.zip(vp_s.iter())
.zip(pred_u.iter())
.zip(pred_v)
{
*ru = up - pred_u as f32;
*rv = vp - pred_v as f32;
}
}
}
pub(crate) fn cfl_partition_prediction<const N: usize>(
pcw: usize,
up: &[f32],
vp: &[f32],
cy: usize,
cx: usize,
suf: &mut [f32],
svf: &mut [f32],
) {
for (r, (suf, svf)) in suf
.as_chunks_mut::<N>()
.0
.iter_mut()
.zip(svf.as_chunks_mut::<N>().0.iter_mut())
.enumerate()
{
let b = (cy + r) * pcw + cx;
let up_s = &up[b..b + N];
let vp_s = &vp[b..b + N];
for (((suf, svf), &up), &vp) in suf
.iter_mut()
.zip(svf.iter_mut())
.zip(up_s.iter())
.zip(vp_s.iter())
{
*suf = up;
*svf = vp;
}
}
}
fn cfl_avg_l_444(
recy: &[f32],
pw: usize,
sb_y: usize,
sb_x: usize,
w: usize,
h: usize,
bd: i32,
) -> i32 {
let have_top = sb_y > 0;
let have_left = sb_x > 0;
let ss_hor = if w > 32 { 2 } else { 1 };
let ss_ver = if h > 32 { 2 } else { 1 };
let mut sum: i32 = 0;
let mut count: i32 = 0;
if have_top {
let base = (sb_y - 1) * pw + sb_x;
let mut i = 0;
while i < w {
sum += (recy[base + i].fast_round() as i32) << 3;
count += 1;
i += ss_hor;
}
}
if have_left {
let mut i = 0;
while i < h {
sum += (recy[(sb_y + i) * pw + sb_x - 1].fast_round() as i32) << 3;
count += 1;
i += ss_ver;
}
}
if count > 0 {
let val = (sum + count / 2) / count;
let max_v = (1 << (bd + 3)) - 1;
val.min(max_v)
} else {
8 << (bd - 1)
}
}
#[derive(Clone, Copy)]
pub(crate) struct Cfl64Input<'a> {
pub(crate) reconstructed_luma: &'a [f32],
pub(crate) source_u: &'a [f32],
pub(crate) source_v: &'a [f32],
pub(crate) reconstructed_u: &'a [f32],
pub(crate) reconstructed_v: &'a [f32],
pub(crate) stride: usize,
pub(crate) y: usize,
pub(crate) x: usize,
pub(crate) neutral: f32,
}
pub(crate) fn cfl_decide_64(input: &Cfl64Input<'_>, rd: &ChromaRdSpec<'_>) -> Option<CflChoice> {
let Cfl64Input {
reconstructed_luma: recy,
source_u: up,
source_v: vp,
reconstructed_u: recu,
reconstructed_v: recv,
stride: pw,
y: sb_y,
x: sb_x,
neutral,
} = *input;
let ChromaRdSpec {
basis: chroma,
qstep,
lambda,
bit_depth: bd,
} = *rd;
let scan = &tables::SCAN;
let mut luma = vec![0i32; 64 * 64];
let mut su = vec![0i32; 64 * 64];
let mut sv = vec![0i32; 64 * 64];
let mut suf = vec![0f32; 64 * 64];
let mut svf = vec![0f32; 64 * 64];
for (r, ((((luma, su), sv), suf), svf)) in luma
.as_chunks_mut::<64>()
.0
.iter_mut()
.zip(su.as_chunks_mut::<64>().0.iter_mut())
.zip(sv.as_chunks_mut::<64>().0.iter_mut())
.zip(suf.as_chunks_mut::<64>().0.iter_mut())
.zip(svf.as_chunks_mut::<64>().0.iter_mut())
.enumerate()
{
let b = (sb_y + r) * pw + sb_x;
let recy = &recy[b..b + 64];
let up = &up[b..b + 64];
let vp = &vp[b..b + 64];
for (((((((luma, su), sv), suf), svf), recy), &up), &vp) in luma
.iter_mut()
.zip(su.iter_mut())
.zip(sv.iter_mut())
.zip(suf.iter_mut())
.zip(svf.iter_mut())
.zip(recy.iter())
.zip(up.iter())
.zip(vp.iter())
{
*luma = recy.fast_round() as i32;
*su = up.fast_round() as i32;
*sv = vp.fast_round() as i32;
*suf = up;
*svf = vp;
}
}
let luma_q3 = subsample_luma_q3(&luma, 64, 64, 64, false, false);
let avg_l = cfl_avg_l_444(recy, pw, sb_y, sb_x, 64, 64, bd);
let ac: Vec<i32> = luma_q3.iter().map(|&v| v - avg_l).collect();
let predu_f = dc_pred(recu, pw, sb_y, sb_x, 64, neutral);
let predv_f = dc_pred(recv, pw, sb_y, sb_x, 64, neutral);
let predu_cfl = dc_pred_cfl_subsampled(recu, pw, sb_y, sb_x, 64, neutral, bd);
let predv_cfl = dc_pred_cfl_subsampled(recv, pw, sb_y, sb_x, 64, neutral, bd);
let cand = cfl_candidate(
&su,
&sv,
&ac,
predu_cfl.fast_round() as i32,
predv_cfl.fast_round() as i32,
bd,
)?;
let res_dc_u: Vec<f32> = suf.iter().map(|&s| s - predu_f).collect();
let res_dc_v: Vec<f32> = svf.iter().map(|&s| s - predv_f).collect();
let lev_dc_u = chroma.project(&res_dc_u, 0.0);
let lev_dc_v = chroma.project(&res_dc_v, 0.0);
let rec_dc_u = itx422::reconstruct_chroma(predu_f, &lev_dc_u, qstep, scan, 64, 64, bd);
let rec_dc_v = itx422::reconstruct_chroma(predv_f, &lev_dc_v, qstep, scan, 64, 64, bd);
let j_dc = (pixel_sse_rounded(&suf, &rec_dc_u) + pixel_sse_rounded(&svf, &rec_dc_v)) as f32
+ lambda * (coeff_rate_f32(&lev_dc_u) + coeff_rate_f32(&lev_dc_v)) as f32;
let res_cf_u: Vec<f32> = suf
.iter()
.zip(cand.pred_u.iter())
.map(|(&s, &u)| s - u as f32)
.collect();
let res_cf_v: Vec<f32> = svf
.iter()
.zip(cand.pred_v.iter())
.map(|(&s, &v)| s - v as f32)
.collect();
let lev_cf_u = chroma.project(&res_cf_u, 0.0);
let lev_cf_v = chroma.project(&res_cf_v, 0.0);
let rec_cf_u = itx422::reconstruct_chroma_cfl(&cand.pred_u, &lev_cf_u, qstep, scan, 64, 64, bd);
let rec_cf_v = itx422::reconstruct_chroma_cfl(&cand.pred_v, &lev_cf_v, qstep, scan, 64, 64, bd);
let alpha_bits = 2.0
+ 3.0
+ if cand.sign_u != 0 { 3.0 } else { 0.0 }
+ if cand.sign_v != 0 { 3.0 } else { 0.0 };
let j_cfl = (pixel_sse_rounded(&suf, &rec_cf_u) + pixel_sse_rounded(&svf, &rec_cf_v)) as f32
+ lambda * ((coeff_rate_f32(&lev_cf_u) + coeff_rate_f32(&lev_cf_v)) as f32 + alpha_bits);
if j_cfl < j_dc { Some(cand) } else { None }
}