use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[derive(Clone, Copy)]
pub enum B {
SkipFlag = 0,
MbType = 1,
RefIdx = 2,
Mvd = 3,
IntraBody = 4,
Cbp = 5,
QpDelta = 6,
ResidLuma = 7,
ResidChroma = 8,
Terminate = 9,
MvdBypass = 10,
IntraResid = 11,
MvdSign = 12,
}
pub const N: usize = 13;
const NAMES: [&str; N] = [
"mb_skip_flag",
"mb_type/sub_type",
"ref_idx",
"mvd (MOTION)",
"intra MB body (I+P)",
"cbp",
"mb_qp_delta",
"residual luma (TEX)",
"residual chroma (TEX)",
"end_of_slice",
" └ of which mvd bypass",
"intra residual (TEX)",
" └ of which mvd SIGNS",
];
static ON: AtomicBool = AtomicBool::new(false);
static BITS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static COUNT: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static ACTUAL: AtomicU64 = AtomicU64::new(0);
#[inline]
pub fn enabled() -> bool {
ON.load(Ordering::Relaxed)
}
pub fn set_enabled(on: bool) {
ON.store(on, Ordering::Relaxed);
}
pub fn init_from_env() {
if std::env::var("RFF_BITACCT").map(|v| v != "0").unwrap_or(false) {
set_enabled(true);
}
}
#[inline]
pub fn add(b: B, bits: u64) {
BITS[b as usize].fetch_add(bits, Ordering::Relaxed);
COUNT[b as usize].fetch_add(1, Ordering::Relaxed);
}
pub fn add_actual_bytes(n: usize) {
ACTUAL.fetch_add(n as u64 * 8, Ordering::Relaxed);
}
pub fn reset() {
for i in 0..N {
BITS[i].store(0, Ordering::Relaxed);
COUNT[i].store(0, Ordering::Relaxed);
}
ACTUAL.store(0, Ordering::Relaxed);
}
pub fn dump(label: &str, mbs: u64) {
let vals: Vec<u64> = (0..N).map(|i| BITS[i].load(Ordering::Relaxed)).collect();
let cnts: Vec<u64> = (0..N).map(|i| COUNT[i].load(Ordering::Relaxed)).collect();
let accounted: u64 = vals.iter().sum();
let actual = ACTUAL.load(Ordering::Relaxed);
println!("\n=== BIT ACCOUNTANT — {label} ===");
println!(
"{:<24}{:>12}{:>9}{:>12}{:>12}",
"syntax element", "bits", "share", "bits/MB", "elements"
);
println!("{}", "-".repeat(69));
for i in 0..N {
if cnts[i] == 0 && vals[i] == 0 {
continue;
}
println!(
"{:<24}{:>12}{:>8.1}%{:>12.1}{:>12}",
NAMES[i],
vals[i],
100.0 * vals[i] as f64 / (accounted - vals[B::MvdBypass as usize] - vals[B::IntraResid as usize] - vals[B::MvdSign as usize]).max(1) as f64,
vals[i] as f64 / mbs.max(1) as f64,
cnts[i]
);
}
println!("{}", "-".repeat(69));
let sub = vals[B::MvdBypass as usize] + vals[B::IntraResid as usize] + vals[B::MvdSign as usize];
let accounted = accounted - sub;
let mv = vals[B::Mvd as usize] + vals[B::RefIdx as usize];
let tex = vals[B::ResidLuma as usize] + vals[B::ResidChroma as usize] + vals[B::IntraResid as usize];
let x264_syntax = accounted - tex - vals[B::Terminate as usize];
let misc = accounted - mv - tex;
let _ = misc;
let pc = |v: u64| 100.0 * v as f64 / accounted.max(1) as f64;
println!(
"x264-comparable: NON-RESIDUAL SYNTAX {:.1}% (of which mvd {:.1}%) TEXTURE {:.1}% hdr/term {:.1}%",
pc(x264_syntax),
pc(mv),
pc(tex),
pc(vals[B::Terminate as usize])
);
println!(
"reconciliation: accounted {accounted} / actual {actual} = {:.1}% (residue {} bits = slice headers + NAL + flush)",
100.0 * accounted as f64 / actual.max(1) as f64,
actual as i64 - accounted as i64
);
}
pub const MVD_K: usize = 65; static MVD_BITS: [AtomicU64; MVD_K] = [const { AtomicU64::new(0) }; MVD_K];
static MVD_CNT: [AtomicU64; MVD_K] = [const { AtomicU64::new(0) }; MVD_K];
#[inline]
pub fn add_mvd_sample(abs_d: u32, bits: u64) {
let k = (abs_d as usize).min(MVD_K - 1);
MVD_BITS[k].fetch_add(bits, Ordering::Relaxed);
MVD_CNT[k].fetch_add(1, Ordering::Relaxed);
}
pub fn dump_mvd_table() {
println!("|d|,count,avg_bits");
for k in 0..MVD_K {
let (b, c) = (MVD_BITS[k].load(Ordering::Relaxed), MVD_CNT[k].load(Ordering::Relaxed));
if c > 0 {
println!("{k},{c},{:.3}", b as f64 / c as f64);
}
}
}