use crate::cdef;
static PRI_CANDS: [i32; 6] = [0, 1, 2, 4, 7, 11];
static SEC_CANDS: [i32; 3] = [0, 1, 2];
const MARGIN_DEN: i64 = 1000;
fn damping_for(base_q_idx: u8) -> i32 {
3 + ((base_q_idx as i32) / 64).min(3)
}
fn to_i32(p: &[f32]) -> Vec<i32> {
p.iter().map(|&v| v as i32).collect()
}
use crate::cdef::cdef_dist_8x8;
#[derive(Clone)]
pub(crate) struct CdefDecision {
pub(crate) damping: u8,
pub(crate) y_str: u8,
pub(crate) uv_str: u8,
pub(crate) grid: Vec<u8>,
pub(crate) sb_cols: usize,
}
const SB_MARGIN_NUM: i64 = 10;
fn sb_of(bx: usize, by: usize, sb_cols: usize) -> usize {
(by / 8) * sb_cols + (bx / 8)
}
#[allow(clippy::too_many_arguments)]
fn luma_sse_per_sb(
rec: &[i32],
src: &[i32],
w: usize,
h: usize,
dirs: &[usize],
vars: &[i32],
nbx: usize,
sb_cols: usize,
n_sb: usize,
pri: i32,
sec: i32,
damping: i32,
bd: u8,
) -> Vec<i64> {
let coeff_shift = (bd - 8) as u32;
let mut out = vec![0i64; n_sb];
let mut tmp = if pri != 0 || sec != 0 {
rec.to_vec()
} else {
Vec::new()
};
for y in (0..h).step_by(8) {
for x in (0..w).step_by(8) {
let bx = x / 8;
let by = y / 8;
let dist = if pri != 0 || sec != 0 {
let bi = by * nbx + bx;
let apri = cdef::adjust_pri(pri << (bd - 8), vars[bi]);
cdef::cdef_filter_8x8(
&mut tmp,
rec,
w,
x,
y,
apri,
sec << (bd - 8),
if pri == 0 { 0 } else { dirs[bi] },
damping,
bd,
);
cdef_dist_8x8(src, &tmp, w, h, x, y, coeff_shift)
} else {
cdef_dist_8x8(src, rec, w, h, x, y, coeff_shift)
};
out[sb_of(bx, by, sb_cols)] += dist;
}
}
out
}
#[allow(clippy::too_many_arguments)]
fn chroma_sse_per_sb(
rec: &[i32],
src: &[i32],
cw: usize,
ch: usize,
ldirs: &[usize],
uv_dir: &[usize; 8],
nbx: usize,
sb_cols: usize,
n_sb: usize,
sub_x: usize,
sub_y: usize,
pri: i32,
sec: i32,
damping: i32,
bd: u8,
) -> Vec<i64> {
let cbw = 8 >> sub_x;
let cbh = 8 >> sub_y;
let mut out = vec![0i64; n_sb];
let mut tmp = if pri != 0 || sec != 0 {
rec.to_vec()
} else {
Vec::new()
};
let nby = ldirs.len().div_ceil(nbx);
for lby in 0..nby {
for lbx in 0..nbx {
let cx = (lbx * 8) >> sub_x;
let cy = (lby * 8) >> sub_y;
if cx >= cw || cy >= ch {
continue;
}
let sse = if pri != 0 || sec != 0 {
let dir = if pri == 0 {
0
} else {
uv_dir[ldirs.get(lby * nbx + lbx).copied().unwrap_or(0)]
};
cdef::cdef_filter_block(
&mut tmp,
0,
rec,
cw,
cx,
cy,
cbw,
cbh,
pri << (bd - 8),
sec << (bd - 8),
dir,
damping,
bd,
);
let mut s = 0i64;
for yy in cy..(cy + cbh).min(ch) {
for xx in cx..(cx + cbw).min(cw) {
let d = (tmp[yy * cw + xx] - src[yy * cw + xx]) as i64;
s += d * d;
}
}
s
} else {
let mut s = 0i64;
for yy in cy..(cy + cbh).min(ch) {
for xx in cx..(cx + cbw).min(cw) {
let d = (rec[yy * cw + xx] - src[yy * cw + xx]) as i64;
s += d * d;
}
}
s
};
out[sb_of(lbx, lby, sb_cols)] += sse;
}
}
out
}
const SB_DIR_VAR_THRESH: i64 = 15000;
fn apply_grid(off: &[i64], filt: &[i64], sb_dir_var: &[i64]) -> (i64, Vec<u8>, usize) {
let m = SB_MARGIN_NUM;
let vthr = SB_DIR_VAR_THRESH;
let mut total = 0i64;
let mut grid = vec![0u8; off.len()];
let mut n_on = 0;
for i in 0..off.len() {
let thr = off[i] - off[i].saturating_mul(m) / MARGIN_DEN.max(1);
if filt[i] < thr && sb_dir_var[i] >= vthr {
total += filt[i];
grid[i] = 1;
n_on += 1;
} else {
total += off[i];
}
}
(total, grid, n_on)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn apply_per_block(
rec: &mut [Vec<f32>],
pw: usize,
ph: usize,
cw: usize,
ch: usize,
sub_x: usize,
sub_y: usize,
has_chroma: bool,
decision: &CdefDecision,
bd: u8,
) {
if rec.is_empty() || rec[0].len() < pw.saturating_mul(ph) {
return;
}
let src_y = to_i32(&rec[0]);
let mut dst_y = src_y.clone();
let nbx = pw.div_ceil(8);
let nby = ph.div_ceil(8);
let blocks: Vec<(usize, i32)> = (0..nbx * nby)
.map(|idx| {
let (bx, by) = (idx % nbx, idx / nbx);
cdef::cdef_direction(&src_y, pw, bx * 8, by * 8, bd)
})
.collect();
let dirs: Vec<usize> = blocks.iter().map(|&(dir, _)| dir).collect();
let vars: Vec<i32> = blocks.iter().map(|&(_, var)| var).collect();
let coeff_shift = bd - 8;
let decode_strength = |strength: u8| {
let pri = (strength >> 2) as i32;
let sec = match strength & 3 {
3 => 4,
value => value as i32,
};
(pri, sec)
};
let (y_pri, y_sec) = decode_strength(decision.y_str);
for by in 0..nby {
for bx in 0..nbx {
let sb = sb_of(bx, by, decision.sb_cols);
if decision.grid.get(sb).copied().unwrap_or(0) == 0 {
continue;
}
let bi = by * nbx + bx;
cdef::cdef_filter_8x8(
&mut dst_y,
&src_y,
pw,
bx * 8,
by * 8,
cdef::adjust_pri(y_pri << coeff_shift, vars[bi]),
y_sec << coeff_shift,
if y_pri == 0 { 0 } else { dirs[bi] },
decision.damping as i32,
bd,
);
}
}
rec[0] = dst_y.into_iter().map(|value| value as f32).collect();
if !has_chroma || rec.len() < 3 || rec[1].len() < cw * ch || rec[2].len() < cw * ch {
return;
}
let (uv_pri, uv_sec) = decode_strength(decision.uv_str);
if uv_pri == 0 && uv_sec == 0 {
return;
}
let uv_dir: [usize; 8] = if sub_x == 1 && sub_y == 0 {
[7, 0, 2, 4, 5, 6, 6, 6]
} else {
[0, 1, 2, 3, 4, 5, 6, 7]
};
let (cbw, cbh) = (8 >> sub_x, 8 >> sub_y);
for plane in &mut rec[1..=2] {
let src = to_i32(plane);
let mut dst = src.clone();
for by in 0..nby {
for bx in 0..nbx {
let sb = sb_of(bx, by, decision.sb_cols);
if decision.grid.get(sb).copied().unwrap_or(0) == 0 {
continue;
}
let (cx, cy) = ((bx * 8) >> sub_x, (by * 8) >> sub_y);
if cx >= cw || cy >= ch {
continue;
}
cdef::cdef_filter_block(
&mut dst,
0,
&src,
cw,
cx,
cy,
cbw,
cbh,
uv_pri << coeff_shift,
uv_sec << coeff_shift,
if uv_pri == 0 {
0
} else {
uv_dir[dirs[by * nbx + bx]]
},
(decision.damping as i32 - 1).max(1),
bd,
);
}
}
*plane = dst.into_iter().map(|value| value as f32).collect();
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn search_per_block(
rec: &[Vec<f32>],
src: &[Vec<f32>],
pw: usize,
ph: usize,
cw: usize,
ch: usize,
sub_x: usize,
sub_y: usize,
has_chroma: bool,
base_q_idx: u8,
bd: u8,
) -> Option<CdefDecision> {
let damping = damping_for(base_q_idx);
let recy = to_i32(&rec[0]);
let srcy = to_i32(&src[0]);
let nbx = pw.div_ceil(8);
let nby = ph.div_ceil(8);
let nthreads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1);
let blk: Vec<(usize, i32)> = crate::av2::helpers::par_map_indexed(nthreads, nbx * nby, |idx| {
let bx = idx % nbx;
let by = idx / nbx;
if bx * 8 < pw && by * 8 < ph {
cdef::cdef_direction(&recy, pw, bx * 8, by * 8, bd)
} else {
(0usize, 0i32)
}
});
let dirs: Vec<usize> = blk.iter().map(|&(d, _)| d).collect();
let vars: Vec<i32> = blk.iter().map(|&(_, v)| v).collect();
let sb_cols = pw.div_ceil(64);
let sb_rows = ph.div_ceil(64);
let n_sb = sb_cols * sb_rows;
let mut dv_sum = vec![0i64; n_sb];
let mut dv_cnt = vec![0i64; n_sb];
for by in 0..nby {
for bx in 0..nbx {
if bx * 8 < pw && by * 8 < ph {
let s = sb_of(bx, by, sb_cols);
dv_sum[s] += vars[by * nbx + bx] as i64;
dv_cnt[s] += 1;
}
}
}
let sb_dir_var: Vec<i64> = (0..n_sb).map(|i| dv_sum[i] / dv_cnt[i].max(1)).collect();
let luma_off = luma_sse_per_sb(
&recy, &srcy, pw, ph, &dirs, &vars, nbx, sb_cols, n_sb, 0, 0, damping, bd,
);
let eval = |pri: i32, sec: i32| -> (i64, Vec<u8>, usize) {
let filt = luma_sse_per_sb(
&recy, &srcy, pw, ph, &dirs, &vars, nbx, sb_cols, n_sb, pri, sec, damping, bd,
);
apply_grid(&luma_off, &filt, &sb_dir_var)
};
let off_total: i64 = luma_off.iter().sum();
let mut best = (off_total, vec![0u8; n_sb], 0usize);
let mut best_pri = 0i32;
let pri_cands: Vec<i32> = PRI_CANDS.iter().skip(1).copied().collect();
let pri_results =
crate::av2::helpers::par_map_indexed(nthreads, pri_cands.len(), |i| eval(pri_cands[i], 0));
for (i, r) in pri_results.into_iter().enumerate() {
if r.0 < best.0 {
best = r;
best_pri = pri_cands[i];
}
}
if best_pri == 0 || best.2 == 0 {
return None;
}
let mut best_sec = 0i32;
let sec_cands: Vec<i32> = SEC_CANDS.iter().skip(1).copied().collect();
let sec_results = crate::av2::helpers::par_map_indexed(nthreads, sec_cands.len(), |i| {
eval(best_pri, sec_cands[i])
});
for (i, r) in sec_results.into_iter().enumerate() {
if r.0 < best.0 {
best = r;
best_sec = sec_cands[i];
}
}
let (best_total, grid, n_on) = best;
const FRAME_MIN_GAIN: i64 = 5; let gain = off_total - best_total;
if n_on == 0 || gain <= off_total.saturating_mul(FRAME_MIN_GAIN) / 1000 {
return None;
}
let y_str = ((best_pri << 2) | best_sec) as u8;
let uv_str = if has_chroma {
let uv_dir: [usize; 8] = if sub_x == 1 && sub_y == 0 {
[7, 0, 2, 4, 5, 6, 6, 6]
} else {
[0, 1, 2, 3, 4, 5, 6, 7]
};
let c_damping = (damping - 1).max(1);
let recu = to_i32(&rec[1]);
let srcu = to_i32(&src[1]);
let recv = to_i32(&rec[2]);
let srcv = to_i32(&src[2]);
let on_sse = |pri: i32, sec: i32| -> i64 {
let u = chroma_sse_per_sb(
&recu, &srcu, cw, ch, &dirs, &uv_dir, nbx, sb_cols, n_sb, sub_x, sub_y, pri, sec,
c_damping, bd,
);
let v = chroma_sse_per_sb(
&recv, &srcv, cw, ch, &dirs, &uv_dir, nbx, sb_cols, n_sb, sub_x, sub_y, pri, sec,
c_damping, bd,
);
(0..n_sb)
.filter(|&i| grid[i] == 1)
.map(|i| u[i] + v[i])
.sum()
};
let off_on = on_sse(0, 0);
let mut c_best = off_on;
let mut c_pri = 0i32;
let cpri_cands: Vec<i32> = PRI_CANDS.iter().skip(1).copied().collect();
let cpri_res = crate::av2::helpers::par_map_indexed(nthreads, cpri_cands.len(), |i| {
on_sse(cpri_cands[i], 0)
});
for (i, s) in cpri_res.into_iter().enumerate() {
if s < c_best {
c_best = s;
c_pri = cpri_cands[i];
}
}
let mut c_sec = 0i32;
if c_pri != 0 {
let csec_cands: Vec<i32> = SEC_CANDS.iter().skip(1).copied().collect();
let csec_res = crate::av2::helpers::par_map_indexed(nthreads, csec_cands.len(), |i| {
on_sse(c_pri, csec_cands[i])
});
for (i, s) in csec_res.into_iter().enumerate() {
if s < c_best {
c_best = s;
c_sec = csec_cands[i];
}
}
}
((c_pri << 2) | c_sec) as u8
} else {
0
};
Some(CdefDecision {
damping: damping as u8,
y_str,
uv_str,
grid,
sb_cols,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn apply_filters_only_enabled_superblocks() {
let (w, h) = (128usize, 64usize);
let y: Vec<f32> = (0..w * h)
.map(|i| {
let (x, row) = (i % w, i / w);
((x * 5 + row * 3 + ((x + row) % 7) * 11) & 255) as f32
})
.collect();
let mut rec = vec![y.clone(), vec![128.0; 64 * 32], vec![128.0; 64 * 32]];
apply_per_block(
&mut rec,
w,
h,
64,
32,
1,
1,
true,
&CdefDecision {
damping: 4,
y_str: (7 << 2) | 2,
uv_str: 0,
grid: vec![1, 0],
sb_cols: 2,
},
8,
);
assert!((0..h).any(|row| rec[0][row * w..row * w + 64] != y[row * w..row * w + 64]));
for row in 0..h {
assert_eq!(
&rec[0][row * w + 64..(row + 1) * w],
&y[row * w + 64..(row + 1) * w]
);
}
}
}