#[allow(unused_imports)]
use alloc::vec;
#[allow(unused_imports)]
use alloc::vec::Vec;
#[allow(unused_imports)]
use rusty_h264_common::fmath::{F32Ext as _, F64Ext as _};
#[allow(unused_imports)]
use rusty_h264_common::once::OnceLock;
use crate::config::{EncoderConfig, LookaheadMode};
use rusty_h264_common::inter::mc_luma;
use rusty_h264_common::transform::hadamard_4x4;
use rusty_h264_common::{YuvFrame, YuvPlanes};
#[derive(Clone, Copy)]
struct MbCost {
intra: i32, inter: i32, mv: (i32, i32), }
fn satd4(res: &[i32; 16]) -> i64 {
hadamard_4x4(res)
.iter()
.map(|&v| v.unsigned_abs() as i64)
.sum()
}
pub(crate) fn coded_luma(cfg: &EncoderConfig, frame: &YuvPlanes<'_>) -> Vec<u8> {
let (cw, ch) = (cfg.mb_width() * 16, cfg.mb_height() * 16);
let (w, h) = (frame.width, frame.height);
let mut y = vec![0u8; cw * ch];
let wc = w.min(cw);
for j in 0..ch {
let src = &frame.y[j.min(h - 1) * w..][..w];
let dst = &mut y[j * cw..][..cw];
dst[..wc].copy_from_slice(&src[..wc]);
dst[wc..].fill(src[w - 1]);
}
y
}
fn downsample2x(y: &[u8], cw: usize, ch: usize) -> (Vec<u8>, usize, usize) {
let (hw, hh) = (cw / 2, ch / 2);
let mut out = vec![0u8; hw * hh];
for j in 0..hh {
let r0 = &y[2 * j * cw..][..cw];
let r1 = &y[(2 * j + 1) * cw..][..cw];
let dst = &mut out[j * hw..][..hw];
for ((o, p0), p1) in dst
.iter_mut()
.zip(r0.chunks_exact(2))
.zip(r1.chunks_exact(2))
{
let s = p0[0] as u32 + p0[1] as u32 + p1[0] as u32 + p1[1] as u32;
*o = ((s + 2) / 4) as u8;
}
}
(out, hw, hh)
}
fn intra_cost(sy: &[u8], cw: usize, bx0: usize, by0: usize, bs: usize) -> i32 {
let mut s = 0i64;
for by in 0..bs / 4 {
for bx in 0..bs / 4 {
let mut blk = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
blk[dy * 4 + dx] = sy[(by0 + by * 4 + dy) * cw + bx0 + bx * 4 + dx] as i32;
}
}
let h = hadamard_4x4(&blk);
s += h[1..].iter().map(|&v| v.unsigned_abs() as i64).sum::<i64>();
}
}
(s.min(i32::MAX as i64) as i32).max(1)
}
pub(crate) static SATD_CALLS: rusty_h264_common::atomic::AtomicU64 =
rusty_h264_common::atomic::AtomicU64::new(0);
fn mc_satd(
sy: &[u8],
cw: usize,
ch: usize,
ref_y: &[u8],
bx0: usize,
by0: usize,
bs: usize,
mv: (i32, i32),
) -> i64 {
SATD_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let (ix, iy) = (
bx0 as isize + (mv.0 >> 2) as isize,
by0 as isize + (mv.1 >> 2) as isize,
);
if mv.0 & 3 == 0
&& mv.1 & 3 == 0
&& ix >= 0
&& iy >= 0
&& ix as usize + bs <= cw
&& iy as usize + bs <= ch
&& by0 + bs <= ch
&& bx0 + bs <= cw
{
return crate::mb16::satd_px(
&sy[by0 * cw + bx0..],
cw,
&ref_y[iy as usize * cw + ix as usize..],
cw,
bs,
bs,
);
}
let mut pred = [0u8; 256]; mc_luma(ref_y, cw, ch, bx0, by0, bs, bs, mv.0, mv.1, &mut pred);
let mut s = 0i64;
for by in 0..bs / 4 {
for bx in 0..bs / 4 {
let mut res = [0i32; 16];
for dy in 0..4 {
for dx in 0..4 {
res[dy * 4 + dx] = sy[(by0 + by * 4 + dy) * cw + bx0 + bx * 4 + dx] as i32
- pred[(by * 4 + dy) * bs + (bx * 4 + dx)] as i32;
}
}
s += satd4(&res);
}
}
s
}
fn inter_cost(
sy: &[u8],
cw: usize,
ch: usize,
ref_y: &[u8],
bx0: usize,
by0: usize,
bs: usize,
seed: (i32, i32),
max_step: i32,
) -> (i32, (i32, i32)) {
let mut best_mv = (0, 0);
let mut best = mc_satd(sy, cw, ch, ref_y, bx0, by0, bs, (0, 0));
if best == 0 {
return (0, best_mv);
}
if seed != (0, 0) {
let s = mc_satd(sy, cw, ch, ref_y, bx0, by0, bs, seed);
if s < best {
best = s;
best_mv = seed;
}
}
let mut step = max_step;
while step >= 1 {
loop {
let mut moved = false;
for &(dx, dy) in &[(step, 0), (-step, 0), (0, step), (0, -step)] {
let mv = (best_mv.0 + dx * 4, best_mv.1 + dy * 4); let s = mc_satd(sy, cw, ch, ref_y, bx0, by0, bs, mv);
if s < best {
best = s;
best_mv = mv;
moved = true;
}
}
if !moved {
break;
}
}
step >>= 1;
}
(best.min(i32::MAX as i64) as i32, best_mv)
}
fn frame_costs(
full: &[u8],
cwf: usize,
chf: usize,
half: &[u8],
cwh: usize,
chh: usize,
mb_w: usize,
mb_h: usize,
ref_full: Option<&[u8]>,
ref_half: Option<&[u8]>,
mode: LookaheadMode,
) -> Vec<MbCost> {
let mut out: Vec<MbCost> = Vec::with_capacity(mb_w * mb_h);
for mb_y in 0..mb_h {
for mb_x in 0..mb_w {
let seed_full = if mb_x > 0 {
out[mb_y * mb_w + mb_x - 1].mv
} else if mb_y > 0 {
out[(mb_y - 1) * mb_w + mb_x].mv
} else {
(0, 0)
};
let intra = if mode == LookaheadMode::HalfRes {
intra_cost(half, cwh, mb_x * 8, mb_y * 8, 8)
} else {
intra_cost(full, cwf, mb_x * 16, mb_y * 16, 16)
};
let (inter, mv) = match (mode, ref_full, ref_half) {
(LookaheadMode::FullRes, Some(rf), _) => {
let (ic, mv) =
inter_cost(full, cwf, chf, rf, mb_x * 16, mb_y * 16, 16, seed_full, 8);
(ic.min(intra), mv)
}
(LookaheadMode::HalfRes, _, Some(rh)) => {
let seed = (seed_full.0 / 2, seed_full.1 / 2);
let (ic, mvp) = inter_cost(half, cwh, chh, rh, mb_x * 8, mb_y * 8, 8, seed, 8);
(ic.min(intra), (mvp.0 * 2, mvp.1 * 2))
}
(LookaheadMode::Hybrid, Some(rf), Some(rh)) => {
let seed = (seed_full.0 / 2, seed_full.1 / 2);
let (_, mvp) = inter_cost(half, cwh, chh, rh, mb_x * 8, mb_y * 8, 8, seed, 8);
let coarse = (mvp.0 * 2, mvp.1 * 2); let (ic, mv) =
inter_cost(full, cwf, chf, rf, mb_x * 16, mb_y * 16, 16, coarse, 2);
(ic.min(intra), mv)
}
_ => (intra, (0, 0)), };
out.push(MbCost { intra, inter, mv });
}
}
out
}
fn propagate_to(
prev: &mut [f64],
mb_w: usize,
mb_h: usize,
mb_x: usize,
mb_y: usize,
mv: (i32, i32),
amount: f64,
) {
if amount <= 0.0 {
return;
}
let rx = (mb_x as i32 * 16 + (mv.0 >> 2)).clamp(0, (mb_w as i32 - 1) * 16);
let ry = (mb_y as i32 * 16 + (mv.1 >> 2)).clamp(0, (mb_h as i32 - 1) * 16);
let cx0 = (rx / 16) as usize;
let cy0 = (ry / 16) as usize;
let fx = (rx % 16) as f64;
let fy = (ry % 16) as f64;
let wl = 16.0 - fx; let wt = 16.0 - fy; for (dy, wy) in [(0usize, wt), (1, fy)] {
if wy <= 0.0 {
continue;
}
let cy = (cy0 + dy).min(mb_h - 1);
for (dx, wx) in [(0usize, wl), (1, fx)] {
if wx <= 0.0 {
continue;
}
let cx = (cx0 + dx).min(mb_w - 1);
prev[cy * mb_w + cx] += amount * (wx * wy) / 256.0;
}
}
}
pub(crate) struct PairPrep {
full: Vec<u8>,
half: Vec<u8>,
}
impl core::fmt::Debug for PairPrep {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"PairPrep({} + {} bytes)",
self.full.len(),
self.half.len()
)
}
}
pub(crate) fn pair_prep(cfg: &EncoderConfig, f: &YuvPlanes<'_>) -> PairPrep {
let (mb_w, mb_h) = (cfg.mb_width(), cfg.mb_height());
let (cwf, chf) = (mb_w * 16, mb_h * 16);
let full = coded_luma(cfg, f);
let (half, _, _) = downsample2x(&full, cwf, chf);
PairPrep { full, half }
}
pub(crate) fn pair_ratio_prepped(cfg: &EncoderConfig, cur: &PairPrep, prev: &PairPrep) -> f64 {
let (mb_w, mb_h) = (cfg.mb_width(), cfg.mb_height());
let (cwf, chf) = (mb_w * 16, mb_h * 16);
let (cwh, chh) = (cwf / 2, chf / 2);
let costs = frame_costs(
&cur.full,
cwf,
chf,
&cur.half,
cwh,
chh,
mb_w,
mb_h,
Some(&prev.full),
Some(&prev.half),
LookaheadMode::HalfRes,
);
let (mut num, mut den) = (0i64, 0i64);
for c in &costs {
num += c.inter.min(c.intra) as i64;
den += c.intra as i64;
}
num as f64 / den.max(1) as f64
}
pub mod gopstats {
#[allow(unused_imports)]
use alloc::{
boxed::Box,
format,
string::{String, ToString},
vec,
vec::Vec,
};
#[allow(unused_imports)]
use rusty_h264_common::once::OnceLock;
#[cfg(feature = "std")]
use std::sync::Mutex;
#[cfg(feature = "std")]
static ROWS: Mutex<Vec<GopRow>> = Mutex::new(Vec::new());
#[derive(Debug, Clone, Copy)]
pub struct GopRow {
pub sd: f64,
pub sd_raw: f64,
pub residual_frac: f64,
pub eff_strength: f64,
pub latched_off: bool,
}
pub fn on() -> bool {
rusty_h264_common::cached_knob!(
bool,
rusty_h264_common::knob("RFF_MBTREE_GOPSTATS").is_some()
)
}
#[cfg_attr(not(feature = "std"), allow(unused_variables))]
pub(crate) fn push(r: GopRow) {
#[cfg(feature = "std")]
if on() {
if let Ok(mut g) = ROWS.lock() {
g.push(r);
}
}
}
pub fn take() -> Vec<GopRow> {
#[cfg(feature = "std")]
{
ROWS.lock()
.map(|mut g| core::mem::take(&mut *g))
.unwrap_or_default()
}
#[cfg(not(feature = "std"))]
{
Vec::new()
}
}
}
fn mbtree_spread_min(cfg: &EncoderConfig) -> f64 {
static V: rusty_h264_common::once::OnceLock<Option<f64>> =
rusty_h264_common::once::OnceLock::new();
V.get_or_init(|| rusty_h264_common::knob("RFF_MBTREE_SDMIN").and_then(|v| v.parse().ok()))
.unwrap_or(cfg.mbtree_spread_min)
}
pub fn gop_qp_offsets(cfg: &EncoderConfig, frames: &[YuvFrame], strength: f64) -> Vec<Vec<i32>> {
gop_qp_offsets_refs(cfg, &frames.iter().collect::<Vec<_>>(), strength)
}
pub fn gop_qp_offsets_refs(
cfg: &EncoderConfig,
frames: &[&YuvFrame],
strength: f64,
) -> Vec<Vec<i32>> {
let (mb_w, mb_h) = (cfg.mb_width(), cfg.mb_height());
let n = frames.len();
if strength <= 0.0 || n == 0 || mb_w * mb_h == 0 {
gopstats::push(gopstats::GopRow {
sd: 0.0,
sd_raw: 0.0,
residual_frac: 0.0,
eff_strength: 0.0,
latched_off: true,
});
return vec![vec![0i32; mb_w * mb_h]; n];
}
let mode = match rusty_h264_common::knob("RFF_MBTREE_LA").as_deref() {
Some("full") => LookaheadMode::FullRes,
Some("hybrid") => LookaheadMode::Hybrid,
Some("half") => LookaheadMode::HalfRes,
_ => cfg.mbtree_lookahead,
};
let (cwf, chf) = (mb_w * 16, mb_h * 16);
let (cwh, chh) = (mb_w * 8, mb_h * 8);
let full: Vec<Vec<u8>> = frames
.iter()
.map(|f| coded_luma(cfg, &f.as_planes()))
.collect();
let grain_veto = {
rusty_h264_common::cached_knob!(
bool,
rusty_h264_common::knob("RFF_MBTREE_GRAIN")
.map(|s| s != "0")
.unwrap_or(true)
)
};
if grain_veto && n >= 2 {
let sig = crate::signals::FrameSignals::new(&full[1], cwf, mb_w, mb_h, Some(&full[0]));
let grain = sig.grain_signature();
crate::signals::census::bump(crate::signals::census::MBTREE_GRAIN, grain);
if grain {
if rusty_h264_common::knob("RFF_MBTREE_DBG").is_some() {
eprintln!("MBTREE_DBG grain latch: eff=0.000 (zero offsets)");
}
gopstats::push(gopstats::GopRow {
sd: 0.0,
sd_raw: 0.0,
residual_frac: 0.0,
eff_strength: 0.0,
latched_off: true,
});
return vec![vec![0i32; mb_w * mb_h]; n];
}
}
let need_half = mode != LookaheadMode::FullRes;
let half: Vec<Vec<u8>> = if need_half {
full.iter().map(|f| downsample2x(f, cwf, chf).0).collect()
} else {
Vec::new()
};
let empty: Vec<u8> = Vec::new();
let costs: Vec<Vec<MbCost>> = (0..n)
.map(|f| {
let ref_full = if f == 0 {
None
} else {
Some(full[f - 1].as_slice())
};
let ref_half = if f == 0 || !need_half {
None
} else {
Some(half[f - 1].as_slice())
};
let hf = if need_half {
half[f].as_slice()
} else {
&empty[..]
};
frame_costs(
&full[f], cwf, chf, hf, cwh, chh, mb_w, mb_h, ref_full, ref_half, mode,
)
})
.collect();
let mbs = mb_w * mb_h;
let mut propagate: Vec<f64> = vec![0.0; n * mbs];
let mut frac_buf: Vec<f64> = vec![0.0; n.saturating_sub(1) * mbs];
for f in (1..n).rev() {
let (head, tail) = propagate.split_at_mut(f * mbs);
let cur = &tail[..mbs];
let prev = &mut head[(f - 1) * mbs..];
for mb_y in 0..mb_h {
for mb_x in 0..mb_w {
let m = mb_y * mb_w + mb_x;
let c = costs[f][m];
let total = c.intra as f64 + cur[m];
let frac = (c.intra - c.inter) as f64 / c.intra as f64; frac_buf[(f - 1) * mbs + m] = frac;
propagate_to(prev, mb_w, mb_h, mb_x, mb_y, c.mv, total * frac);
}
}
}
let res_min: f64 = {
rusty_h264_common::cached_knob!(f64, {
rusty_h264_common::knob("RFF_MBTREE_RESMIN")
.and_then(|v| v.parse().ok())
.unwrap_or(0.03)
})
};
let fsum: f64 = frac_buf.iter().sum();
let fc = (n.saturating_sub(1) * mbs) as f64;
let residual_frac = 1.0 - if fc > 0.0 { fsum / fc } else { 0.0 };
let eff_strength = if res_min > 0.0 && residual_frac < res_min {
0.0
} else {
strength
};
crate::signals::census::bump(crate::signals::census::MBTREE_BACKOFF, eff_strength == 0.0);
if eff_strength == 0.0 {
gopstats::push(gopstats::GopRow {
sd: 0.0,
sd_raw: 0.0,
residual_frac,
eff_strength: 0.0,
latched_off: true,
});
if rusty_h264_common::knob("RFF_MBTREE_DBG").is_some() {
eprintln!(
"MBTREE_DBG spread=0.000 residual_frac={residual_frac:.3} eff=0.000 (latched off)"
);
}
return vec![vec![0i32; mb_w * mb_h]; n];
}
let poly = crate::fastmath::polytier_on();
let mut offs: Vec<f64> = Vec::with_capacity(n * mbs);
for f in 0..n {
for m in 0..mbs {
let p = propagate[f * mbs + m];
offs.push(if p == 0.0 {
-eff_strength * 0.0
} else {
let intra = costs[f][m].intra as f64;
let total = intra + p;
let l = if poly {
crate::fastmath::log2_poly(total / intra)
} else {
rusty_h264_common::fmath::log2(total / intra)
};
-eff_strength * l
});
}
}
let cnt = (n * mbs) as f64;
let mean: f64 = offs.iter().sum::<f64>() / cnt;
for o in offs.iter_mut() {
*o -= mean;
}
let sd_raw = rusty_h264_common::fmath::sqrt(offs.iter().map(|o| o * o).sum::<f64>() / cnt);
let sd = sd_raw / eff_strength.max(1e-9);
if rusty_h264_common::knob("RFF_MBTREE_DBG").is_some() {
eprintln!("MBTREE_DBG spread={sd:.3} raw={sd_raw:.3} residual_frac={residual_frac:.3} eff={eff_strength:.3}");
}
let sd_min = mbtree_spread_min(cfg);
gopstats::push(gopstats::GopRow {
sd,
sd_raw,
residual_frac,
eff_strength,
latched_off: sd < sd_min,
});
crate::signals::census::bump(crate::signals::census::MBTREE_SPREAD_LATCH, sd < sd_min);
if sd < sd_min {
return vec![vec![0i32; mb_w * mb_h]; n];
}
const MBTREE_DQP_MAX: i32 = 6;
(0..n)
.map(|f| {
offs[f * mbs..][..mbs]
.iter()
.map(|&o| {
let r = if poly {
crate::fastmath::round_ties_even_fast(o)
} else {
rusty_h264_common::fmath::round(o)
};
(r as i32).clamp(-MBTREE_DQP_MAX, MBTREE_DQP_MAX)
})
.collect()
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use alloc::{
boxed::Box,
format,
string::{String, ToString},
vec,
vec::Vec,
};
#[allow(unused_imports)]
use rusty_h264_common::once::OnceLock;
fn synth_frames(w: usize, h: usize, n: usize) -> Vec<YuvFrame> {
(0..n)
.map(|f| {
let mut y = vec![0u8; w * h];
for j in 0..h {
for i in 0..w {
let mut v = ((i / 4 + j / 4) % 2) as i32 * 60
+ (i as i32 * 3 + j as i32 * 2) % 90
+ 40;
if (16..24).contains(&j) {
v = 30 + (((i + f) * 13) % 200) as i32;
}
let sx = 4 + f * 4;
if (sx..sx + 16).contains(&i) && (28..44).contains(&j) {
v = 200 - ((i * 7 + j * 11) % 120) as i32;
}
y[j * w + i] = v.clamp(0, 255) as u8;
}
}
YuvFrame {
width: w,
height: h,
y,
u: vec![128; (w / 2) * (h / 2)],
v: vec![128; (w / 2) * (h / 2)],
}
})
.collect()
}
fn fnv1a_i32s(offs: &[Vec<i32>]) -> u64 {
let mut hsh: u64 = 0xcbf2_9ce4_8422_2325;
for fr in offs {
for &v in fr {
for b in v.to_le_bytes() {
hsh ^= b as u64;
hsh = hsh.wrapping_mul(0x100_0000_01b3);
}
}
}
hsh
}
#[test]
fn coded_luma_matches_per_pixel() {
for (w, h) in [(64usize, 48usize), (20, 13), (33, 17), (16, 16)] {
let cfg = EncoderConfig::new(w, h);
let frame = &synth_frames(w, h, 1)[0];
let (cw, ch) = (cfg.mb_width() * 16, cfg.mb_height() * 16);
let mut want = vec![0u8; cw * ch];
for j in 0..ch {
for i in 0..cw {
want[j * cw + i] = frame.y[j.min(h - 1) * w + i.min(w - 1)];
}
}
assert_eq!(coded_luma(&cfg, &frame.as_planes()), want, "{w}x{h}");
}
}
#[test]
fn gop_qp_offsets_golden() {
std::env::set_var("RFF_MBTREE_GRAIN", "0");
std::env::set_var("RFF_MBTREE_RESMIN", "0");
let (w, h, n) = (64usize, 48usize, 6usize);
let frames = synth_frames(w, h, n);
let mut cfg = EncoderConfig::new(w, h);
crate::fastmath::TEST_POLYTIER.with(|c| c.set(Some(false)));
cfg.mbtree_lookahead = LookaheadMode::HalfRes;
let a = gop_qp_offsets(&cfg, &frames, 0.9);
assert!(
a.iter().flatten().any(|&v| v != 0),
"HalfRes offsets all zero"
);
assert_eq!(fnv1a_i32s(&a), 1359955132549194384, "HalfRes golden");
cfg.mbtree_lookahead = LookaheadMode::Hybrid;
let b = gop_qp_offsets(&cfg, &frames, 0.9);
assert!(
b.iter().flatten().any(|&v| v != 0),
"Hybrid offsets all zero"
);
assert_eq!(fnv1a_i32s(&b), 7391805242828194773, "Hybrid golden");
cfg.mbtree_lookahead = LookaheadMode::FullRes;
let c = gop_qp_offsets(&cfg, &frames, 2.0);
assert!(
c.iter().flatten().any(|&v| v != 0),
"FullRes offsets all zero"
);
assert_eq!(fnv1a_i32s(&c), 17244099396955043453, "FullRes golden");
crate::fastmath::TEST_POLYTIER.with(|c| c.set(Some(true)));
cfg.mbtree_lookahead = LookaheadMode::HalfRes;
assert_eq!(
gop_qp_offsets(&cfg, &frames, 0.9),
a,
"poly arm HalfRes differs"
);
cfg.mbtree_lookahead = LookaheadMode::Hybrid;
assert_eq!(
gop_qp_offsets(&cfg, &frames, 0.9),
b,
"poly arm Hybrid differs"
);
cfg.mbtree_lookahead = LookaheadMode::FullRes;
assert_eq!(
gop_qp_offsets(&cfg, &frames, 2.0),
c,
"poly arm FullRes differs"
);
crate::fastmath::TEST_POLYTIER.with(|c| c.set(None));
}
}