use crate::{
cdf::MI_SIZE,
uncompressed_header_tail::{INTRA_FRAME, MAX_LOOP_FILTER, TOTAL_REFS_PER_FRAME},
};
pub const NEARESTMV: u8 = crate::cdf::MODE_NEARESTMV;
pub const GLOBALMV: u8 = crate::cdf::MODE_GLOBALMV;
pub const GLOBAL_GLOBALMV: u8 = crate::cdf::MODE_GLOBAL_GLOBALMV;
pub const SEG_LVL_ALT_LF_Y_V: usize = 1;
#[derive(Debug)]
pub struct PlaneBuffer<'a> {
pub rows: u32,
pub cols: u32,
pub samples: &'a mut [i32],
}
impl PlaneBuffer<'_> {
#[inline]
fn get(&self, y: i32, x: i32) -> i32 {
let yc = y.clamp(0, self.rows as i32 - 1) as usize;
let xc = x.clamp(0, self.cols as i32 - 1) as usize;
self.samples[yc * (self.cols as usize) + xc]
}
#[inline]
fn index_or_none(&self, y: i32, x: i32) -> Option<usize> {
if y < 0 || x < 0 || y >= self.rows as i32 || x >= self.cols as i32 {
return None;
}
Some((y as usize) * (self.cols as usize) + (x as usize))
}
#[inline]
fn set(&mut self, y: i32, x: i32, v: i32) {
if y < 0 || x < 0 {
return;
}
let (y, x) = (y as u32, x as u32);
if y >= self.rows || x >= self.cols {
return;
}
let idx = (y as usize) * (self.cols as usize) + (x as usize);
self.samples[idx] = v;
}
}
pub struct LoopFilterFrameContext<'a> {
pub loop_filter_level: [u8; 4],
pub loop_filter_sharpness: u8,
pub loop_filter_delta_enabled: bool,
pub loop_filter_ref_deltas: [i8; TOTAL_REFS_PER_FRAME],
pub loop_filter_mode_deltas: [i8; 2],
pub delta_lf_multi: bool,
pub mi_rows: u32,
pub mi_cols: u32,
pub num_planes: u8,
pub bit_depth: u8,
pub subsampling_x: u8,
pub subsampling_y: u8,
pub frame_width: u32,
pub frame_height: u32,
pub is_intra: &'a dyn Fn(u32, u32) -> bool,
pub skip: &'a dyn Fn(u32, u32) -> bool,
pub ref_frame: &'a dyn Fn(u32, u32) -> u8,
pub mode: &'a dyn Fn(u32, u32) -> u8,
pub segment_id: &'a dyn Fn(u32, u32) -> u8,
pub delta_lf: &'a dyn Fn(u32, u32, usize) -> i8,
pub seg_feature_active: &'a dyn Fn(u32, usize) -> bool,
pub seg_feature_data: &'a dyn Fn(u32, usize) -> i16,
pub lf_tx_size: &'a dyn Fn(usize, u32, u32) -> usize,
pub mi_size: &'a dyn Fn(u32, u32) -> usize,
}
impl std::fmt::Debug for LoopFilterFrameContext<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LoopFilterFrameContext")
.field("loop_filter_level", &self.loop_filter_level)
.field("loop_filter_sharpness", &self.loop_filter_sharpness)
.field("loop_filter_delta_enabled", &self.loop_filter_delta_enabled)
.field("loop_filter_ref_deltas", &self.loop_filter_ref_deltas)
.field("loop_filter_mode_deltas", &self.loop_filter_mode_deltas)
.field("delta_lf_multi", &self.delta_lf_multi)
.field("mi_rows", &self.mi_rows)
.field("mi_cols", &self.mi_cols)
.field("num_planes", &self.num_planes)
.field("bit_depth", &self.bit_depth)
.field("subsampling_x", &self.subsampling_x)
.field("subsampling_y", &self.subsampling_y)
.field("frame_width", &self.frame_width)
.field("frame_height", &self.frame_height)
.finish_non_exhaustive()
}
}
pub fn loop_filter_frame(ctx: &LoopFilterFrameContext<'_>, planes: &mut [PlaneBuffer<'_>]) {
loop_filter_rows(ctx, planes, 0, ctx.mi_rows);
}
pub fn loop_filter_rows(
ctx: &LoopFilterFrameContext<'_>,
planes: &mut [PlaneBuffer<'_>],
mi_row_start: u32,
mi_row_end: u32,
) {
let num_planes = ctx.num_planes.min(planes.len() as u8);
let mi_row_end = mi_row_end.min(ctx.mi_rows);
for plane in 0..num_planes {
if plane > 0 && ctx.loop_filter_level[(1 + plane) as usize] == 0 {
continue;
}
let (sub_x, sub_y) = subsampling_for_plane(plane, ctx.subsampling_x, ctx.subsampling_y);
let row_step: u32 = if plane == 0 { 1 } else { 1 << sub_y };
let col_step: u32 = if plane == 0 { 1 } else { 1 << sub_x };
for pass in 0..2u8 {
let mut row = mi_row_start;
while row < mi_row_end {
let mut col = 0u32;
while col < ctx.mi_cols {
loop_filter_edge(ctx, planes, plane, pass, row, col);
col += col_step;
}
row += row_step;
}
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn loop_filter_rows_window(
ctx: &LoopFilterFrameContext<'_>,
plane: u8,
pass: u8,
window: &mut PlaneBuffer<'_>,
window_row0: u32,
mi_row_start: u32,
mi_row_end: u32,
) {
if plane > 0 && ctx.loop_filter_level[(1 + plane) as usize] == 0 {
return;
}
let (sub_x, sub_y) = subsampling_for_plane(plane, ctx.subsampling_x, ctx.subsampling_y);
let row_step: u32 = if plane == 0 { 1 } else { 1 << sub_y };
let col_step: u32 = if plane == 0 { 1 } else { 1 << sub_x };
let mi_row_end = mi_row_end.min(ctx.mi_rows);
let mut row = mi_row_start;
while row < mi_row_end {
let mut col = 0u32;
while col < ctx.mi_cols {
loop_filter_edge_in(ctx, window, plane, pass, row, col, window_row0);
col += col_step;
}
row += row_step;
}
}
pub(crate) fn row_windows_mut<'a>(
samples: &'a mut [i32],
cols: usize,
ranges: &[(usize, usize)],
) -> Vec<&'a mut [i32]> {
let mut out = Vec::with_capacity(ranges.len());
let mut rest = samples;
let mut consumed = 0usize;
for &(start, end) in ranges {
debug_assert!(start >= consumed && end >= start);
let (_, tail) = std::mem::take(&mut rest).split_at_mut((start - consumed) * cols);
let (win, tail) = tail.split_at_mut((end - start) * cols);
out.push(win);
rest = tail;
consumed = end;
}
out
}
pub(crate) fn run_jobs<'a>(threads: usize, jobs: Vec<Box<dyn FnOnce() + Send + 'a>>) {
if threads <= 1 || jobs.len() <= 1 {
for job in jobs {
job();
}
return;
}
let queue = std::sync::Mutex::new(jobs);
std::thread::scope(|scope| {
for _ in 0..threads.min(queue.lock().map(|q| q.len()).unwrap_or(1)) {
let queue = &queue;
scope.spawn(move || loop {
let job = match queue.lock() {
Ok(mut q) => q.pop(),
Err(_) => None,
};
match job {
Some(job) => job(),
None => break,
}
});
}
});
}
pub fn loop_filter_edge(
ctx: &LoopFilterFrameContext<'_>,
planes: &mut [PlaneBuffer<'_>],
plane: u8,
pass: u8,
row: u32,
col: u32,
) {
let Some(plane_buf) = planes.get_mut(plane as usize) else {
return;
};
loop_filter_edge_in(ctx, plane_buf, plane, pass, row, col, 0);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn loop_filter_edge_in(
ctx: &LoopFilterFrameContext<'_>,
plane_buf: &mut PlaneBuffer<'_>,
plane: u8,
pass: u8,
row: u32,
col: u32,
window_row0: u32,
) {
let (sub_x, sub_y) = subsampling_for_plane(plane, ctx.subsampling_x, ctx.subsampling_y);
let (dx, dy) = if pass == 0 {
(1i32, 0i32)
} else {
(0i32, 1i32)
};
let x = col * (MI_SIZE as u32);
let y = row * (MI_SIZE as u32);
let row = row | (sub_y as u32);
let col = col | (sub_x as u32);
if x >= ctx.frame_width {
return;
}
if y >= ctx.frame_height {
return;
}
if pass == 0 && x == 0 {
return;
}
if pass == 1 && y == 0 {
return;
}
let x_p = (x >> sub_x) as i32;
let y_p = (y >> sub_y) as i32;
let prev_row = (row as i32) - (dy << sub_y);
let prev_col = (col as i32) - (dx << sub_x);
if prev_row < 0 || prev_col < 0 {
return;
}
let prev_row = prev_row as u32;
let prev_col = prev_col as u32;
if row >= ctx.mi_rows || col >= ctx.mi_cols {
return;
}
let mi_size = (ctx.mi_size)(row, col);
let tx_sz = (ctx.lf_tx_size)(plane as usize, row >> sub_y, col >> sub_x);
let plane_size =
crate::cdf::get_plane_residual_size(mi_size, plane, sub_x, sub_y).unwrap_or(mi_size);
let skip = (ctx.skip)(row, col);
let is_intra = (ctx.is_intra)(row, col);
let prev_tx_sz = (ctx.lf_tx_size)(plane as usize, prev_row >> sub_y, prev_col >> sub_x);
let is_block_edge = if pass == 0 {
(x_p as u32) % block_width(plane_size).max(1) == 0
} else {
(y_p as u32) % block_height(plane_size).max(1) == 0
};
let is_tx_edge = if pass == 0 {
(x_p as u32) % crate::cdf::TX_WIDTH[tx_sz].max(1) as u32 == 0
} else {
(y_p as u32) % crate::cdf::TX_HEIGHT[tx_sz].max(1) as u32 == 0
};
let apply_filter = if !is_tx_edge {
false
} else {
is_block_edge || !skip || is_intra
};
let filter_size = filter_size(tx_sz, prev_tx_sz, pass, plane);
let mut strength = adaptive_filter_strength(ctx, row, col, plane, pass);
if strength.lvl == 0 {
strength = adaptive_filter_strength(ctx, prev_row, prev_col, plane, pass);
}
if !apply_filter || strength.lvl == 0 {
return;
}
let y_p = y_p - window_row0 as i32;
for i in 0..(MI_SIZE as i32) {
let sx = x_p + dy * i;
let sy = y_p + dx * i;
sample_filtering(
plane_buf,
sx,
sy,
plane,
strength.limit,
strength.blimit,
strength.thresh,
dx,
dy,
filter_size,
ctx.bit_depth,
);
}
}
#[must_use]
pub fn filter_size(tx_sz: usize, prev_tx_sz: usize, pass: u8, plane: u8) -> u32 {
let base_size = if pass == 0 {
(crate::cdf::TX_WIDTH[tx_sz].min(crate::cdf::TX_WIDTH[prev_tx_sz])) as u32
} else {
(crate::cdf::TX_HEIGHT[tx_sz].min(crate::cdf::TX_HEIGHT[prev_tx_sz])) as u32
};
let cap = if plane == 0 { 16 } else { 8 };
base_size.min(cap)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FilterStrength {
pub lvl: u8,
pub limit: u8,
pub blimit: u8,
pub thresh: u8,
}
#[must_use]
pub fn adaptive_filter_strength(
ctx: &LoopFilterFrameContext<'_>,
row: u32,
col: u32,
plane: u8,
pass: u8,
) -> FilterStrength {
let segment = (ctx.segment_id)(row, col);
let r = (ctx.ref_frame)(row, col);
let m = (ctx.mode)(row, col);
let mode_type: u8 = if m >= NEARESTMV && m != GLOBALMV && m != GLOBAL_GLOBALMV {
1
} else {
0
};
let delta_lf_idx = if ctx.delta_lf_multi {
if plane == 0 {
pass as usize
} else {
(plane + 1) as usize
}
} else {
0
};
let delta_lf = (ctx.delta_lf)(row, col, delta_lf_idx);
let lvl = adaptive_filter_strength_selection(
ctx,
segment as u32,
r,
mode_type,
delta_lf,
plane,
pass,
);
let shift = if ctx.loop_filter_sharpness > 4 {
2
} else if ctx.loop_filter_sharpness > 0 {
1
} else {
0
};
let lvl_i = lvl as i32;
let limit_i = if ctx.loop_filter_sharpness > 0 {
(lvl_i >> shift).clamp(1, 9 - ctx.loop_filter_sharpness as i32)
} else {
(lvl_i >> shift).max(1)
};
let limit = limit_i as u8;
let blimit = (2 * (lvl as u32 + 2) + limit as u32) as u8;
let thresh = lvl >> 4;
FilterStrength {
lvl,
limit,
blimit,
thresh,
}
}
#[must_use]
pub fn adaptive_filter_strength_selection(
ctx: &LoopFilterFrameContext<'_>,
segment: u32,
ref_frame: u8,
mode_type: u8,
delta_lf: i8,
plane: u8,
pass: u8,
) -> u8 {
let i = if plane == 0 {
pass as usize
} else {
(plane + 1) as usize
};
let base_filter_level = clip3_i32(
0,
MAX_LOOP_FILTER as i32,
(delta_lf as i32) + (ctx.loop_filter_level[i] as i32),
);
let mut lvl_seg = base_filter_level;
let feature = SEG_LVL_ALT_LF_Y_V + i;
if (ctx.seg_feature_active)(segment, feature) {
lvl_seg = clip3_i32(
0,
MAX_LOOP_FILTER as i32,
(ctx.seg_feature_data)(segment, feature) as i32 + lvl_seg,
);
}
if ctx.loop_filter_delta_enabled {
let n_shift = lvl_seg >> 5;
if ref_frame as usize == INTRA_FRAME {
lvl_seg += (ctx.loop_filter_ref_deltas[INTRA_FRAME] as i32) << n_shift;
} else {
let ref_idx = (ref_frame as usize).min(TOTAL_REFS_PER_FRAME - 1);
let mode_idx = (mode_type as usize).min(ctx.loop_filter_mode_deltas.len() - 1);
lvl_seg += (ctx.loop_filter_ref_deltas[ref_idx] as i32) << n_shift;
lvl_seg += (ctx.loop_filter_mode_deltas[mode_idx] as i32) << n_shift;
}
lvl_seg = clip3_i32(0, MAX_LOOP_FILTER as i32, lvl_seg);
}
lvl_seg.clamp(0, 255) as u8
}
#[allow(clippy::too_many_arguments)]
pub fn sample_filtering(
plane_buf: &mut PlaneBuffer<'_>,
x: i32,
y: i32,
plane: u8,
limit: u8,
blimit: u8,
thresh: u8,
dx: i32,
dy: i32,
filter_size: u32,
bit_depth: u8,
) {
let masks = filter_mask(
plane_buf,
x,
y,
plane,
limit,
blimit,
thresh,
dx,
dy,
filter_size,
bit_depth,
);
if !masks.filter_mask {
return;
}
if filter_size == 4 || !masks.flat_mask {
narrow_filter(plane_buf, x, y, dx, dy, masks.hev_mask, bit_depth);
} else if filter_size == 8 || !masks.flat_mask2 {
wide_filter(plane_buf, x, y, plane, dx, dy, 3, bit_depth);
} else {
wide_filter(plane_buf, x, y, plane, dx, dy, 4, bit_depth);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FilterMaskOutput {
pub hev_mask: bool,
pub filter_mask: bool,
pub flat_mask: bool,
pub flat_mask2: bool,
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn filter_mask(
plane_buf: &PlaneBuffer<'_>,
x: i32,
y: i32,
plane: u8,
limit: u8,
blimit: u8,
thresh: u8,
dx: i32,
dy: i32,
filter_size: u32,
bit_depth: u8,
) -> FilterMaskOutput {
let reach: i32 = if filter_size >= 16 {
7
} else if filter_size >= 8 {
4
} else {
2
};
let mut taps = [0i32; 14];
let base = plane_buf.index_or_none(y - reach * dy, x - reach * dx);
let end = plane_buf.index_or_none(y + (reach - 1) * dy, x + (reach - 1) * dx);
match (base, end) {
(Some(base), Some(_)) => {
let stride = (dx + dy * plane_buf.cols as i32) as usize;
let first = 7 - reach as usize;
for k in 0..(2 * reach) as usize {
taps[first + k] = plane_buf.samples[base + k * stride];
}
}
_ => {
for p in -reach..reach {
taps[(p + 7) as usize] = plane_buf.get(y + p * dy, x + p * dx);
}
}
}
let [p6, p5, p4, p3, p2, p1, p0, q0, q1, q2, q3, q4, q5, q6] = taps;
let bd_shift = bit_depth - 8;
let thresh_bd = (thresh as i32) << bd_shift;
let hev_mask = (p1 - p0).abs() > thresh_bd || (q1 - q0).abs() > thresh_bd;
let filter_len: u32 = if filter_size == 4 {
4
} else if plane != 0 {
6
} else if filter_size == 8 {
8
} else {
16
};
let limit_bd = (limit as i32) << bd_shift;
let blimit_bd = (blimit as i32) << bd_shift;
let mut mask = (p1 - p0).abs() > limit_bd;
mask |= (q1 - q0).abs() > limit_bd;
mask |= (p0 - q0).abs() * 2 + (p1 - q1).abs() / 2 > blimit_bd;
if filter_len >= 6 {
mask |= (p2 - p1).abs() > limit_bd;
mask |= (q2 - q1).abs() > limit_bd;
}
if filter_len >= 8 {
mask |= (p3 - p2).abs() > limit_bd;
mask |= (q3 - q2).abs() > limit_bd;
}
let filter_mask = !mask;
let threshold_bd = 1i32 << bd_shift;
let flat_mask = if filter_size >= 8 {
let mut m = (p1 - p0).abs() > threshold_bd;
m |= (q1 - q0).abs() > threshold_bd;
m |= (p2 - p0).abs() > threshold_bd;
m |= (q2 - q0).abs() > threshold_bd;
if filter_len >= 8 {
m |= (p3 - p0).abs() > threshold_bd;
m |= (q3 - q0).abs() > threshold_bd;
}
!m
} else {
false
};
let flat_mask2 = if filter_size >= 16 {
let mut m = (p6 - p0).abs() > threshold_bd;
m |= (q6 - q0).abs() > threshold_bd;
m |= (p5 - p0).abs() > threshold_bd;
m |= (q5 - q0).abs() > threshold_bd;
m |= (p4 - p0).abs() > threshold_bd;
m |= (q4 - q0).abs() > threshold_bd;
!m
} else {
false
};
FilterMaskOutput {
hev_mask,
filter_mask,
flat_mask,
flat_mask2,
}
}
pub fn narrow_filter(
plane_buf: &mut PlaneBuffer<'_>,
x: i32,
y: i32,
dx: i32,
dy: i32,
hev_mask: bool,
bit_depth: u8,
) {
let half = 0x80i32 << (bit_depth - 8);
let q0 = plane_buf.get(y, x);
let q1 = plane_buf.get(y + dy, x + dx);
let p0 = plane_buf.get(y - dy, x - dx);
let p1 = plane_buf.get(y - 2 * dy, x - 2 * dx);
let ps1 = p1 - half;
let ps0 = p0 - half;
let qs0 = q0 - half;
let qs1 = q1 - half;
let mut filter = if hev_mask {
filter4_clamp(ps1 - qs1, bit_depth)
} else {
0
};
filter = filter4_clamp(filter + 3 * (qs0 - ps0), bit_depth);
let filter1 = filter4_clamp(filter + 4, bit_depth) >> 3;
let filter2 = filter4_clamp(filter + 3, bit_depth) >> 3;
let oq0 = filter4_clamp(qs0 - filter1, bit_depth) + half;
let op0 = filter4_clamp(ps0 + filter2, bit_depth) + half;
plane_buf.set(y, x, oq0);
plane_buf.set(y - dy, x - dx, op0);
if !hev_mask {
let f = round2_i32(filter1, 1);
let oq1 = filter4_clamp(qs1 - f, bit_depth) + half;
let op1 = filter4_clamp(ps1 + f, bit_depth) + half;
plane_buf.set(y + dy, x + dx, oq1);
plane_buf.set(y - 2 * dy, x - 2 * dx, op1);
}
}
#[allow(clippy::too_many_arguments)]
pub fn wide_filter(
plane_buf: &mut PlaneBuffer<'_>,
x: i32,
y: i32,
plane: u8,
dx: i32,
dy: i32,
log2_size: u32,
_bit_depth: u8,
) {
let n: i32 = if log2_size == 4 {
6
} else if plane == 0 {
3
} else {
2
};
let n2: i32 = if log2_size == 3 && plane == 0 { 0 } else { 1 };
let mut taps = [0i32; 14];
let base = plane_buf.index_or_none(y - (n + 1) * dy, x - (n + 1) * dx);
let end = plane_buf.index_or_none(y + n * dy, x + n * dx);
let stride = (dx + dy * plane_buf.cols as i32) as usize;
match (base, end) {
(Some(base), Some(_)) => {
for (k, slot) in taps.iter_mut().take((2 * n + 2) as usize).enumerate() {
*slot = plane_buf.samples[base + k * stride];
}
}
_ => {
for (k, slot) in taps.iter_mut().take((2 * n + 2) as usize).enumerate() {
let p = k as i32 - (n + 1);
*slot = plane_buf.get(y + p * dy, x + p * dx);
}
}
}
let tap_at = |k: i32| taps[(clip3_i32(-(n + 1), n, k) + n + 1) as usize];
let mut window: i32 = 0;
for j in -n..=n {
window += tap_at(-n + j);
}
let mut f = [0i32; 12];
for (idx, i) in (-n..n).enumerate() {
if i > -n {
window += tap_at(i + n) - tap_at(i - 1 - n);
}
let mut t = window;
for j in -n2..=n2 {
t += tap_at(i + j);
}
f[idx] = round2_i32(t, log2_size);
}
if let Some(base) = base {
let first = base + stride;
for (idx, _) in (-n..n).enumerate() {
plane_buf.samples[first + idx * stride] = f[idx];
}
} else {
for (idx, i) in (-n..n).enumerate() {
plane_buf.set(y + i * dy, x + i * dx, f[idx]);
}
}
}
#[inline]
fn subsampling_for_plane(plane: u8, subsampling_x: u8, subsampling_y: u8) -> (u8, u8) {
if plane == 0 {
(0, 0)
} else {
(subsampling_x, subsampling_y)
}
}
#[inline]
fn block_width(plane_size: usize) -> u32 {
if plane_size >= crate::cdf::BLOCK_SIZES {
return 4;
}
(crate::cdf::num_4x4_blocks_wide(plane_size) * 4) as u32
}
#[inline]
fn block_height(plane_size: usize) -> u32 {
if plane_size >= crate::cdf::BLOCK_SIZES {
return 4;
}
(crate::cdf::num_4x4_blocks_high(plane_size) * 4) as u32
}
#[inline]
fn clip3_i32(a: i32, b: i32, x: i32) -> i32 {
if x < a {
a
} else if x > b {
b
} else {
x
}
}
#[inline]
fn round2_i32(x: i32, n: u32) -> i32 {
if n == 0 {
x
} else {
(x + (1 << (n - 1))) >> n
}
}
#[inline]
fn filter4_clamp(value: i32, bit_depth: u8) -> i32 {
let lo = -(1i32 << (bit_depth - 1));
let hi = (1i32 << (bit_depth - 1)) - 1;
clip3_i32(lo, hi, value)
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::too_many_arguments)]
fn make_ctx<'a>(
mi_rows: u32,
mi_cols: u32,
loop_filter_level: [u8; 4],
is_intra: &'a dyn Fn(u32, u32) -> bool,
skip: &'a dyn Fn(u32, u32) -> bool,
ref_frame: &'a dyn Fn(u32, u32) -> u8,
mode: &'a dyn Fn(u32, u32) -> u8,
segment_id: &'a dyn Fn(u32, u32) -> u8,
delta_lf: &'a dyn Fn(u32, u32, usize) -> i8,
seg_feature_active: &'a dyn Fn(u32, usize) -> bool,
seg_feature_data: &'a dyn Fn(u32, usize) -> i16,
lf_tx_size: &'a dyn Fn(usize, u32, u32) -> usize,
mi_size: &'a dyn Fn(u32, u32) -> usize,
) -> LoopFilterFrameContext<'a> {
LoopFilterFrameContext {
loop_filter_level,
loop_filter_sharpness: 0,
loop_filter_delta_enabled: false,
loop_filter_ref_deltas: [0; TOTAL_REFS_PER_FRAME],
loop_filter_mode_deltas: [0; 2],
delta_lf_multi: false,
mi_rows,
mi_cols,
num_planes: 3,
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
frame_width: mi_cols * MI_SIZE as u32,
frame_height: mi_rows * MI_SIZE as u32,
is_intra,
skip,
ref_frame,
mode,
segment_id,
delta_lf,
seg_feature_active,
seg_feature_data,
lf_tx_size,
mi_size,
}
}
#[test]
fn strength_returns_zero_when_loop_filter_level_zero() {
let ctx = make_ctx(
4,
4,
[0, 0, 0, 0],
&|_, _| true,
&|_, _| false,
&|_, _| INTRA_FRAME as u8,
&|_, _| 0u8,
&|_, _| 0u8,
&|_, _, _| 0i8,
&|_, _| false,
&|_, _| 0i16,
&|_, _, _| crate::cdf::TX_4X4,
&|_, _| crate::cdf::BLOCK_4X4,
);
let s = adaptive_filter_strength(&ctx, 0, 0, 0, 0);
assert_eq!(s.lvl, 0);
}
#[test]
fn strength_basic_table_lvl_32_sharp_0() {
let ctx = make_ctx(
4,
4,
[32, 32, 32, 32],
&|_, _| true,
&|_, _| false,
&|_, _| INTRA_FRAME as u8,
&|_, _| 0u8,
&|_, _| 0u8,
&|_, _, _| 0i8,
&|_, _| false,
&|_, _| 0i16,
&|_, _, _| crate::cdf::TX_4X4,
&|_, _| crate::cdf::BLOCK_4X4,
);
let s = adaptive_filter_strength(&ctx, 0, 0, 0, 0);
assert_eq!(s.lvl, 32);
assert_eq!(s.limit, 32);
assert_eq!(s.blimit, 100);
assert_eq!(s.thresh, 2);
}
#[test]
fn strength_intra_ref_delta_lifts_lvl() {
let mut ctx = make_ctx(
4,
4,
[10, 10, 10, 10],
&|_, _| true,
&|_, _| false,
&|_, _| INTRA_FRAME as u8,
&|_, _| 0u8,
&|_, _| 0u8,
&|_, _, _| 0i8,
&|_, _| false,
&|_, _| 0i16,
&|_, _, _| crate::cdf::TX_4X4,
&|_, _| crate::cdf::BLOCK_4X4,
);
ctx.loop_filter_delta_enabled = true;
ctx.loop_filter_ref_deltas[INTRA_FRAME] = 5;
let s = adaptive_filter_strength(&ctx, 0, 0, 0, 0);
assert_eq!(s.lvl, 15);
}
#[test]
fn strength_clipped_at_max_loop_filter() {
let mut ctx = make_ctx(
4,
4,
[60, 60, 60, 60],
&|_, _| true,
&|_, _| false,
&|_, _| INTRA_FRAME as u8,
&|_, _| 0u8,
&|_, _| 0u8,
&|_, _, _| 0i8,
&|_, _| false,
&|_, _| 0i16,
&|_, _, _| crate::cdf::TX_4X4,
&|_, _| crate::cdf::BLOCK_4X4,
);
ctx.loop_filter_delta_enabled = true;
ctx.loop_filter_ref_deltas[INTRA_FRAME] = 10; let s = adaptive_filter_strength(&ctx, 0, 0, 0, 0);
assert_eq!(s.lvl, MAX_LOOP_FILTER as u8);
}
#[test]
fn filter_size_luma_caps_at_16_chroma_at_8() {
assert_eq!(
filter_size(crate::cdf::TX_64X64, crate::cdf::TX_64X64, 0, 0),
16
);
assert_eq!(
filter_size(crate::cdf::TX_64X64, crate::cdf::TX_64X64, 0, 1),
8
);
assert_eq!(filter_size(crate::cdf::TX_4X4, crate::cdf::TX_4X4, 0, 0), 4);
}
#[test]
fn narrow_filter_idempotent_on_uniform_input() {
let mut samples = vec![128i32; 8 * 8];
let mut buf = PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut samples,
};
let before = buf.samples.to_vec();
narrow_filter(&mut buf, 4, 4, 1, 0, false, 8);
assert_eq!(buf.samples, &before[..]);
}
#[test]
fn narrow_filter_softens_sharp_step_edge() {
let mut samples = vec![0i32; 8 * 8];
for y in 0..8 {
for x in 0..8 {
samples[y * 8 + x] = if x < 4 { 100 } else { 156 };
}
}
let mut buf = PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut samples,
};
narrow_filter(&mut buf, 4, 4, 1, 0, true, 8);
let p0 = buf.samples[4 * 8 + 3];
let q0 = buf.samples[4 * 8 + 4];
assert!(p0 > 100, "p0={p0} should rise above 100");
assert!(q0 < 156, "q0={q0} should fall below 156");
assert_eq!(q0 - 128, 128 - p0);
}
#[test]
fn wide_filter_log2_3_luma_preserves_uniform_input() {
let mut samples = vec![200i32; 16 * 16];
let mut buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut samples,
};
let before = buf.samples.to_vec();
wide_filter(&mut buf, 8, 8, 0, 1, 0, 3, 8);
assert_eq!(buf.samples, &before[..]);
}
#[test]
fn wide_filter_log2_4_luma_preserves_uniform_input() {
let mut samples = vec![64i32; 32 * 32];
let mut buf = PlaneBuffer {
rows: 32,
cols: 32,
samples: &mut samples,
};
let before = buf.samples.to_vec();
wide_filter(&mut buf, 16, 16, 0, 1, 0, 4, 8);
assert_eq!(buf.samples, &before[..]);
}
#[test]
fn filter_mask_flat_region_allows_filter() {
let mut samples = vec![100i32; 16 * 16];
let buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut samples,
};
let m = filter_mask(&buf, 8, 8, 0, 16, 32, 8, 1, 0, 8, 8);
assert!(m.filter_mask, "flat region must permit filtering");
assert!(m.flat_mask, "flat region must be flat");
assert!(!m.hev_mask, "uniform region cannot be hev");
}
#[test]
fn filter_mask_steep_edge_blocks_filter() {
let mut samples = vec![0i32; 16 * 16];
for y in 0..16 {
for x in 0..16 {
samples[y * 16 + x] = if x < 8 { 50 } else { 200 };
}
}
let buf = PlaneBuffer {
rows: 16,
cols: 16,
samples: &mut samples,
};
let m = filter_mask(&buf, 8, 8, 0, 4, 12, 6, 1, 0, 8, 8);
assert!(!m.filter_mask, "steep step must veto filter");
}
#[test]
fn driver_iterates_correct_edge_count_for_2x2_mi_grid() {
use std::cell::Cell;
let edges: Cell<u32> = Cell::new(0);
let ctr = |_r: u32, _c: u32| {
edges.set(edges.get() + 1);
false
};
let ctx = LoopFilterFrameContext {
loop_filter_level: [10, 10, 0, 0],
loop_filter_sharpness: 0,
loop_filter_delta_enabled: false,
loop_filter_ref_deltas: [0; TOTAL_REFS_PER_FRAME],
loop_filter_mode_deltas: [0; 2],
delta_lf_multi: false,
mi_rows: 2,
mi_cols: 2,
num_planes: 1,
bit_depth: 8,
subsampling_x: 1,
subsampling_y: 1,
frame_width: 8,
frame_height: 8,
is_intra: &ctr,
skip: &|_, _| false,
ref_frame: &|_, _| INTRA_FRAME as u8,
mode: &|_, _| 0u8,
segment_id: &|_, _| 0u8,
delta_lf: &|_, _, _| 0i8,
seg_feature_active: &|_, _| false,
seg_feature_data: &|_, _| 0i16,
lf_tx_size: &|_, _, _| crate::cdf::TX_4X4,
mi_size: &|_, _| crate::cdf::BLOCK_8X8,
};
let mut samples = vec![100i32; 8 * 8];
let mut planes = vec![PlaneBuffer {
rows: 8,
cols: 8,
samples: &mut samples,
}];
loop_filter_frame(&ctx, &mut planes);
assert_eq!(
edges.get(),
4,
"expected 4 on-screen edges across both passes"
);
}
}