#[rustfmt::skip]
const ALPHA: [i32; 52] = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
4,4,5,6,7,8,9,10,12,13,15,17,20,22,25,28,
32,36,40,45,50,56,63,71,80,90,101,113,127,144,162,182,203,226,255,255,
];
#[rustfmt::skip]
const BETA: [i32; 52] = [
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,2,2,3,3,3,3,4,4,4,6,6,7,7,8,8,
9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,
];
#[rustfmt::skip]
const TC0: [[i32; 3]; 52] = [
[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],
[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],
[0,0,0],[0,0,1],[0,0,1],[0,0,1],[0,0,1],[0,1,1],[0,1,1],[1,1,1],
[1,1,1],[1,1,1],[1,1,1],[1,1,2],[1,1,2],[1,1,2],[1,1,2],[1,2,3],
[1,2,3],[2,2,3],[2,2,4],[2,3,4],[2,3,4],[3,3,5],[3,4,6],[3,4,6],
[4,5,7],[4,5,8],[4,6,9],[5,7,10],[6,8,11],[6,8,13],[7,10,14],[8,11,16],
[9,12,18],[10,13,20],[11,15,23],[13,17,25],
];
#[inline]
fn clip1(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
#[inline]
fn clip3(lo: i32, hi: i32, v: i32) -> i32 {
v.clamp(lo, hi)
}
struct Line {
base: usize,
step: isize,
}
#[allow(clippy::too_many_arguments)]
fn filter_luma_line(plane: &mut [u8], line: &Line, bs: i32, alpha: i32, beta: i32, tc0: i32) {
let at = |i: isize| -> i32 {
plane[(line.base as isize + i * line.step) as usize] as i32
};
let (p0, p1, p2, p3) = (at(-1), at(-2), at(-3), at(-4));
let (q0, q1, q2, q3) = (at(0), at(1), at(2), at(3));
if (p0 - q0).abs() >= alpha || (p1 - p0).abs() >= beta || (q1 - q0).abs() >= beta {
return;
}
let set = |plane: &mut [u8], i: isize, v: u8| {
plane[(line.base as isize + i * line.step) as usize] = v;
};
let ap = (p2 - p0).abs();
let aq = (q2 - q0).abs();
if bs < 4 {
let tc = tc0 + (ap < beta) as i32 + (aq < beta) as i32;
let delta = clip3(-tc, tc, (((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
set(plane, -1, clip1(p0 + delta));
set(plane, 0, clip1(q0 - delta));
if ap < beta {
let d = clip3(-tc0, tc0, (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1);
set(plane, -2, clip1(p1 + d));
}
if aq < beta {
let d = clip3(-tc0, tc0, (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1);
set(plane, 1, clip1(q1 + d));
}
} else {
let strong = (p0 - q0).abs() < (alpha >> 2) + 2;
if strong && ap < beta {
set(plane, -1, clip1((p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3));
set(plane, -2, clip1((p2 + p1 + p0 + q0 + 2) >> 2));
set(plane, -3, clip1((2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3));
} else {
set(plane, -1, clip1((2 * p1 + p0 + q1 + 2) >> 2));
}
if strong && aq < beta {
set(plane, 0, clip1((q2 + 2 * q1 + 2 * q0 + 2 * p0 + p1 + 4) >> 3));
set(plane, 1, clip1((q2 + q1 + q0 + p0 + 2) >> 2));
set(plane, 2, clip1((2 * q3 + 3 * q2 + q1 + q0 + p0 + 4) >> 3));
} else {
set(plane, 0, clip1((2 * q1 + q0 + p1 + 2) >> 2));
}
}
}
fn filter_chroma_line(plane: &mut [u8], line: &Line, bs: i32, alpha: i32, beta: i32, tc0: i32) {
let at = |i: isize| -> i32 {
plane[(line.base as isize + i * line.step) as usize] as i32
};
let (p0, p1) = (at(-1), at(-2));
let (q0, q1) = (at(0), at(1));
if (p0 - q0).abs() >= alpha || (p1 - p0).abs() >= beta || (q1 - q0).abs() >= beta {
return;
}
let set = |plane: &mut [u8], i: isize, v: u8| {
plane[(line.base as isize + i * line.step) as usize] = v;
};
if bs < 4 {
let tc = tc0 + 1;
let delta = clip3(-tc, tc, (((q0 - p0) << 2) + (p1 - q1) + 4) >> 3);
set(plane, -1, clip1(p0 + delta));
set(plane, 0, clip1(q0 - delta));
} else {
set(plane, -1, clip1((2 * p1 + p0 + q1 + 2) >> 2));
set(plane, 0, clip1((2 * q1 + q0 + p1 + 2) >> 2));
}
}
pub struct BlockInfo<'a> {
pub inter: &'a [bool],
pub nnz: &'a [u8],
pub mv: &'a [(i32, i32)],
pub ref_id: &'a [i32],
pub mv1: &'a [(i32, i32)],
pub ref_id1: &'a [i32],
pub w4: usize,
pub t8x8: &'a [bool],
pub bs: &'a [MbBs],
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
pub struct MbBs {
pub v: [[u8; 4]; 4],
pub h: [[u8; 4]; 4],
}
impl MbBs {
pub const UNSET: MbBs = MbBs { v: [[0xFF; 4]; 4], h: [[0xFF; 4]; 4] };
}
const NO_REF: i32 = i32::MIN;
#[derive(Clone, Copy, Default)]
struct Blk {
inter: bool,
nz: bool,
ref_id: i32,
mvx: i32,
mvy: i32,
ref1: i32,
mv1x: i32,
mv1y: i32,
}
impl Blk {
#[inline]
fn load(info: &BlockInfo, i: usize) -> Self {
let (mvx, mvy) = info.mv[i];
let (ref1, (mv1x, mv1y)) = if info.ref_id1.is_empty() {
(NO_REF, (0, 0))
} else {
(info.ref_id1[i], info.mv1[i])
};
Blk { inter: info.inter[i], nz: info.nnz[i] != 0, ref_id: info.ref_id[i], mvx, mvy, ref1, mv1x, mv1y }
}
#[inline]
fn same_motion(&self, o: &Blk) -> bool {
self.ref_id == o.ref_id
&& self.mvx == o.mvx
&& self.mvy == o.mvy
&& self.ref1 == o.ref1
&& self.mv1x == o.mv1x
&& self.mv1y == o.mv1y
}
}
#[inline]
fn bs1_tile(p: &Blk, q: &Blk) -> bool {
if (p.ref1 == NO_REF) & (q.ref1 == NO_REF) {
let far = ((p.mvx - q.mvx).abs() >= 4) | ((p.mvy - q.mvy).abs() >= 4);
return (p.ref_id != q.ref_id) | ((p.ref_id != NO_REF) & far);
}
let used = |b: &Blk| {
let mut v = [(0i32, (0i32, 0i32)); 2];
let mut n = 0usize;
if b.ref_id != NO_REF {
v[n] = (b.ref_id, (b.mvx, b.mvy));
n += 1;
}
if b.ref1 != NO_REF {
v[n] = (b.ref1, (b.mv1x, b.mv1y));
n += 1;
}
(v, n)
};
let (pv, pn) = used(p);
let (qv, qn) = used(q);
if pn != qn {
return true;
}
let far = |a: (i32, i32), b: (i32, i32)| (a.0 - b.0).abs() >= 4 || (a.1 - b.1).abs() >= 4;
match pn {
0 => false,
1 => pv[0].0 != qv[0].0 || far(pv[0].1, qv[0].1),
_ => {
let direct = !far(pv[0].1, qv[0].1) && !far(pv[1].1, qv[1].1);
let swap = !far(pv[0].1, qv[1].1) && !far(pv[1].1, qv[0].1);
if pv[0].0 == pv[1].0 {
qv[0].0 != pv[0].0 || qv[1].0 != pv[0].0 || !(direct || swap)
} else if pv[0].0 == qv[0].0 && pv[1].0 == qv[1].0 {
!direct
} else if pv[0].0 == qv[1].0 && pv[1].0 == qv[0].0 {
!swap
} else {
true
}
}
}
}
#[inline]
fn bs_tile(p: &Blk, q: &Blk, mb_edge: bool) -> i32 {
let intra = !(p.inter & q.inter);
let nz = p.nz | q.nz;
let moved = bs1_tile(p, q);
let intra_bs = if mb_edge { 4 } else { 3 };
let non_intra = if nz { 2 } else { moved as i32 };
if intra {
intra_bs
} else {
non_intra
}
}
#[inline]
fn bs_inter(p: &Blk, q: &Blk) -> i32 {
let nz = p.nz | q.nz;
let moved = bs1_tile(p, q);
if nz {
2
} else {
moved as i32
}
}
#[inline]
fn derive_mb_bs(
tile: &Tile,
mb_x: usize,
mb_y: usize,
flat_inter: bool,
mb_t8: bool,
bs_v: &mut [[i32; 4]; 4],
bs_h: &mut [[i32; 4]; 4],
) {
let cur_intra = !tile[1][1].inter;
let uniform_motion = !cur_intra && {
let b0 = &tile[1][1];
(1..5).all(|r| (1..5).all(|c| tile[r][c].inter && tile[r][c].same_motion(b0)))
};
if mb_x > 0 {
bs_v[0] = if cur_intra || !tile[1][0].inter {
[4; 4]
} else {
std::array::from_fn(|seg| bs_inter(&tile[seg + 1][0], &tile[seg + 1][1]))
};
}
if mb_y > 0 {
bs_h[0] = if cur_intra || !tile[0][1].inter {
[4; 4]
} else {
std::array::from_fn(|seg| bs_inter(&tile[0][seg + 1], &tile[1][seg + 1]))
};
}
if flat_inter {
return; }
for be in 1..4usize {
if mb_t8 && (be == 1 || be == 3) {
continue;
}
if cur_intra {
bs_v[be] = [3; 4];
bs_h[be] = [3; 4];
} else if uniform_motion {
bs_v[be] = std::array::from_fn(|seg| {
2 * (tile[seg + 1][be].nz | tile[seg + 1][be + 1].nz) as i32
});
bs_h[be] = std::array::from_fn(|seg| {
2 * (tile[be][seg + 1].nz | tile[be + 1][seg + 1].nz) as i32
});
} else {
bs_v[be] = std::array::from_fn(|seg| bs_inter(&tile[seg + 1][be], &tile[seg + 1][be + 1]));
bs_h[be] = std::array::from_fn(|seg| bs_inter(&tile[be][seg + 1], &tile[be + 1][seg + 1]));
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum MbKind {
Intra,
Skip,
InterUniform,
Inter,
}
pub fn derive_mb_kind(info: &BlockInfo, mb_x: usize, mb_y: usize, kind: MbKind) -> MbBs {
let (bx0, by0) = (mb_x * 4, mb_y * 4);
let w4 = info.w4;
match kind {
MbKind::Intra => {
let mut m = MbBs::default();
if mb_x > 0 {
m.v[0] = [4; 4];
}
if mb_y > 0 {
m.h[0] = [4; 4];
}
for e in 1..4 {
m.v[e] = [3; 4];
m.h[e] = [3; 4];
}
m
}
MbKind::Skip => {
let mut m = MbBs::default();
let me = Blk::load(info, by0 * w4 + bx0); if mb_x > 0 {
m.v[0] = std::array::from_fn(|seg| {
let p = Blk::load(info, (by0 + seg) * w4 + bx0 - 1);
if p.inter { bs_inter(&p, &me) as u8 } else { 4 }
});
}
if mb_y > 0 {
m.h[0] = std::array::from_fn(|seg| {
let p = Blk::load(info, (by0 - 1) * w4 + bx0 + seg);
if p.inter { bs_inter(&p, &me) as u8 } else { 4 }
});
}
m
}
MbKind::InterUniform => {
let mut m = MbBs::default();
for e in 1..4usize {
m.v[e] = std::array::from_fn(|seg| {
let i = (by0 + seg) * w4 + bx0 + e;
2 * ((info.nnz[i] != 0) | (info.nnz[i - 1] != 0)) as u8
});
m.h[e] = std::array::from_fn(|seg| {
let i = (by0 + e) * w4 + bx0 + seg;
2 * ((info.nnz[i] != 0) | (info.nnz[i - w4] != 0)) as u8
});
}
if mb_x > 0 {
m.v[0] = std::array::from_fn(|seg| {
let qi = (by0 + seg) * w4 + bx0;
let p = Blk::load(info, qi - 1);
if p.inter { bs_inter(&p, &Blk::load(info, qi)) as u8 } else { 4 }
});
}
if mb_y > 0 {
m.h[0] = std::array::from_fn(|seg| {
let qi = by0 * w4 + bx0 + seg;
let p = Blk::load(info, qi - w4);
if p.inter { bs_inter(&p, &Blk::load(info, qi)) as u8 } else { 4 }
});
}
m
}
MbKind::Inter => derive_mb(info, mb_x, mb_y, false),
}
}
pub fn derive_mb(info: &BlockInfo, mb_x: usize, mb_y: usize, mb_t8: bool) -> MbBs {
let tile = gather_tile(info, mb_x, mb_y);
let b0 = &tile[1][1];
let flat_inter = b0.inter
&& (1..5).all(|r| {
(1..5).all(|c| {
let b = &tile[r][c];
b.inter && !b.nz && b.same_motion(b0)
})
});
let (mut bs_v, mut bs_h) = ([[0i32; 4]; 4], [[0i32; 4]; 4]);
derive_mb_bs(&tile, mb_x, mb_y, flat_inter, mb_t8, &mut bs_v, &mut bs_h);
let pack = |a: [[i32; 4]; 4]| a.map(|e| e.map(|x| x as u8));
MbBs { v: pack(bs_v), h: pack(bs_h) }
}
type Tile = [[Blk; 5]; 5];
fn gather_tile(info: &BlockInfo, mb_x: usize, mb_y: usize) -> Tile {
let mut t: Tile = Default::default();
let (bx0, by0) = (mb_x * 4, mb_y * 4);
for r in 0..4 {
let row = (by0 + r) * info.w4 + bx0;
for c in 0..4 {
t[r + 1][c + 1] = Blk::load(info, row + c);
}
}
if mb_x > 0 {
for r in 0..4 {
t[r + 1][0] = Blk::load(info, (by0 + r) * info.w4 + bx0 - 1);
}
}
if mb_y > 0 {
let row = (by0 - 1) * info.w4 + bx0;
for c in 0..4 {
t[0][c + 1] = Blk::load(info, row + c);
}
}
debug_assert!(
(1..5).all(|r| (1..5).all(|c| t[r][c].inter == t[1][1].inter)),
"macroblock ({mb_x},{mb_y}) mixes intra and inter 4x4 blocks"
);
t
}
static BS_MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
fn branchless_bs() -> bool {
use std::sync::atomic::Ordering;
match BS_MODE.load(Ordering::Relaxed) {
1 => true,
2 => false,
_ => {
let branchy = std::env::var_os("RS_H264_DEBLOCK_BRANCHY").is_some_and(|v| v != "0");
BS_MODE.store(if branchy { 2 } else { 1 }, Ordering::Relaxed);
!branchy
}
}
}
fn deblock_tile() -> bool {
branchless_bs()
}
static BS_PRECOMP: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
pub fn precomputed_bs_enabled() -> bool {
BS_PRECOMP.load(std::sync::atomic::Ordering::Relaxed) != 0
}
#[doc(hidden)]
pub fn set_precomputed_bs(on: bool) {
BS_PRECOMP.store(on as u8, std::sync::atomic::Ordering::Relaxed);
}
#[doc(hidden)]
pub fn set_branchless_bs(on: bool) {
BS_MODE.store(if on { 1 } else { 2 }, std::sync::atomic::Ordering::Relaxed);
}
impl BlockInfo<'_> {
#[inline]
fn at(&self, bx: usize, by: usize) -> usize {
by * self.w4 + bx
}
#[inline]
fn bs(&self, p: usize, q: usize, mb_edge: bool) -> i32 {
if branchless_bs() {
self.bs_branchless(p, q, mb_edge)
} else {
self.bs_branchy(p, q, mb_edge)
}
}
fn bs_branchy(&self, p: usize, q: usize, mb_edge: bool) -> i32 {
if !self.inter[p] || !self.inter[q] {
if mb_edge {
4
} else {
3
}
} else if self.nnz[p] > 0 || self.nnz[q] > 0 {
2
} else if self.inter_bs1(p, q) {
1
} else {
0
}
}
#[inline]
fn bs_branchless(&self, p: usize, q: usize, mb_edge: bool) -> i32 {
let intra = !(self.inter[p] & self.inter[q]);
let nz = (self.nnz[p] | self.nnz[q]) != 0;
let moved = self.inter_bs1(p, q);
let intra_bs = if mb_edge { 4 } else { 3 };
let motion_bs = moved as i32; let non_intra = if nz { 2 } else { motion_bs };
if intra {
intra_bs
} else {
non_intra
}
}
fn inter_bs1(&self, p: usize, q: usize) -> bool {
if self.ref_id1.is_empty() {
let (rp, rq) = (self.ref_id[p], self.ref_id[q]);
let (a, b) = (self.mv[p], self.mv[q]);
let far = ((a.0 - b.0).abs() >= 4) | ((a.1 - b.1).abs() >= 4);
return (rp != rq) | ((rp != NO_REF) & far);
}
let used = |i: usize| {
let mut v = [(0i32, (0i32, 0i32)); 2];
let mut n = 0;
if self.ref_id[i] != NO_REF {
v[n] = (self.ref_id[i], self.mv[i]);
n += 1;
}
if !self.ref_id1.is_empty() && self.ref_id1[i] != NO_REF {
v[n] = (self.ref_id1[i], self.mv1[i]);
n += 1;
}
(v, n)
};
let (pv, pn) = used(p);
let (qv, qn) = used(q);
if pn != qn {
return true; }
let far = |a: (i32, i32), b: (i32, i32)| (a.0 - b.0).abs() >= 4 || (a.1 - b.1).abs() >= 4;
match pn {
0 => false,
1 => pv[0].0 != qv[0].0 || far(pv[0].1, qv[0].1),
_ => {
let direct = !far(pv[0].1, qv[0].1) && !far(pv[1].1, qv[1].1);
let swap = !far(pv[0].1, qv[1].1) && !far(pv[1].1, qv[0].1);
if pv[0].0 == pv[1].0 {
qv[0].0 != pv[0].0 || qv[1].0 != pv[0].0 || !(direct || swap)
} else if pv[0].0 == qv[0].0 && pv[1].0 == qv[1].0 {
!direct
} else if pv[0].0 == qv[1].0 && pv[1].0 == qv[0].0 {
!swap
} else {
true }
}
}
}
}
#[inline]
fn thresholds(qpav: i32, offset_a: i32, offset_b: i32) -> (i32, i32, [i32; 3]) {
let ia = (qpav + offset_a).clamp(0, 51) as usize;
let ib = (qpav + offset_b).clamp(0, 51) as usize;
(ALPHA[ia], BETA[ib], TC0[ia])
}
#[allow(clippy::too_many_arguments)]
pub fn filter_frame(
y: &mut [u8],
u: &mut [u8],
v: &mut [u8],
mb_w: usize,
mb_h: usize,
mb_qp: &[u8],
chroma_qp_offset: i32,
offset_a: i32,
offset_b: i32,
info: &BlockInfo,
) {
let _g = crate::prof::scope(crate::prof::Stage::Deblock);
let cw = mb_w * 16;
let ccw = mb_w * 8;
let qpy = |mx: usize, my: usize| mb_qp[my * mb_w + mx] as i32;
let qpc = |qpy_val: i32| {
crate::predict::chroma_qp((qpy_val + chroma_qp_offset).clamp(0, 51) as u8) as i32
};
for mb_y in 0..mb_h {
for mb_x in 0..mb_w {
let mb_t8 = !info.t8x8.is_empty() && info.t8x8[mb_y * mb_w + mb_x];
let precomputed = !info.bs.is_empty();
let use_tile = !precomputed && deblock_tile();
let have_bs = precomputed || use_tile;
let tile = if use_tile { gather_tile(info, mb_x, mb_y) } else { Default::default() };
let flat_inter = if precomputed {
false } else if use_tile {
let b0 = &tile[1][1];
b0.inter
&& (1..5).all(|r| {
(1..5).all(|c| {
let b = &tile[r][c];
b.inter && !b.nz && b.same_motion(b0)
})
})
} else {
let b0 = info.at(mb_x * 4, mb_y * 4);
let mut ok = info.inter[b0];
if ok {
let (r0, m0) = (info.ref_id[b0], info.mv[b0]);
let has1 = !info.ref_id1.is_empty();
let (r10, m10) = if has1 { (info.ref_id1[b0], info.mv1[b0]) } else { (NO_REF, (0, 0)) };
'scan: for by in 0..4 {
for bx in 0..4 {
let i = info.at(mb_x * 4 + bx, mb_y * 4 + by);
if !info.inter[i]
|| info.nnz[i] != 0
|| info.ref_id[i] != r0
|| info.mv[i] != m0
|| (has1 && (info.ref_id1[i] != r10 || info.mv1[i] != m10))
{
ok = false;
break 'scan;
}
}
}
}
ok
};
let mut bs_v = [[0i32; 4]; 4];
let mut bs_h = [[0i32; 4]; 4];
if precomputed {
let m = &info.bs[mb_y * mb_w + mb_x];
for e in 0..4 {
for sg in 0..4 {
bs_v[e][sg] = m.v[e][sg] as i32;
bs_h[e][sg] = m.h[e][sg] as i32;
}
}
} else if use_tile {
derive_mb_bs(&tile, mb_x, mb_y, flat_inter, mb_t8, &mut bs_v, &mut bs_h);
}
for be in 0..4usize {
if be == 0 && mb_x == 0 {
continue;
}
if flat_inter && be != 0 {
continue; }
if mb_t8 && (be == 1 || be == 3) {
continue;
}
let mb_edge = be == 0;
let mut bs4 = [0i32; 4];
if have_bs {
bs4 = bs_v[be];
} else {
let abx = mb_x * 4 + be;
for (seg, b) in bs4.iter_mut().enumerate() {
let aby = mb_y * 4 + seg;
*b = info.bs(info.at(abx - 1, aby), info.at(abx, aby), mb_edge);
}
}
if bs4.iter().all(|&b| b == 0) {
continue;
}
let qpav = if mb_edge {
(qpy(mb_x - 1, mb_y) + qpy(mb_x, mb_y) + 1) >> 1
} else {
qpy(mb_x, mb_y)
};
let (alpha_y, beta_y, tc0a) = thresholds(qpav, offset_a, offset_b);
let tc0_luma = |bs: i32| if (1..4).contains(&bs) { tc0a[bs as usize - 1] } else { 0 };
let x = mb_x * 16 + be * 4;
#[cfg(accel)]
{
let base = mb_y * 16 * cw + (x - 4); if bs4.iter().all(|&b| b == 4) {
rusty_h264_accel::deblock_luma_eq4_h(&mut y[base..], cw, alpha_y, beta_y);
} else {
let tc: [i8; 4] = std::array::from_fn(|i| {
if (1..4).contains(&bs4[i]) { tc0_luma(bs4[i]) as i8 } else { -1 }
});
rusty_h264_accel::deblock_luma_lt4_h(&mut y[base..], cw, alpha_y, beta_y, &tc);
}
}
#[cfg(not(accel))]
for (seg, &bs) in bs4.iter().enumerate() {
if bs == 0 {
continue;
}
let tc0 = tc0_luma(bs);
for row in 0..4 {
let yy = mb_y * 16 + seg * 4 + row;
let line = Line { base: yy * cw + x, step: 1 };
filter_luma_line(y, &line, bs, alpha_y, beta_y, tc0);
}
}
}
for be in 0..4usize {
if be == 0 && mb_y == 0 {
continue;
}
if flat_inter && be != 0 {
continue; }
if mb_t8 && (be == 1 || be == 3) {
continue;
}
let mb_edge = be == 0;
let mut bs4 = [0i32; 4];
if have_bs {
bs4 = bs_h[be];
} else {
let aby = mb_y * 4 + be;
for (seg, b) in bs4.iter_mut().enumerate() {
let abx = mb_x * 4 + seg;
*b = info.bs(info.at(abx, aby - 1), info.at(abx, aby), mb_edge);
}
}
if bs4.iter().all(|&b| b == 0) {
continue;
}
let qpav = if mb_edge {
(qpy(mb_x, mb_y - 1) + qpy(mb_x, mb_y) + 1) >> 1
} else {
qpy(mb_x, mb_y)
};
let (alpha_y, beta_y, tc0a) = thresholds(qpav, offset_a, offset_b);
let tc0_luma = |bs: i32| if (1..4).contains(&bs) { tc0a[bs as usize - 1] } else { 0 };
let yy = mb_y * 16 + be * 4;
#[cfg(accel)]
{
let base = (yy - 4) * cw + mb_x * 16; if bs4.iter().all(|&b| b == 4) {
rusty_h264_accel::deblock_luma_eq4_v(&mut y[base..], cw, alpha_y, beta_y);
} else {
let tc: [i8; 4] = std::array::from_fn(|i| {
if (1..4).contains(&bs4[i]) { tc0_luma(bs4[i]) as i8 } else { -1 }
});
rusty_h264_accel::deblock_luma_lt4_v(&mut y[base..], cw, alpha_y, beta_y, &tc);
}
}
#[cfg(not(accel))]
for (seg, &bs) in bs4.iter().enumerate() {
if bs == 0 {
continue;
}
let tc0 = tc0_luma(bs);
for col in 0..4 {
let x = mb_x * 16 + seg * 4 + col;
let line = Line { base: yy * cw + x, step: cw as isize };
filter_luma_line(y, &line, bs, alpha_y, beta_y, tc0);
}
}
}
#[cfg(accel)]
{
let tc0_of = |arr: [i32; 3], bs: i32| if (1..4).contains(&bs) { arr[bs as usize - 1] } else { 0 };
let chroma_thresholds = |mb_edge: bool, nx: usize, ny: usize| {
let cur = qpc(qpy(mb_x, mb_y));
let q = if mb_edge { (qpc(qpy(nx, ny)) + cur + 1) >> 1 } else { cur };
thresholds(q, offset_a, offset_b)
};
for cxe in [0usize, 4] {
if cxe == 0 && mb_x == 0 {
continue;
}
if flat_inter && cxe != 0 {
continue; }
let mb_edge = cxe == 0;
let x = mb_x * 8 + cxe;
let mut bs4 = [0i32; 4];
if have_bs {
bs4 = bs_v[cxe / 2]; } else {
let abx = mb_x * 4 + cxe / 2;
for (seg, b) in bs4.iter_mut().enumerate() {
let aby = mb_y * 4 + seg;
*b = info.bs(info.at(abx - 1, aby), info.at(abx, aby), mb_edge);
}
}
if bs4.iter().all(|&b| b == 0) {
continue;
}
let (alpha_c, beta_c, tc0c) =
chroma_thresholds(mb_edge, mb_x.wrapping_sub(1), mb_y);
let base = (mb_y * 8) * ccw + (x - 2); if bs4.iter().all(|&b| b == 4) {
rusty_h264_accel::deblock_chroma_eq4_h(&mut u[base..], &mut v[base..], ccw, alpha_c, beta_c);
} else {
let tc: [i8; 4] = std::array::from_fn(|i| {
if (1..4).contains(&bs4[i]) { tc0_of(tc0c, bs4[i]) as i8 + 1 } else { 0 }
});
rusty_h264_accel::deblock_chroma_lt4_h(&mut u[base..], &mut v[base..], ccw, alpha_c, beta_c, &tc);
}
}
for cye in [0usize, 4] {
if cye == 0 && mb_y == 0 {
continue;
}
if flat_inter && cye != 0 {
continue; }
let mb_edge = cye == 0;
let yy = mb_y * 8 + cye;
let mut bs4 = [0i32; 4];
if have_bs {
bs4 = bs_h[cye / 2]; } else {
let aby = mb_y * 4 + cye / 2;
for (seg, b) in bs4.iter_mut().enumerate() {
let abx = mb_x * 4 + seg;
*b = info.bs(info.at(abx, aby - 1), info.at(abx, aby), mb_edge);
}
}
if bs4.iter().all(|&b| b == 0) {
continue;
}
let (alpha_c, beta_c, tc0c) =
chroma_thresholds(mb_edge, mb_x, mb_y.wrapping_sub(1));
let base = (yy - 2) * ccw + mb_x * 8; if bs4.iter().all(|&b| b == 4) {
rusty_h264_accel::deblock_chroma_eq4_v(&mut u[base..], &mut v[base..], ccw, alpha_c, beta_c);
} else {
let tc: [i8; 4] = std::array::from_fn(|i| {
if (1..4).contains(&bs4[i]) { tc0_of(tc0c, bs4[i]) as i8 + 1 } else { 0 }
});
rusty_h264_accel::deblock_chroma_lt4_v(&mut u[base..], &mut v[base..], ccw, alpha_c, beta_c, &tc);
}
}
}
#[cfg(not(accel))]
{
let cur_qpc = qpc(qpy(mb_x, mb_y));
let (alpha_cv, beta_cv, tc0cv) = if mb_x > 0 {
thresholds((qpc(qpy(mb_x - 1, mb_y)) + cur_qpc + 1) >> 1, offset_a, offset_b)
} else {
(0, 0, [0; 3]) };
let (alpha_ch, beta_ch, tc0ch) = if mb_y > 0 {
thresholds((qpc(qpy(mb_x, mb_y - 1)) + cur_qpc + 1) >> 1, offset_a, offset_b)
} else {
(0, 0, [0; 3])
};
let (alpha_ci, beta_ci, tc0ci) = thresholds(cur_qpc, offset_a, offset_b);
let tc0_of = |arr: [i32; 3], bs: i32| if (1..4).contains(&bs) { arr[bs as usize - 1] } else { 0 };
for plane in [&mut *u, &mut *v] {
for cxe in [0usize, 4] {
if cxe == 0 && mb_x == 0 {
continue;
}
if flat_inter && cxe != 0 {
continue;
}
let mb_edge = cxe == 0;
let (alpha_c, beta_c, tc0c) =
if mb_edge { (alpha_cv, beta_cv, tc0cv) } else { (alpha_ci, beta_ci, tc0ci) };
let abx = mb_x * 4 + cxe / 2; let x = mb_x * 8 + cxe;
for row in 0..8 {
let aby = mb_y * 4 + (row * 2) / 4; let bs = info.bs(info.at(abx - 1, aby), info.at(abx, aby), mb_edge);
if bs == 0 {
continue;
}
let yy = mb_y * 8 + row;
let line = Line { base: yy * ccw + x, step: 1 };
filter_chroma_line(plane, &line, bs, alpha_c, beta_c, tc0_of(tc0c, bs));
}
}
for cye in [0usize, 4] {
if cye == 0 && mb_y == 0 {
continue;
}
if flat_inter && cye != 0 {
continue;
}
let mb_edge = cye == 0;
let (alpha_c, beta_c, tc0c) =
if mb_edge { (alpha_ch, beta_ch, tc0ch) } else { (alpha_ci, beta_ci, tc0ci) };
let aby = mb_y * 4 + cye / 2;
let yy = mb_y * 8 + cye;
for col in 0..8 {
let abx = mb_x * 4 + (col * 2) / 4;
let bs = info.bs(info.at(abx, aby - 1), info.at(abx, aby), mb_edge);
if bs == 0 {
continue;
}
let line = Line { base: yy * ccw + (mb_x * 8 + col), step: ccw as isize };
filter_chroma_line(plane, &line, bs, alpha_c, beta_c, tc0_of(tc0c, bs));
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bs_arms_agree() {
let (w4, h4) = (16usize, 16usize);
let n = w4 * h4;
let mut st = 0x9e3779b9u32;
let mut rnd = || {
st ^= st << 13;
st ^= st >> 17;
st ^= st << 5;
st
};
let mut inter = vec![false; n];
let mut nnz = vec![0u8; n];
let mut mv = vec![(0i32, 0i32); n];
let mut ref_id = vec![0i32; n];
for i in 0..n {
let r = rnd();
inter[i] = r & 3 != 0;
nnz[i] = if r & 0x30 != 0 { (r >> 8 & 15) as u8 } else { 0 };
mv[i] = (((r >> 12) & 15) as i32 - 8, ((r >> 16) & 15) as i32 - 8);
ref_id[i] = if inter[i] { ((r >> 20) & 3) as i32 } else { NO_REF };
}
let info = BlockInfo {
inter: &inter,
nnz: &nnz,
mv: &mv,
ref_id: &ref_id,
mv1: &[],
ref_id1: &[],
w4,
t8x8: &[],
bs: &[],
};
let mut checked = 0;
for q in 0..n {
for &p in &[q.saturating_sub(1), q.saturating_sub(w4)] {
for mb_edge in [false, true] {
assert_eq!(
info.bs_branchy(p, q, mb_edge),
info.bs_branchless(p, q, mb_edge),
"bS mismatch at p={p} q={q} mb_edge={mb_edge}"
);
checked += 1;
}
}
}
assert!(checked > 1000, "expected broad coverage, checked {checked}");
}
}
#[cfg(test)]
mod tile_tests {
use super::*;
#[test]
fn tile_matches_frame_indexing() {
let (mb_w, mb_h) = (5usize, 4usize);
let w4 = mb_w * 4;
let n = w4 * mb_h * 4;
let mut st = 0xdeadbeefu32;
let mut rnd = || {
st ^= st << 13;
st ^= st >> 17;
st ^= st << 5;
st
};
let mut inter = vec![false; n];
let mut nnz = vec![0u8; n];
let mut mv = vec![(0i32, 0i32); n];
let mut ref_id = vec![0i32; n];
for i in 0..n {
let r = rnd();
inter[i] = r & 3 != 0;
nnz[i] = if r & 0x30 != 0 { (r >> 8 & 15) as u8 } else { 0 };
mv[i] = (((r >> 12) & 15) as i32 - 8, ((r >> 16) & 15) as i32 - 8);
ref_id[i] = if inter[i] { ((r >> 20) & 3) as i32 } else { NO_REF };
}
let info = BlockInfo {
inter: &inter, nnz: &nnz, mv: &mv, ref_id: &ref_id,
mv1: &[], ref_id1: &[], w4, t8x8: &[], bs: &[],
};
let mut checked = 0;
for mb_y in 0..mb_h {
for mb_x in 0..mb_w {
let tile = gather_tile(&info, mb_x, mb_y);
for be in 0..4 {
let mb_edge = be == 0;
if mb_edge && mb_x == 0 {
continue;
}
let abx = mb_x * 4 + be;
for seg in 0..4 {
let aby = mb_y * 4 + seg;
assert_eq!(
info.bs(info.at(abx - 1, aby), info.at(abx, aby), mb_edge),
bs_tile(&tile[seg + 1][be], &tile[seg + 1][be + 1], mb_edge),
"luma V mb=({mb_x},{mb_y}) be={be} seg={seg}"
);
checked += 1;
}
}
for be in 0..4 {
let mb_edge = be == 0;
if mb_edge && mb_y == 0 {
continue;
}
let aby = mb_y * 4 + be;
for seg in 0..4 {
let abx = mb_x * 4 + seg;
assert_eq!(
info.bs(info.at(abx, aby - 1), info.at(abx, aby), mb_edge),
bs_tile(&tile[be][seg + 1], &tile[be + 1][seg + 1], mb_edge),
"luma H mb=({mb_x},{mb_y}) be={be} seg={seg}"
);
checked += 1;
}
}
for cxe in [0usize, 4] {
let mb_edge = cxe == 0;
if mb_edge && mb_x == 0 {
continue;
}
let abx = mb_x * 4 + cxe / 2;
for seg in 0..4 {
let aby = mb_y * 4 + seg;
assert_eq!(
info.bs(info.at(abx - 1, aby), info.at(abx, aby), mb_edge),
bs_tile(&tile[seg + 1][cxe / 2], &tile[seg + 1][cxe / 2 + 1], mb_edge),
"chroma V mb=({mb_x},{mb_y}) cxe={cxe} seg={seg}"
);
checked += 1;
}
}
for cye in [0usize, 4] {
let mb_edge = cye == 0;
if mb_edge && mb_y == 0 {
continue;
}
let aby = mb_y * 4 + cye / 2;
for seg in 0..4 {
let abx = mb_x * 4 + seg;
assert_eq!(
info.bs(info.at(abx, aby - 1), info.at(abx, aby), mb_edge),
bs_tile(&tile[cye / 2][seg + 1], &tile[cye / 2 + 1][seg + 1], mb_edge),
"chroma H mb=({mb_x},{mb_y}) cye={cye} seg={seg}"
);
checked += 1;
}
}
}
}
assert!(checked > 800, "coverage too low: {checked}");
}
}
#[cfg(test)]
mod chroma_bs_tests {
use super::*;
#[test]
fn chroma_bs_matches_luma() {
let (mb_w, mb_h) = (5usize, 4usize);
let w4 = mb_w * 4;
let n = w4 * mb_h * 4;
let mut st = 0x1badb002u32;
let mut rnd = || {
st ^= st << 13;
st ^= st >> 17;
st ^= st << 5;
st
};
let (mut inter, mut nnz) = (vec![false; n], vec![0u8; n]);
let (mut mv, mut ref_id) = (vec![(0i32, 0i32); n], vec![0i32; n]);
for i in 0..n {
let r = rnd();
inter[i] = r & 3 != 0;
nnz[i] = if r & 0x30 != 0 { (r >> 8 & 15) as u8 } else { 0 };
mv[i] = (((r >> 12) & 15) as i32 - 8, ((r >> 16) & 15) as i32 - 8);
ref_id[i] = if inter[i] { ((r >> 20) & 3) as i32 } else { NO_REF };
}
let info = BlockInfo {
inter: &inter, nnz: &nnz, mv: &mv, ref_id: &ref_id,
mv1: &[], ref_id1: &[], w4, t8x8: &[], bs: &[],
};
let mut checked = 0;
for mb_y in 0..mb_h {
for mb_x in 0..mb_w {
let tile = gather_tile(&info, mb_x, mb_y);
for (cxe, be) in [(0usize, 0usize), (4, 2)] {
let mb_edge = cxe == 0;
for seg in 0..4 {
assert_eq!(
bs_tile(&tile[seg + 1][cxe / 2], &tile[seg + 1][cxe / 2 + 1], mb_edge),
bs_tile(&tile[seg + 1][be], &tile[seg + 1][be + 1], mb_edge),
"V mb=({mb_x},{mb_y}) cxe={cxe} seg={seg}"
);
assert_eq!(
bs_tile(&tile[cxe / 2][seg + 1], &tile[cxe / 2 + 1][seg + 1], mb_edge),
bs_tile(&tile[be][seg + 1], &tile[be + 1][seg + 1], mb_edge),
"H mb=({mb_x},{mb_y}) cye={cxe} seg={seg}"
);
checked += 2;
}
}
}
}
assert!(checked > 300, "coverage too low: {checked}");
}
}
#[cfg(test)]
mod derive_tests {
use super::*;
#[test]
fn derive_matches_per_edge() {
let (mb_w, mb_h) = (6usize, 5usize);
let w4 = mb_w * 4;
let n = w4 * mb_h * 4;
let mut st = 0xfeedfaceu32;
let mut rnd = || {
st ^= st << 13;
st ^= st >> 17;
st ^= st << 5;
st
};
let (mut inter, mut nnz) = (vec![false; n], vec![0u8; n]);
let (mut mv, mut ref_id) = (vec![(0i32, 0i32); n], vec![0i32; n]);
for my in 0..mb_h {
for mx in 0..mb_w {
let mb_inter = rnd() & 3 != 0;
for by in 0..4 {
for bx in 0..4 {
let i = (my * 4 + by) * w4 + mx * 4 + bx;
let r = rnd();
inter[i] = mb_inter;
nnz[i] = if r & 0x30 != 0 { (r >> 8 & 15) as u8 } else { 0 };
mv[i] = (((r >> 12) & 15) as i32 - 8, ((r >> 16) & 15) as i32 - 8);
ref_id[i] = if mb_inter { ((r >> 20) & 3) as i32 } else { NO_REF };
}
}
}
}
let info = BlockInfo {
inter: &inter, nnz: &nnz, mv: &mv, ref_id: &ref_id,
mv1: &[], ref_id1: &[], w4, t8x8: &[], bs: &[],
};
let mut checked = 0;
for mb_y in 0..mb_h {
for mb_x in 0..mb_w {
let tile = gather_tile(&info, mb_x, mb_y);
let b0 = &tile[1][1];
let flat = b0.inter
&& (1..5).all(|r| (1..5).all(|c| {
let b = &tile[r][c];
b.inter && !b.nz && b.same_motion(b0)
}));
for &mb_t8 in &[false, true] {
let (mut bv, mut bh) = ([[0i32; 4]; 4], [[0i32; 4]; 4]);
derive_mb_bs(&tile, mb_x, mb_y, flat, mb_t8, &mut bv, &mut bh);
for be in 0..4usize {
let mb_edge = be == 0;
let skip_internal =
!mb_edge && (flat || (mb_t8 && (be == 1 || be == 3)));
for seg in 0..4 {
let want_v = if skip_internal || (mb_edge && mb_x == 0) {
0
} else {
bs_tile(&tile[seg + 1][be], &tile[seg + 1][be + 1], mb_edge)
};
let want_h = if skip_internal || (mb_edge && mb_y == 0) {
0
} else {
bs_tile(&tile[be][seg + 1], &tile[be + 1][seg + 1], mb_edge)
};
assert_eq!(bv[be][seg], want_v, "V mb=({mb_x},{mb_y}) be={be} seg={seg} t8={mb_t8}");
assert_eq!(bh[be][seg], want_h, "H mb=({mb_x},{mb_y}) be={be} seg={seg} t8={mb_t8}");
checked += 2;
}
}
}
}
}
assert!(checked > 1500, "coverage too low: {checked}");
}
}