use crate::decode::{SliceDeblock, qpc};
use crate::exec::ExecContext;
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 exec: ExecContext,
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 chroma_idc: u8,
pub cb_qp_offset: i32,
pub cr_qp_offset: i32,
pub beta_offset: i32,
pub tc_offset: i32,
pub deblocking_disabled: bool,
pub default_qp: i16,
pub log2_ctb: u32,
pub qp_y_map: &'a [i16],
pub tqb: &'a [bool],
pub edge_v: &'a [bool],
pub edge_h: &'a [bool],
pub bs_v: &'a [u8],
pub bs_h: &'a [u8],
pub pcm: &'a [bool],
pub slice_idx: &'a [u16],
pub slice_deblock: &'a [SliceDeblock],
pub slice_lf_across: &'a [bool],
pub pcm_loop_filter_disabled: bool,
pub loop_filter_across_slices: bool,
pub tile_grid: Option<&'a crate::tiles::TileGrid>,
}
#[derive(Default)]
pub(crate) struct DeblockScratch {
pub(crate) snap_y: Vec<u16>,
pub(crate) snap_cb: Vec<u16>,
pub(crate) snap_cr: Vec<u16>,
pub(crate) suppressed: Vec<bool>,
bands: Vec<(usize, usize)>,
}
impl DeblockScratch {
pub(crate) fn new(
y_len: usize,
chroma_len: usize,
grid_len: usize,
plane_heights: (usize, usize),
log2_ctb: u32,
sub_h: usize,
) -> Self {
let (h, ch) = plane_heights;
let ctb = 1usize << log2_ctb;
let cband = (ctb / sub_h.max(1)).max(1);
let band_capacity = h.div_ceil(ctb).max(ch.div_ceil(cband)) + 1;
Self {
snap_y: Vec::with_capacity(y_len),
snap_cb: Vec::with_capacity(chroma_len),
snap_cr: Vec::with_capacity(chroma_len),
suppressed: Vec::with_capacity(grid_len),
bands: Vec::with_capacity(band_capacity),
}
}
#[inline]
fn snapshot(&mut self, y: &[u16], cb: &[u16], cr: &[u16]) {
self.snap_y.clear();
self.snap_y.extend_from_slice(y);
self.snap_cb.clear();
self.snap_cb.extend_from_slice(cb);
self.snap_cr.clear();
self.snap_cr.extend_from_slice(cr);
}
}
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 grid(&self, px: usize, py: usize) -> Option<usize> {
if px >= self.w || py >= self.h || self.gw == 0 {
return None;
}
Some((py / 4) * self.gw + (px / 4))
}
#[inline]
fn bs_v_at(&self, px: usize, py: usize) -> u8 {
if px == 0 {
return 0;
}
let g = match self.grid(px, py) {
Some(g) => g,
None => return 0,
};
if !self.edge_v.get(g).copied().unwrap_or(false) {
return 0;
}
if !self.filter_across(px - 1, py, px, py) {
return 0;
}
self.bs_v.get(g).copied().unwrap_or(0)
}
#[inline]
fn bs_h_at(&self, px: usize, py: usize) -> u8 {
if py == 0 {
return 0;
}
let g = match self.grid(px, py) {
Some(g) => g,
None => return 0,
};
if !self.edge_h.get(g).copied().unwrap_or(false) {
return 0;
}
if !self.filter_across(px, py - 1, px, py) {
return 0;
}
self.bs_h.get(g).copied().unwrap_or(0)
}
#[inline]
fn filter_across(&self, pxp: usize, pyp: usize, pxq: usize, pyq: usize) -> bool {
let sq = self.slice_at(pxq, pyq);
let q_across = self
.slice_lf_across
.get(sq as usize)
.copied()
.unwrap_or(self.loop_filter_across_slices);
if !q_across && self.slice_at(pxp, pyp) != sq {
return false;
}
if let Some(g) = &self.tile_grid {
let c = self.log2_ctb;
if g.tile_id_at(pxp >> c, pyp >> c) != g.tile_id_at(pxq >> c, pyq >> c) {
return false;
}
}
true
}
#[inline]
fn slice_at(&self, px: usize, py: usize) -> u16 {
self.grid(px, py)
.and_then(|g| self.slice_idx.get(g))
.copied()
.unwrap_or(0)
}
#[inline]
fn slice_deblock_at(&self, px: usize, py: usize) -> SliceDeblock {
self.slice_deblock
.get(self.slice_at(px, py) as usize)
.copied()
.unwrap_or(SliceDeblock {
disabled: self.deblocking_disabled,
beta_offset_div2: self.beta_offset / 2,
tc_offset_div2: self.tc_offset / 2,
})
}
#[inline]
fn sample_suppressed(&self, grid: usize) -> bool {
self.tqb.get(grid).copied().unwrap_or(false)
|| (self.pcm_loop_filter_disabled && self.pcm.get(grid).copied().unwrap_or(false))
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum LumaDecision {
Skip,
Strong,
Weak { do_p1: bool, do_q1: bool },
}
#[inline]
pub(crate) fn luma_decision(beta: i32, tc: i32, g: impl Fn(usize, i32) -> i32) -> LumaDecision {
let dp0 = (g(0, -3) - 2 * g(0, -2) + g(0, -1)).abs();
let dp3 = (g(3, -3) - 2 * g(3, -2) + g(3, -1)).abs();
let dq0 = (g(0, 2) - 2 * g(0, 1) + g(0, 0)).abs();
let dq3 = (g(3, 2) - 2 * g(3, 1) + g(3, 0)).abs();
if dp0 + dp3 + dq0 + dq3 >= beta {
return LumaDecision::Skip;
}
let dsam = |line: usize, dpq: i32| -> bool {
2 * dpq < (beta >> 2)
&& (g(line, -4) - g(line, -1)).abs() + (g(line, 0) - g(line, 3)).abs() < (beta >> 3)
&& (g(line, -1) - g(line, 0)).abs() < (5 * tc + 1) >> 1
};
if dsam(0, dp0 + dq0) && dsam(3, dp3 + dq3) {
LumaDecision::Strong
} else {
let side_thr = (beta + (beta >> 1)) >> 3;
LumaDecision::Weak {
do_p1: dp0 + dp3 < side_thr,
do_q1: dq0 + dq3 < side_thr,
}
}
}
#[inline]
pub(crate) fn deblock_luma_segment(
plane: &mut [u16],
beta: i32,
tc: i32,
max_val: i32,
at: impl Fn(usize, i32) -> usize,
) {
let g = |plane: &[u16], line: usize, tap: i32| plane[at(line, tap)] as i32;
let decision = luma_decision(beta, tc, |line, tap| g(plane, line, tap));
let (strong, do_p1, do_q1) = match decision {
LumaDecision::Skip => return,
LumaDecision::Strong => (true, false, false),
LumaDecision::Weak { do_p1, do_q1 } => (false, do_p1, do_q1),
};
let tc2 = tc >> 1;
for line in 0..4 {
let p0 = g(plane, line, -1);
let p1 = g(plane, line, -2);
let p2 = g(plane, line, -3);
let p3 = g(plane, line, -4);
let q0 = g(plane, line, 0);
let q1 = g(plane, line, 1);
let q2 = g(plane, line, 2);
let q3 = g(plane, line, 3);
if strong {
let c = |orig: i32, v: i32| -> u16 {
v.clamp(orig - 2 * tc, orig + 2 * tc).clamp(0, max_val) as u16
};
plane[at(line, -1)] = c(p0, (p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3);
plane[at(line, -2)] = c(p1, (p2 + p1 + p0 + q0 + 2) >> 2);
plane[at(line, -3)] = c(p2, (2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3);
plane[at(line, 0)] = c(q0, (p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3);
plane[at(line, 1)] = c(q1, (p0 + q0 + q1 + q2 + 2) >> 2);
plane[at(line, 2)] = c(q2, (p0 + q0 + q1 + 3 * q2 + 2 * q3 + 4) >> 3);
} else {
let delta0 = (9 * (q0 - p0) - 3 * (q1 - p1) + 8) >> 4;
if delta0.abs() < tc * 10 {
let delta = delta0.clamp(-tc, tc);
plane[at(line, -1)] = (p0 + delta).clamp(0, max_val) as u16;
plane[at(line, 0)] = (q0 - delta).clamp(0, max_val) as u16;
if do_p1 {
let dp1 = ((((p2 + p0 + 1) >> 1) - p1 + delta) >> 1).clamp(-tc2, tc2);
plane[at(line, -2)] = (p1 + dp1).clamp(0, max_val) as u16;
}
if do_q1 {
let dq1 = ((((q2 + q0 + 1) >> 1) - q1 - delta) >> 1).clamp(-tc2, tc2);
plane[at(line, 1)] = (q1 + dq1).clamp(0, max_val) as u16;
}
}
}
}
}
fn luma_vertical_segment_params(
ctx: &DeblockCtx<'_>,
y: &[u16],
w: usize,
row0: usize,
row1: usize,
edge: usize,
s: usize,
) -> Option<(LumaDecision, i32)> {
let mid = s + 1;
let bs = ctx.bs_v_at(edge, mid);
if bs == 0 {
return None;
}
let bs = bs as i32;
let sd = ctx.slice_deblock_at(edge, mid);
if sd.disabled {
return None;
}
let qp_p = ctx.qp_at(edge - 1, mid);
let qp_q = ctx.qp_at(edge, mid);
let avg_qp = (qp_p + qp_q + 1) >> 1;
let beta_prime = (avg_qp + sd.beta_offset_div2 * 2).clamp(0, 51);
let tc_prime = (avg_qp + 2 * (bs - 1) + sd.tc_offset_div2 * 2).clamp(0, 53);
let bd_shift = (ctx.bd - 8) as u32;
let beta = BETA[beta_prime as usize] << bd_shift;
let tc = TC[tc_prime as usize] << bd_shift;
if tc == 0 {
return None;
}
let dec = luma_decision(beta, tc, |line, tap| {
let col = (edge as i32 + tap) as usize;
y[(s + line - row0) * w + col] as i32
});
let _ = row1;
(dec != LumaDecision::Skip).then_some((dec, tc))
}
#[inline]
fn luma_horizontal_segment_params(
ctx: &DeblockCtx<'_>,
y: &[u16],
w: usize,
row0: usize,
edge: usize,
scan: usize,
) -> Option<(LumaDecision, i32)> {
let mid = scan + 1;
let bs = ctx.bs_h_at(mid, edge);
if bs == 0 {
return None;
}
let bs = bs as i32;
let sd = ctx.slice_deblock_at(mid, edge);
if sd.disabled {
return None;
}
let qp_p = ctx.qp_at(mid, edge - 1);
let qp_q = ctx.qp_at(mid, edge);
let avg_qp = (qp_p + qp_q + 1) >> 1;
let beta_prime = (avg_qp + sd.beta_offset_div2 * 2).clamp(0, 51);
let tc_prime = (avg_qp + 2 * (bs - 1) + sd.tc_offset_div2 * 2).clamp(0, 53);
let bd_shift = (ctx.bd - 8) as u32;
let beta = BETA[beta_prime as usize] << bd_shift;
let tc = TC[tc_prime as usize] << bd_shift;
if tc == 0 {
return None;
}
let dec = luma_decision(beta, tc, |line, tap| {
let row = (edge as i32 + tap) as usize - row0;
y[row * w + (scan + line)] as i32
});
(dec != LumaDecision::Skip).then_some((dec, tc))
}
fn luma_vertical(ctx: &DeblockCtx<'_>, y: &mut [u16], row0: usize, row1: usize) {
let w = ctx.w;
let maxv = (1i32 << ctx.bd) - 1;
let filter = ctx.exec.luma_deblock_vertical;
let pair_filter = ctx.exec.luma_deblock_vertical_pair;
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 {
if let Some(pair_filter) = pair_filter
&& s + 8 <= row1
{
let first = luma_vertical_segment_params(ctx, y, w, row0, row1, edge, s);
let second = luma_vertical_segment_params(ctx, y, w, row0, row1, edge, s + 4);
match (first, second) {
(Some((d0, tc0)), Some((d1, tc1))) => {
pair_filter(y, w, edge, s, row0, d0, tc0, d1, tc1, maxv);
s += 8;
continue;
}
(None, _) => {
s += 4;
continue;
}
(Some((d0, tc0)), None) => {
filter(y, w, edge, s, row0, d0, tc0, maxv);
s += 4;
continue;
}
}
}
if let Some((dec, tc)) = luma_vertical_segment_params(ctx, y, w, row0, row1, edge, s) {
filter(y, w, edge, s, row0, dec, tc, maxv);
}
s += 4;
}
edge += 8;
}
}
fn luma_horizontal(ctx: &DeblockCtx<'_>, y: &mut [u16], row0: usize, row1: usize) {
let w = ctx.w;
let h = ctx.h;
let maxv = (1i32 << ctx.bd) - 1;
let filter = ctx.exec.luma_deblock_horizontal;
let pair_filter = ctx.exec.luma_deblock_horizontal_pair;
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 {
if let Some(pair_filter) = pair_filter
&& scan + 8 <= w
{
let first = luma_horizontal_segment_params(ctx, y, w, row0, edge, scan);
let second = luma_horizontal_segment_params(ctx, y, w, row0, edge, scan + 4);
match (first, second) {
(Some((d0, tc0)), Some((d1, tc1))) => {
pair_filter(y, w, edge, scan, row0, d0, tc0, d1, tc1, maxv);
scan += 8;
continue;
}
(None, _) => {
scan += 4;
continue;
}
(Some((d0, tc0)), None) => {
filter(y, w, edge, scan, row0, d0, tc0, maxv);
scan += 4;
continue;
}
}
}
if let Some((dec, tc)) = luma_horizontal_segment_params(ctx, y, w, row0, edge, scan) {
filter(y, w, edge, scan, row0, dec, tc, maxv);
}
scan += 4;
}
edge += 8;
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) type LumaDeblockPlaneFn =
fn(&mut [u16], usize, usize, usize, usize, LumaDecision, i32, i32);
#[allow(clippy::too_many_arguments)]
pub(crate) type LumaDeblockPairFn =
fn(&mut [u16], usize, usize, usize, usize, LumaDecision, i32, LumaDecision, i32, i32);
static LUMA_VERTICAL_PLANE: std::sync::OnceLock<LumaDeblockPlaneFn> = std::sync::OnceLock::new();
static LUMA_HORIZONTAL_PLANE: std::sync::OnceLock<LumaDeblockPlaneFn> = std::sync::OnceLock::new();
static LUMA_VERTICAL_PAIR: std::sync::OnceLock<Option<LumaDeblockPairFn>> =
std::sync::OnceLock::new();
static LUMA_HORIZONTAL_PAIR: std::sync::OnceLock<Option<LumaDeblockPairFn>> =
std::sync::OnceLock::new();
#[inline]
pub(crate) fn resolve_luma_vertical_plane() -> LumaDeblockPlaneFn {
*LUMA_VERTICAL_PLANE.get_or_init(|| {
let mut _f: LumaDeblockPlaneFn = luma_vertical_plane_scalar;
#[cfg(all(feature = "neon", target_arch = "aarch64"))]
{
_f = crate::neon::luma_vertical_plane_neon;
}
#[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
{
if std::is_x86_feature_detected!("sse4.1") {
_f = crate::sse::luma_vertical_plane_sse41;
}
}
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = crate::avx::luma_vertical_plane_avx2;
}
}
_f
})
}
#[inline]
pub(crate) fn resolve_luma_vertical_pair() -> Option<LumaDeblockPairFn> {
*LUMA_VERTICAL_PAIR.get_or_init(|| {
let mut _f: Option<LumaDeblockPairFn> = None;
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = Some(crate::avx::luma_vertical_plane_pair_avx2);
}
}
_f
})
}
#[inline]
pub(crate) fn resolve_luma_horizontal_plane() -> LumaDeblockPlaneFn {
*LUMA_HORIZONTAL_PLANE.get_or_init(|| {
let mut _f: LumaDeblockPlaneFn = luma_horizontal_plane_scalar;
#[cfg(all(feature = "neon", target_arch = "aarch64"))]
{
_f = crate::neon::luma_horizontal_plane_neon;
}
#[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
{
if std::is_x86_feature_detected!("sse4.1") {
_f = crate::sse::luma_horizontal_plane_sse41;
}
}
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = crate::avx::luma_horizontal_plane_avx2;
}
}
_f
})
}
#[inline]
pub(crate) fn resolve_luma_horizontal_pair() -> Option<LumaDeblockPairFn> {
*LUMA_HORIZONTAL_PAIR.get_or_init(|| {
let mut _f: Option<LumaDeblockPairFn> = None;
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = Some(crate::avx::luma_horizontal_plane_pair_avx2);
}
}
_f
})
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub(crate) fn apply_luma_decision(
plane: &mut [u16],
decision: LumaDecision,
tc: i32,
max_val: i32,
at: impl Fn(usize, i32) -> usize,
) {
let (strong, do_p1, do_q1) = match decision {
LumaDecision::Skip => return,
LumaDecision::Strong => (true, false, false),
LumaDecision::Weak { do_p1, do_q1 } => (false, do_p1, do_q1),
};
let tc2 = tc >> 1;
let g = |plane: &[u16], line: usize, tap: i32| plane[at(line, tap)] as i32;
for line in 0..4 {
let p0 = g(plane, line, -1);
let p1 = g(plane, line, -2);
let p2 = g(plane, line, -3);
let p3 = g(plane, line, -4);
let q0 = g(plane, line, 0);
let q1 = g(plane, line, 1);
let q2 = g(plane, line, 2);
let q3 = g(plane, line, 3);
if strong {
let c = |orig: i32, v: i32| -> u16 {
v.clamp(orig - 2 * tc, orig + 2 * tc).clamp(0, max_val) as u16
};
plane[at(line, -1)] = c(p0, (p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3);
plane[at(line, -2)] = c(p1, (p2 + p1 + p0 + q0 + 2) >> 2);
plane[at(line, -3)] = c(p2, (2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3);
plane[at(line, 0)] = c(q0, (p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3);
plane[at(line, 1)] = c(q1, (p0 + q0 + q1 + q2 + 2) >> 2);
plane[at(line, 2)] = c(q2, (p0 + q0 + q1 + 3 * q2 + 2 * q3 + 4) >> 3);
} else {
let delta0 = (9 * (q0 - p0) - 3 * (q1 - p1) + 8) >> 4;
if delta0.abs() < tc * 10 {
let delta = delta0.clamp(-tc, tc);
plane[at(line, -1)] = (p0 + delta).clamp(0, max_val) as u16;
plane[at(line, 0)] = (q0 - delta).clamp(0, max_val) as u16;
if do_p1 {
let dp1 = ((((p2 + p0 + 1) >> 1) - p1 + delta) >> 1).clamp(-tc2, tc2);
plane[at(line, -2)] = (p1 + dp1).clamp(0, max_val) as u16;
}
if do_q1 {
let dq1 = ((((q2 + q0 + 1) >> 1) - q1 - delta) >> 1).clamp(-tc2, tc2);
plane[at(line, 1)] = (q1 + dq1).clamp(0, max_val) as u16;
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn luma_vertical_plane_scalar(
pix: &mut [u16],
w: usize,
edge: usize,
s: usize,
row0: usize,
decision: LumaDecision,
tc: i32,
maxv: i32,
) {
apply_luma_decision(pix, decision, tc, maxv, |line, tap| {
let col = (edge as i32 + tap) as usize;
(s + line - row0) * w + col
});
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn luma_horizontal_plane_scalar(
pix: &mut [u16],
w: usize,
edge: usize,
scan: usize,
row0: usize,
decision: LumaDecision,
tc: i32,
maxv: i32,
) {
apply_luma_decision(pix, decision, tc, maxv, |line, tap| {
let row = (edge as i32 + tap) as usize - row0;
row * w + (scan + line)
});
}
#[allow(clippy::too_many_arguments)]
pub(crate) type ChromaDeblockPlaneFn = fn(&mut [u16], usize, usize, usize, usize, i32, i32);
#[allow(clippy::too_many_arguments)]
pub(crate) type ChromaDeblockPairFn = fn(&mut [u16], usize, usize, usize, usize, i32, i32, i32);
static CHROMA_VERTICAL_PLANE: std::sync::OnceLock<ChromaDeblockPlaneFn> =
std::sync::OnceLock::new();
static CHROMA_HORIZONTAL_PLANE: std::sync::OnceLock<ChromaDeblockPlaneFn> =
std::sync::OnceLock::new();
static CHROMA_VERTICAL_PAIR: std::sync::OnceLock<Option<ChromaDeblockPairFn>> =
std::sync::OnceLock::new();
static CHROMA_HORIZONTAL_PAIR: std::sync::OnceLock<Option<ChromaDeblockPairFn>> =
std::sync::OnceLock::new();
#[inline]
pub(crate) fn resolve_chroma_vertical_plane() -> ChromaDeblockPlaneFn {
*CHROMA_VERTICAL_PLANE.get_or_init(|| {
let mut _f: ChromaDeblockPlaneFn = chroma_vertical_plane_scalar;
#[cfg(all(feature = "neon", target_arch = "aarch64"))]
{
_f = crate::neon::chroma_vertical_plane_neon;
}
#[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
{
if std::is_x86_feature_detected!("sse4.1") {
_f = crate::sse::chroma_vertical_plane_sse41;
}
}
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = crate::avx::chroma_vertical_plane_avx2;
}
}
_f
})
}
#[inline]
pub(crate) fn resolve_chroma_vertical_pair() -> Option<ChromaDeblockPairFn> {
*CHROMA_VERTICAL_PAIR.get_or_init(|| {
let mut _f: Option<ChromaDeblockPairFn> = None;
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = Some(crate::avx::chroma_vertical_plane_pair_avx2);
}
}
_f
})
}
#[inline]
pub(crate) fn resolve_chroma_horizontal_plane() -> ChromaDeblockPlaneFn {
*CHROMA_HORIZONTAL_PLANE.get_or_init(|| {
let mut _f: ChromaDeblockPlaneFn = chroma_horizontal_plane_scalar;
#[cfg(all(feature = "neon", target_arch = "aarch64"))]
{
_f = crate::neon::chroma_horizontal_plane_neon;
}
#[cfg(all(feature = "sse", any(target_arch = "x86", target_arch = "x86_64")))]
{
if std::is_x86_feature_detected!("sse4.1") {
_f = crate::sse::chroma_horizontal_plane_sse41;
}
}
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = crate::avx::chroma_horizontal_plane_avx2;
}
}
_f
})
}
#[inline]
pub(crate) fn resolve_chroma_horizontal_pair() -> Option<ChromaDeblockPairFn> {
*CHROMA_HORIZONTAL_PAIR.get_or_init(|| {
let mut _f: Option<ChromaDeblockPairFn> = None;
#[cfg(all(feature = "avx", target_arch = "x86_64"))]
{
if std::is_x86_feature_detected!("avx2") {
_f = Some(crate::avx::chroma_horizontal_plane_pair_avx2);
}
}
_f
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn chroma_vertical_plane_scalar(
pix: &mut [u16],
cw: usize,
edge: usize,
s: usize,
crow0: usize,
tc_c: i32,
maxv_c: i32,
) {
for r in s..s + 4 {
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;
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn chroma_horizontal_plane_scalar(
pix: &mut [u16],
cw: usize,
edge: usize,
scan: usize,
crow0: usize,
tc_c: i32,
maxv_c: i32,
) {
for c in scan..scan + 4 {
let base = |gy: usize| (gy - crow0) * cw + c;
let p0 = pix[base(edge - 1)] as i32;
let p1 = pix[base(edge - 2)] as i32;
let q0 = pix[base(edge)] as i32;
let q1 = pix[base(edge + 1)] as i32;
let delta = (((q0 - p0) * 4 + p1 - q1 + 4) >> 3).clamp(-tc_c, tc_c);
if delta != 0 {
pix[base(edge - 1)] = (p0 + delta).clamp(0, maxv_c) as u16;
pix[base(edge)] = (q0 - delta).clamp(0, maxv_c) as u16;
}
}
}
#[inline]
fn chroma_tcs(ctx: &DeblockCtx<'_>, avg_qp_l: i32, tc_offset: i32) -> (i32, i32) {
let shift = (ctx.bd_c - 8) as u32;
let tc_for = |offset: i32| {
let qp_c = qpc(avg_qp_l + offset, ctx.chroma_idc, ctx.bd_c);
let tc_prime = (qp_c + 2 + tc_offset).clamp(0, 53);
TC[tc_prime as usize] << shift
};
(tc_for(ctx.cb_qp_offset), tc_for(ctx.cr_qp_offset))
}
#[inline]
fn chroma_vertical_segment_tc(ctx: &DeblockCtx<'_>, edge: usize, s: usize) -> Option<(i32, i32)> {
let mid = s + 1;
let qlx = edge * ctx.sub_w;
let qly = mid * ctx.sub_h;
if ctx.bs_v_at(qlx, qly) < 2 {
return None;
}
let lqx = qlx.min(ctx.w.saturating_sub(1));
let lqy = qly.min(ctx.h.saturating_sub(1));
let sd = ctx.slice_deblock_at(lqx, lqy);
if sd.disabled {
return None;
}
let lpx = qlx.saturating_sub(1).min(ctx.w.saturating_sub(1));
let qp_p = ctx.qp_at(lpx, lqy);
let qp_q = ctx.qp_at(lqx, lqy);
let avg_qp_l = (qp_p + qp_q + 1) >> 1;
Some(chroma_tcs(ctx, avg_qp_l, sd.tc_offset_div2 * 2))
}
#[inline]
fn chroma_horizontal_segment_tc(
ctx: &DeblockCtx<'_>,
edge: usize,
scan: usize,
) -> Option<(i32, i32)> {
let mid = scan + 1;
let qlx = mid * ctx.sub_w;
let qly = edge * ctx.sub_h;
if ctx.bs_h_at(qlx, qly) < 2 {
return None;
}
let lqx = qlx.min(ctx.w.saturating_sub(1));
let lqy = qly.min(ctx.h.saturating_sub(1));
let sd = ctx.slice_deblock_at(lqx, lqy);
if sd.disabled {
return None;
}
let lpy = qly.saturating_sub(1).min(ctx.h.saturating_sub(1));
let qp_p = ctx.qp_at(lqx, lpy);
let qp_q = ctx.qp_at(lqx, lqy);
let avg_qp_l = (qp_p + qp_q + 1) >> 1;
Some(chroma_tcs(ctx, avg_qp_l, sd.tc_offset_div2 * 2))
}
fn chroma_vertical(
ctx: &DeblockCtx<'_>,
cb: &mut [u16],
cr: &mut [u16],
crow0: usize,
crow1: usize,
) {
let cw = ctx.cw;
let maxv_c = (1i32 << ctx.bd_c) - 1;
let filter = ctx.exec.chroma_deblock_vertical;
let pair_filter = ctx.exec.chroma_deblock_vertical_pair;
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 {
if let Some(pair_filter) = pair_filter
&& s + 8 <= crow1
{
let first = chroma_vertical_segment_tc(ctx, edge, s);
let second = chroma_vertical_segment_tc(ctx, edge, s + 4);
match (first, second) {
(Some((cb0, cr0)), Some((cb1, cr1))) => {
if cb0 != 0 || cb1 != 0 {
pair_filter(cb, cw, edge, s, crow0, cb0, cb1, maxv_c);
}
if cr0 != 0 || cr1 != 0 {
pair_filter(cr, cw, edge, s, crow0, cr0, cr1, maxv_c);
}
s += 8;
continue;
}
(None, _) => {
s += 4;
continue;
}
(Some((tc_cb, tc_cr)), None) => {
if tc_cb != 0 {
filter(cb, cw, edge, s, crow0, tc_cb, maxv_c);
}
if tc_cr != 0 {
filter(cr, cw, edge, s, crow0, tc_cr, maxv_c);
}
s += 4;
continue;
}
}
}
if let Some((tc_cb, tc_cr)) = chroma_vertical_segment_tc(ctx, edge, s) {
if tc_cb != 0 {
filter(cb, cw, edge, s, crow0, tc_cb, maxv_c);
}
if tc_cr != 0 {
filter(cr, cw, edge, s, crow0, tc_cr, maxv_c);
}
}
s += 4;
}
edge += 8;
}
}
fn chroma_horizontal(
ctx: &DeblockCtx<'_>,
cb: &mut [u16],
cr: &mut [u16],
crow0: usize,
crow1: usize,
) {
let cw = ctx.cw;
let ch = ctx.ch;
let maxv_c = (1i32 << ctx.bd_c) - 1;
let filter = ctx.exec.chroma_deblock_horizontal;
let pair_filter = ctx.exec.chroma_deblock_horizontal_pair;
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 {
if let Some(pair_filter) = pair_filter
&& scan + 8 <= cw
{
let first = chroma_horizontal_segment_tc(ctx, edge, scan);
let second = chroma_horizontal_segment_tc(ctx, edge, scan + 4);
match (first, second) {
(Some((cb0, cr0)), Some((cb1, cr1))) => {
if cb0 != 0 || cb1 != 0 {
pair_filter(cb, cw, edge, scan, crow0, cb0, cb1, maxv_c);
}
if cr0 != 0 || cr1 != 0 {
pair_filter(cr, cw, edge, scan, crow0, cr0, cr1, maxv_c);
}
scan += 8;
continue;
}
(None, _) => {
scan += 4;
continue;
}
(Some((tc_cb, tc_cr)), None) => {
if tc_cb != 0 {
filter(cb, cw, edge, scan, crow0, tc_cb, maxv_c);
}
if tc_cr != 0 {
filter(cr, cw, edge, scan, crow0, tc_cr, maxv_c);
}
scan += 4;
continue;
}
}
}
if let Some((tc_cb, tc_cr)) = chroma_horizontal_segment_tc(ctx, edge, scan) {
if tc_cb != 0 {
filter(cb, cw, edge, scan, crow0, tc_cb, maxv_c);
}
if tc_cr != 0 {
filter(cr, cw, edge, scan, crow0, tc_cr, maxv_c);
}
}
scan += 4;
}
edge += 8;
}
}
fn ctb_bands(bands: &mut Vec<(usize, usize)>, total: usize, ctb: usize) {
bands.clear();
let mut r = 0;
while r < total {
let end = (r + ctb).min(total);
bands.push((r, end));
r = end;
}
}
fn horiz_bands(bands: &mut Vec<(usize, usize)>, total: usize, ctb: usize) {
bands.clear();
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;
}
}
pub(crate) struct DeblockPlanes {
pub y: Vec<u16>,
pub cb: Vec<u16>,
pub cr: Vec<u16>,
}
#[inline]
fn suppression_active(ctx: &DeblockCtx<'_>) -> bool {
ctx.tqb.iter().any(|&v| v) || (ctx.pcm_loop_filter_disabled && ctx.pcm.iter().any(|&v| v))
}
fn restore_luma(ctx: &DeblockCtx<'_>, plane: &mut [u16], snapshot: &[u16]) {
for gy in 0..ctx.gh {
let grid_row = gy * ctx.gw;
for gx in 0..ctx.gw {
if !ctx.sample_suppressed(grid_row + gx) {
continue;
}
let x0 = gx * 4;
let y0 = gy * 4;
let width = ctx.w.saturating_sub(x0).min(4);
let height = ctx.h.saturating_sub(y0).min(4);
if width == 0 || height == 0 {
continue;
}
for row in 0..height {
let off = (y0 + row) * ctx.w + x0;
plane[off..off + width].copy_from_slice(&snapshot[off..off + width]);
}
}
}
}
fn restore_chroma(ctx: &DeblockCtx<'_>, plane: &mut [u16], snapshot: &[u16]) {
for cy in 0..ctx.ch {
let ly = cy * ctx.sub_h;
let grid_row = (ly / 4) * ctx.gw;
for cx in 0..ctx.cw {
let lx = cx * ctx.sub_w;
let grid = grid_row + lx / 4;
if ctx.sample_suppressed(grid) {
let off = cy * ctx.cw + cx;
plane[off] = snapshot[off];
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn apply_deblocking_parallel(
pool: &ThreadPool,
ctx: &DeblockCtx<'_>,
log2_ctb: u32,
scratch: &mut DeblockScratch,
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 suppress = suppression_active(ctx);
if suppress {
scratch.snapshot(&y, &cb, &cr);
}
{
ctb_bands(&mut scratch.bands, ctx.h, ctb);
let bands = &scratch.bands;
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();
if suppress {
restore_luma(ctx, &mut y, &scratch.snap_y);
}
}
{
horiz_bands(&mut scratch.bands, ctx.h, ctb);
let bands = &scratch.bands;
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, r0, r1);
});
y = y_dm.into_inner();
if suppress {
restore_luma(ctx, &mut y, &scratch.snap_y);
}
}
if ctx.cw > 0 && ctx.ch > 0 {
let cband = (ctb / ctx.sub_h).max(1);
ctb_bands(&mut scratch.bands, ctx.ch, cband);
let bands = &scratch.bands;
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 suppress {
restore_chroma(ctx, &mut cb, &scratch.snap_cb);
restore_chroma(ctx, &mut cr, &scratch.snap_cr);
}
}
if ctx.cw > 0 && ctx.ch > 0 {
let cband = (ctb / ctx.sub_h).max(1);
horiz_bands(&mut scratch.bands, ctx.ch, cband);
let bands = &scratch.bands;
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, r0, r1);
});
cb = cb_dm.into_inner();
cr = cr_dm.into_inner();
if suppress {
restore_chroma(ctx, &mut cb, &scratch.snap_cb);
restore_chroma(ctx, &mut cr, &scratch.snap_cr);
}
}
DeblockPlanes { y, cb, cr }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exec::ExecContext;
#[test]
fn simd_luma_kernels_match_scalar_reference() {
let simd_v = resolve_luma_vertical_plane();
let simd_h = resolve_luma_horizontal_plane();
let decisions = [
LumaDecision::Strong,
LumaDecision::Weak {
do_p1: true,
do_q1: true,
},
LumaDecision::Weak {
do_p1: true,
do_q1: false,
},
LumaDecision::Weak {
do_p1: false,
do_q1: true,
},
LumaDecision::Weak {
do_p1: false,
do_q1: false,
},
];
let w = 16usize;
for &tc in &[1i32, 2, 3, 5, 8, 14] {
for &maxv in &[255i32, 1023] {
for &(pv, qv) in &[(0u16, 255u16), (10, 240), (60, 190), (128, 132), (300, 700)] {
let (pv, qv) = (pv.min(maxv as u16), qv.min(maxv as u16));
let mut vbase = vec![0u16; w * 4];
for row in 0..4 {
for col in 0..w {
vbase[row * w + col] = if col < 8 { pv } else { qv };
}
}
let mut hbase = vec![0u16; w * 16];
for row in 0..16 {
for col in 0..w {
hbase[row * w + col] = if row < 8 { pv } else { qv };
}
}
for &dec in &decisions {
let mut a = vbase.clone();
let mut b = vbase.clone();
luma_vertical_plane_scalar(&mut a, w, 8, 0, 0, dec, tc, maxv);
simd_v(&mut b, w, 8, 0, 0, dec, tc, maxv);
assert_eq!(
a, b,
"vertical SIMD vs scalar diverged: tc={tc} maxv={maxv} pv={pv} qv={qv} dec={dec:?}"
);
let mut a = hbase.clone();
let mut b = hbase.clone();
luma_horizontal_plane_scalar(&mut a, w, 8, 0, 0, dec, tc, maxv);
simd_h(&mut b, w, 8, 0, 0, dec, tc, maxv);
assert_eq!(
a, b,
"horizontal SIMD vs scalar diverged: tc={tc} maxv={maxv} pv={pv} qv={qv} dec={dec:?}"
);
}
}
}
}
}
fn ctx<'a>(
edge_v: &'a [bool],
edge_h: &'a [bool],
bs_v: &'a [u8],
bs_h: &'a [u8],
tqb: &'a [bool],
pcm: &'a [bool],
slice_idx: &'a [u16],
across_slices: bool,
pcm_lf_disabled: bool,
) -> DeblockCtx<'a> {
DeblockCtx {
exec: ExecContext::new(),
w: 8,
h: 8,
cw: 4,
ch: 4,
gw: 2,
gh: 2,
sub_w: 2,
sub_h: 2,
bd: 8,
bd_c: 8,
chroma_idc: 1,
cb_qp_offset: 0,
cr_qp_offset: 0,
beta_offset: 0,
tc_offset: 0,
deblocking_disabled: false,
default_qp: 26,
log2_ctb: 6,
qp_y_map: &[],
tqb,
edge_v,
edge_h,
bs_v,
bs_h,
pcm,
slice_idx,
slice_deblock: &[],
slice_lf_across: &[],
pcm_loop_filter_disabled: pcm_lf_disabled,
loop_filter_across_slices: across_slices,
tile_grid: None,
}
}
#[test]
fn bs_zero_when_not_a_real_edge() {
let edge = [false; 4];
let bs = [2u8; 4];
let no = [false; 4];
let s = [0u16; 4];
let c = ctx(&edge, &edge, &bs, &bs, &no, &no, &s, true, false);
assert_eq!(c.bs_v_at(4, 0), 0);
assert_eq!(c.bs_h_at(0, 4), 0);
}
#[test]
fn bs_reported_at_real_edge() {
let mut edge_v = [false; 4];
let mut bs_v = [0u8; 4];
edge_v[1] = true; bs_v[1] = 2;
let z = [false; 4];
let zb = [0u8; 4];
let s = [0u16; 4];
let c = ctx(&edge_v, &z, &bs_v, &zb, &z, &z, &s, true, false);
assert_eq!(c.bs_v_at(4, 0), 2);
}
#[test]
fn tqb_and_pcm_suppress_only_the_exempt_samples() {
let mut edge_v = [false; 4];
let mut bs_v = [0u8; 4];
edge_v[1] = true;
bs_v[1] = 2;
let z = [false; 4];
let zb = [0u8; 4];
let s = [0u16; 4];
let mut tqb = [false; 4];
tqb[0] = true;
let c = ctx(&edge_v, &z, &bs_v, &zb, &tqb, &z, &s, true, false);
assert_eq!(c.bs_v_at(4, 0), 2);
assert!(c.sample_suppressed(0));
let mut pcm = [false; 4];
pcm[1] = true;
let c2 = ctx(&edge_v, &z, &bs_v, &zb, &z, &pcm, &s, true, true);
assert_eq!(c2.bs_v_at(4, 0), 2);
assert!(c2.sample_suppressed(1));
let c3 = ctx(&edge_v, &z, &bs_v, &zb, &z, &pcm, &s, true, false);
assert_eq!(c3.bs_v_at(4, 0), 2);
assert!(!c3.sample_suppressed(1));
}
#[test]
fn cross_slice_gate() {
let mut edge_v = [false; 4];
let mut bs_v = [0u8; 4];
edge_v[1] = true;
bs_v[1] = 2;
let z = [false; 4];
let zb = [0u8; 4];
let s = [0u16, 1, 0, 1];
let c = ctx(&edge_v, &z, &bs_v, &zb, &z, &z, &s, false, false);
assert_eq!(c.bs_v_at(4, 0), 0);
let c2 = ctx(&edge_v, &z, &bs_v, &zb, &z, &z, &s, true, false);
assert_eq!(c2.bs_v_at(4, 0), 2);
}
#[test]
fn q_side_slice_controls_cross_slice_and_offsets() {
let mut edge_v = [false; 4];
let mut bs_v = [0u8; 4];
edge_v[1] = true;
bs_v[1] = 2;
let z = [false; 4];
let zb = [0u8; 4];
let owners = [0u16, 1, 0, 1];
let slices = [
SliceDeblock {
disabled: false,
beta_offset_div2: 0,
tc_offset_div2: 0,
},
SliceDeblock {
disabled: false,
beta_offset_div2: 2,
tc_offset_div2: -1,
},
];
let across = [true, false];
let mut c = ctx(&edge_v, &z, &bs_v, &zb, &z, &z, &owners, true, false);
c.slice_deblock = &slices;
c.slice_lf_across = &across;
assert_eq!(c.bs_v_at(4, 0), 0);
let sd = c.slice_deblock_at(4, 0);
assert_eq!(sd.beta_offset_div2, 2);
assert_eq!(sd.tc_offset_div2, -1);
}
}