use crate::threadpool::{DisjointMut, ThreadPool, parallel_for};
#[rustfmt::skip]
static BETA: [i32; 52] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 6, 7, 8, 9,
10,11,12,13,14,15,16,17,18,20,
22,24,26,28,30,32,34,36,38,40,
42,44,46,48,50,52,54,56,58,60,
62,64,
];
#[rustfmt::skip]
static TC: [i32; 54] = [
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,1,
1,1,1,1,1,1,1,2,2,2,
2,3,3,3,3,4,4,4,5,5,
6,6,7,8,9,10,11,13,14,16,
18,20,22,24,
];
pub(crate) struct DeblockCtx<'a> {
pub w: usize,
pub h: usize,
pub cw: usize,
pub ch: usize,
pub gw: usize,
pub gh: usize,
pub sub_w: usize,
pub sub_h: usize,
pub bd: u8,
pub bd_c: u8,
pub beta_offset: i32,
pub tc_offset: i32,
pub qp_bd_offset_y: i32,
pub qp_bd_offset_c: i32,
pub default_qp: i16,
pub qp_y_map: &'a [i16],
pub tqb: &'a [bool],
}
impl DeblockCtx<'_> {
#[inline]
fn qp_at(&self, px: usize, py: usize) -> i32 {
if self.qp_y_map.is_empty() || self.gw == 0 || self.gh == 0 {
return self.default_qp as i32;
}
let gx = (px / 4).min(self.gw.saturating_sub(1));
let gy = (py / 4).min(self.gh.saturating_sub(1));
gy.checked_mul(self.gw)
.and_then(|base| base.checked_add(gx))
.and_then(|idx| self.qp_y_map.get(idx))
.copied()
.unwrap_or(self.default_qp) as i32
}
#[inline]
fn tqb_at(&self, px: usize, py: usize) -> bool {
if px >= self.w || py >= self.h {
return false;
}
let idx = (py / 4) * self.gw + (px / 4);
self.tqb.get(idx).copied().unwrap_or(false)
}
}
fn luma_vertical(ctx: &DeblockCtx<'_>, y: &mut [u16], row0: usize, row1: usize) {
let w = ctx.w;
let maxv = (1i32 << ctx.bd) - 1;
let last_full_edge = w.saturating_sub(4);
let mut edge = 8;
while edge <= last_full_edge {
let mut s = row0;
while s + 4 <= row1 {
let mid = s + 1;
let qp_p = ctx.qp_at(edge - 1, mid);
let qp_q = ctx.qp_at(edge, mid);
if ctx.tqb_at(edge - 1, mid) || ctx.tqb_at(edge, mid) {
s += 4;
continue;
}
let avg_qp = (qp_p + qp_q + 1) >> 1;
let beta_prime = (avg_qp + ctx.qp_bd_offset_y + ctx.beta_offset).clamp(0, 51);
let tc_prime = (avg_qp + ctx.qp_bd_offset_y + 2 + ctx.tc_offset).clamp(0, 53);
let beta = BETA[beta_prime as usize];
let tc = TC[tc_prime as usize];
if tc == 0 {
s += 4;
continue;
}
let seg_end = (s + 4).min(row1);
let mut d_total = 0i32;
for r in s..seg_end {
let lr = r - row0;
let p = |o: usize| y[lr * w + edge - 1 - o] as i32;
let q = |o: usize| y[lr * w + edge + o] as i32;
d_total += (p(2) - 2 * p(1) + p(0)).abs() + (q(0) - 2 * q(1) + q(2)).abs();
}
if d_total >= beta {
s += 4;
continue;
}
for r in s..seg_end {
let lr = r - row0;
let base_p = lr * w + edge - 1;
let base_q = lr * w + edge;
let (p0, p1, p2, p3) = (
y[base_p] as i32,
y[base_p - 1] as i32,
y[base_p - 2] as i32,
y[base_p - 3] as i32,
);
let (q0, q1, q2, q3) = (
y[base_q] as i32,
y[base_q + 1] as i32,
y[base_q + 2] as i32,
y[base_q + 3] as i32,
);
let dp = (p2 - 2 * p1 + p0).abs();
let dq = (q2 - 2 * q1 + q0).abs();
let d = dp + dq;
let strong = d < (beta >> 2)
&& (p0 - q0).abs() < (5 * tc + 1) >> 1
&& (p3 - p0).abs() + (q0 - q3).abs() < (beta * 3) >> 3;
if strong {
y[base_p] =
((p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3).clamp(0, maxv) as u16;
y[base_p - 1] = ((p2 + p1 + p0 + q0 + 2) >> 2).clamp(0, maxv) as u16;
y[base_p - 2] =
((2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3).clamp(0, maxv) as u16;
y[base_q] =
((p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3).clamp(0, maxv) as u16;
y[base_q + 1] = ((p0 + q0 + q1 + q2 + 2) >> 2).clamp(0, maxv) as u16;
y[base_q + 2] =
((p0 + q0 + q1 + 3 * q2 + 2 * q3 + 4) >> 3).clamp(0, maxv) as u16;
} else {
let delta = ((9 * (q0 - p0) - 3 * (q1 - p1) + 8) >> 4).clamp(-tc, tc);
y[base_p] = (p0 + delta).clamp(0, maxv) as u16;
y[base_q] = (q0 - delta).clamp(0, maxv) as u16;
let thres = (tc * 10 + 1) >> 1;
if (2 * (p0 - p1) - delta).abs() < thres {
let dp1 =
(((p2 + p0 + 1) >> 1) - p1 + (delta >> 1)).clamp(-(tc >> 1), tc >> 1);
y[base_p - 1] = (p1 + dp1).clamp(0, maxv) as u16;
}
if (2 * (q0 - q1) + delta).abs() < thres {
let dq1 =
(((q2 + q0 + 1) >> 1) - q1 - (delta >> 1)).clamp(-(tc >> 1), tc >> 1);
y[base_q + 1] = (q1 + dq1).clamp(0, maxv) as u16;
}
}
}
s += 4;
}
edge += 8;
}
}
fn luma_horizontal(ctx: &DeblockCtx<'_>, dst: &mut [u16], src: &[u16], row0: usize, row1: usize) {
let w = ctx.w;
let h = ctx.h;
let maxv = (1i32 << ctx.bd) - 1;
let last_full_edge = h.saturating_sub(4);
let mut edge = (row0.div_ceil(8) * 8).max(8);
while edge <= last_full_edge {
if edge >= row1 {
break;
}
let mut scan = 0;
while scan + 4 <= w {
let mid = scan + 1;
let qp_p = ctx.qp_at(mid, edge - 1);
let qp_q = ctx.qp_at(mid, edge);
if ctx.tqb_at(mid, edge - 1) || ctx.tqb_at(mid, edge) {
scan += 4;
continue;
}
let avg_qp = (qp_p + qp_q + 1) >> 1;
let beta_prime = (avg_qp + ctx.qp_bd_offset_y + ctx.beta_offset).clamp(0, 51);
let tc_prime = (avg_qp + ctx.qp_bd_offset_y + 2 + ctx.tc_offset).clamp(0, 53);
let beta = BETA[beta_prime as usize];
let tc = TC[tc_prime as usize];
if tc == 0 {
scan += 4;
continue;
}
let mut d_total = 0i32;
for c in scan..scan + 4 {
if c >= w {
break;
}
let p = |o: usize| src[(edge - 1 - o) * w + c] as i32;
let q = |o: usize| src[(edge + o) * w + c] as i32;
d_total += (p(2) - 2 * p(1) + p(0)).abs() + (q(0) - 2 * q(1) + q(2)).abs();
}
if d_total >= beta {
scan += 4;
continue;
}
for c in scan..scan + 4 {
if c >= w {
continue;
}
let (p0, p1, p2, p3) = (
src[(edge - 1) * w + c] as i32,
src[(edge - 2) * w + c] as i32,
src[(edge - 3) * w + c] as i32,
if edge >= 4 {
src[(edge - 4) * w + c] as i32
} else {
0
},
);
let (q0, q1, q2, q3) = (
src[(edge) * w + c] as i32,
src[(edge + 1) * w + c] as i32,
src[(edge + 2) * w + c] as i32,
if edge + 3 < h {
src[(edge + 3) * w + c] as i32
} else {
0
},
);
let dp = (p2 - 2 * p1 + p0).abs();
let dq = (q2 - 2 * q1 + q0).abs();
let d = dp + dq;
let strong = d < (beta >> 2)
&& (p0 - q0).abs() < (5 * tc + 1) >> 1
&& (p3 - p0).abs() + (q0 - q3).abs() < (beta * 3) >> 3;
let put = |dst: &mut [u16], gy: usize, val: i32| {
dst[(gy - row0) * w + c] = val.clamp(0, maxv) as u16;
};
if strong {
put(dst, edge - 1, (p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3);
put(dst, edge - 2, (p2 + p1 + p0 + q0 + 2) >> 2);
put(dst, edge - 3, (2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3);
put(dst, edge, (p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3);
put(dst, edge + 1, (p0 + q0 + q1 + q2 + 2) >> 2);
put(dst, edge + 2, (p0 + q0 + q1 + 3 * q2 + 2 * q3 + 4) >> 3);
} else {
let delta = ((9 * (q0 - p0) - 3 * (q1 - p1) + 8) >> 4).clamp(-tc, tc);
put(dst, edge - 1, p0 + delta);
put(dst, edge, q0 - delta);
let thres = (tc * 10 + 1) >> 1;
if (2 * (p0 - p1) - delta).abs() < thres {
let dp1 =
(((p2 + p0 + 1) >> 1) - p1 + (delta >> 1)).clamp(-(tc >> 1), tc >> 1);
put(dst, edge - 2, p1 + dp1);
}
if (2 * (q0 - q1) + delta).abs() < thres {
let dq1 =
(((q2 + q0 + 1) >> 1) - q1 - (delta >> 1)).clamp(-(tc >> 1), tc >> 1);
put(dst, edge + 1, q1 + dq1);
}
}
}
scan += 4;
}
edge += 8;
}
}
fn chroma_vertical(
ctx: &DeblockCtx<'_>,
cb: &mut [u16],
cr: &mut [u16],
crow0: usize,
crow1: usize,
) {
let cw = ctx.cw;
let ch = ctx.ch;
let w = ctx.w;
let h = ctx.h;
let maxv_c = (1i32 << ctx.bd_c) - 1;
let last_full_chroma_edge = cw.saturating_sub(2);
let mut edge = 8;
while edge <= last_full_chroma_edge {
let mut s = crow0;
while s + 4 <= crow1 {
let mid = s + 1;
let qlx = edge * ctx.sub_w;
let qly = mid * ctx.sub_h;
let avg_qp_l = ctx.qp_at(qlx.min(w - 1), qly.min(h - 1));
let tc_prime_c = (avg_qp_l + ctx.qp_bd_offset_c + 2 + ctx.tc_offset).clamp(0, 53);
let tc_c = TC[tc_prime_c as usize];
if tc_c == 0 {
s += 4;
continue;
}
let px_p = (edge - 1) * ctx.sub_w;
let py_p = mid * ctx.sub_h;
let px_q = edge * ctx.sub_w;
let py_q = mid * ctx.sub_h;
if ctx.tqb_at(px_p, py_p) || ctx.tqb_at(px_q, py_q) {
s += 4;
continue;
}
let seg_end = (s + 4).min(crow1);
for plane in 0..2 {
let pix: &mut [u16] = if plane == 0 { &mut *cb } else { &mut *cr };
for r in s..seg_end {
if r >= ch {
continue;
}
let lr = r - crow0;
let p0 = pix[lr * cw + edge - 1] as i32;
let p1 = pix[lr * cw + edge - 2] as i32;
let q0 = pix[lr * cw + edge] as i32;
let q1 = pix[lr * cw + edge + 1] as i32;
let delta = (((q0 - p0) * 4 + p1 - q1 + 4) >> 3).clamp(-tc_c, tc_c);
if delta != 0 {
pix[lr * cw + edge - 1] = (p0 + delta).clamp(0, maxv_c) as u16;
pix[lr * cw + edge] = (q0 - delta).clamp(0, maxv_c) as u16;
}
}
}
s += 4;
}
edge += 8;
}
}
fn chroma_horizontal(
ctx: &DeblockCtx<'_>,
cb_dst: &mut [u16],
cr_dst: &mut [u16],
cb_src: &[u16],
cr_src: &[u16],
crow0: usize,
crow1: usize,
) {
let cw = ctx.cw;
let ch = ctx.ch;
let w = ctx.w;
let h = ctx.h;
let maxv_c = (1i32 << ctx.bd_c) - 1;
let last_full_chroma_edge = ch.saturating_sub(2);
let mut edge = (crow0.div_ceil(8) * 8).max(8);
while edge <= last_full_chroma_edge {
if edge >= crow1 {
break;
}
let mut scan = 0;
while scan + 4 <= cw {
let mid = scan + 1;
let qlx = mid * ctx.sub_w;
let qly = edge * ctx.sub_h;
let avg_qp_l = ctx.qp_at(qlx.min(w - 1), qly.min(h - 1));
let tc_prime_c = (avg_qp_l + ctx.qp_bd_offset_c + 2 + ctx.tc_offset).clamp(0, 53);
let tc_c = TC[tc_prime_c as usize];
if tc_c == 0 {
scan += 4;
continue;
}
let px_p = mid * ctx.sub_w;
let py_p = (edge - 1) * ctx.sub_h;
let px_q = mid * ctx.sub_w;
let py_q = edge * ctx.sub_h;
if ctx.tqb_at(px_p, py_p) || ctx.tqb_at(px_q, py_q) {
scan += 4;
continue;
}
for plane in 0..2 {
let (dst, src): (&mut [u16], &[u16]) = if plane == 0 {
(&mut *cb_dst, cb_src)
} else {
(&mut *cr_dst, cr_src)
};
for c in scan..scan + 4 {
if c >= cw {
continue;
}
let p0 = src[(edge - 1) * cw + c] as i32;
let p1 = src[(edge - 2) * cw + c] as i32;
let q0 = src[(edge) * cw + c] as i32;
let q1 = src[(edge + 1) * cw + c] as i32;
let delta = (((q0 - p0) * 4 + p1 - q1 + 4) >> 3).clamp(-tc_c, tc_c);
if delta != 0 {
dst[(edge - 1 - crow0) * cw + c] = (p0 + delta).clamp(0, maxv_c) as u16;
dst[(edge - crow0) * cw + c] = (q0 - delta).clamp(0, maxv_c) as u16;
}
}
}
scan += 4;
}
edge += 8;
}
}
fn ctb_bands(total: usize, ctb: usize) -> Vec<(usize, usize)> {
let mut bands = Vec::new();
let mut r = 0;
while r < total {
let end = (r + ctb).min(total);
bands.push((r, end));
r = end;
}
bands
}
fn horiz_bands(total: usize, ctb: usize) -> Vec<(usize, usize)> {
let mut bands = Vec::new();
let mut r = 0;
while r < total {
let target = r + ctb;
if target >= total {
bands.push((r, total));
break;
}
let rem = target % 8;
let end = if rem <= 4 {
target + (4 - rem)
} else {
target + (12 - rem)
};
let end = end.min(total);
bands.push((r, end));
r = end;
}
bands
}
pub(crate) struct DeblockPlanes {
pub y: Vec<u16>,
pub cb: Vec<u16>,
pub cr: Vec<u16>,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn apply_deblocking_parallel(
pool: &ThreadPool,
ctx: &DeblockCtx<'_>,
log2_ctb: u32,
mut y: Vec<u16>,
mut cb: Vec<u16>,
mut cr: Vec<u16>,
) -> DeblockPlanes {
let ctb = 1usize << log2_ctb;
let w = ctx.w;
let cw = ctx.cw;
{
let bands = ctb_bands(ctx.h, ctb);
let y_dm = DisjointMut::new(std::mem::take(&mut y));
parallel_for(pool, bands.len(), |bi| {
let (r0, r1) = bands[bi];
let mut band = y_dm.slice_mut(r0 * w..r1 * w);
luma_vertical(ctx, &mut band, r0, r1);
});
y = y_dm.into_inner();
}
{
let src = y.clone();
let bands = horiz_bands(ctx.h, ctb);
let y_dm = DisjointMut::new(std::mem::take(&mut y));
parallel_for(pool, bands.len(), |bi| {
let (r0, r1) = bands[bi];
let mut band = y_dm.slice_mut(r0 * w..r1 * w);
luma_horizontal(ctx, &mut band, &src, r0, r1);
});
y = y_dm.into_inner();
}
if ctx.cw > 0 && ctx.ch > 0 {
let cband = (ctb / ctx.sub_h).max(1);
let bands = ctb_bands(ctx.ch, cband);
let cb_dm = DisjointMut::new(std::mem::take(&mut cb));
let cr_dm = DisjointMut::new(std::mem::take(&mut cr));
parallel_for(pool, bands.len(), |bi| {
let (r0, r1) = bands[bi];
let mut cbb = cb_dm.slice_mut(r0 * cw..r1 * cw);
let mut crb = cr_dm.slice_mut(r0 * cw..r1 * cw);
chroma_vertical(ctx, &mut cbb, &mut crb, r0, r1);
});
cb = cb_dm.into_inner();
cr = cr_dm.into_inner();
}
if ctx.cw > 0 && ctx.ch > 0 {
let cb_src = cb.clone();
let cr_src = cr.clone();
let cband = (ctb / ctx.sub_h).max(1);
let bands = horiz_bands(ctx.ch, cband);
let cb_dm = DisjointMut::new(std::mem::take(&mut cb));
let cr_dm = DisjointMut::new(std::mem::take(&mut cr));
parallel_for(pool, bands.len(), |bi| {
let (r0, r1) = bands[bi];
let mut cbb = cb_dm.slice_mut(r0 * cw..r1 * cw);
let mut crb = cr_dm.slice_mut(r0 * cw..r1 * cw);
chroma_horizontal(ctx, &mut cbb, &mut crb, &cb_src, &cr_src, r0, r1);
});
cb = cb_dm.into_inner();
cr = cr_dm.into_inner();
}
DeblockPlanes { y, cb, cr }
}