Files
effectcraft/crates/vp9enc/src/lf.rs
T
vangel76andClaude Sonnet 5.5 153e907afa Merge upstream/main into vp9enc-loop-filter-lanes
Resolve crates/vp9enc/src/lf.rs: main moved sb_plane into FrameFilter::sb_plane
(mi grid and limits as struct fields, no allow(too_many_arguments)). The
branch's 8-lane group filtering (apply mask per lane, filter_group) is
re-applied inside that method; the now-unneeded too_many_arguments allow on
filter_group is dropped (7 arguments).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 15:41:16 +01:00

300 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The in-loop deblocking filter (spec 8.8), as the decoder applies it to a frame coded with 4×4
//! transforms, 8×8 / 16×16 blocks, no segmentation and no reference/mode deltas: superblocks in
//! raster order, for each plane the vertical edges (pass 0) then the horizontal edges (pass 1).
//! Transform edges inside a skipped inter block are left alone; every 4×4 edge uses the 4-tap
//! filter except edges on a 32-sample boundary, which get the 8-tap (flat) filter.
use crate::{BLOCK_16X16, Mi, Plane};
#[derive(Clone, Copy)]
struct Limits {
limit: i32,
blimit: i32,
thresh: i32,
}
/// Mode-info grid and thresholds shared by every superblock of this frame.
struct FrameFilter<'a> {
mi: &'a [Mi],
mi_rows: usize,
mi_cols: usize,
limits: Limits,
}
/// Filter the reconstructed planes (Y, U, V, or a prefix of them) in place at `level` (0 = off;
/// sharpness 0).
pub fn filter_frame(planes: &mut [Plane], mi: &[Mi], mi_rows: usize, mi_cols: usize, level: u8) {
if level == 0 {
return;
}
let l = level as i32;
let limit = l.max(1);
let lim = Limits { limit, blimit: 2 * (l + 2) + limit, thresh: l >> 4 };
let filter = FrameFilter { mi, mi_rows, mi_cols, limits: lim };
for sbr in 0..mi_rows.div_ceil(8) {
for sbc in 0..mi_cols.div_ceil(8) {
for (plane, p) in planes.iter_mut().enumerate() {
for pass in 0..2 {
filter.sb_plane(p, plane, pass, sbr * 8, sbc * 8);
}
}
}
}
}
impl FrameFilter<'_> {
fn sb_plane(&self, p: &mut Plane, plane: usize, pass: usize, row: usize, col: usize) {
let Self { mi, mi_rows, mi_cols, limits: lim } = *self;
let (sx, sy) = if plane > 0 { (1, 1) } else { (0, 0) };
let (sub, edge_len) = if pass == 0 { (sx, 64 >> sy) } else { (sy, 64 >> sx) };
let stride = p.w;
for edge in 0..(16usize >> sub) {
let mut i = 0;
while i < edge_len {
let (x, y) = if pass == 0 { (col * 8 + edge * (4 << sx), row * 8 + (i << sy)) } else { (col * 8 + (i << sx), row * 8 + edge * (4 << sy)) };
if x >= 8 * mi_cols || y >= 8 * mi_rows || (pass == 0 && x == 0) || (pass == 1 && y == 0) {
i += 8;
continue;
}
let lc = ((x >> 3) >> sx) << sx;
let lr = ((y >> 3) >> sy) << sy;
let m = mi[lr * mi_cols + lc];
let size = if sub == 0 { m.size } else { m.size.max(BLOCK_16X16) };
let n8 = if size >= BLOCK_16X16 { 2 } else { 1 };
let is_block_edge = if pass == 0 { x & (8 * n8 - 1) == 0 } else { y & (8 * n8 - 1) == 0 };
let tx_ok = !m.inter || !m.skip;
if !is_block_edge && !tx_ok {
i += 8;
continue;
}
let wide = edge % 8 == 0;
let n = if pass == 0 { (8 * mi_rows - y).div_ceil(1 << sy) } else { (8 * mi_cols - x).div_ceil(1 << sx) }.min(8).min(edge_len - i);
// A transform edge is not filtered in the last odd chroma column.
let odd_last = pass == 1 && sx == 1 && mi_cols & 1 == 1 && edge & 1 == 1;
let (px, py) = (x >> sx, y >> sy);
let mut apply = [false; LANES];
for (l, a) in apply.iter_mut().enumerate().take(n) {
let is_tx_edge = tx_ok && !(odd_last && x + (l << sx) + 8 >= mi_cols * 8);
*a = is_block_edge || is_tx_edge;
}
if apply.iter().any(|&a| a) {
let pos = py * stride + px;
if pass == 0 {
filter_group::<false>(&mut p.rec, pos, stride, 1, apply, wide, lim);
} else {
filter_group::<true>(&mut p.rec, pos, 1, stride, apply, wide, lim);
}
}
i += 8;
}
}
}
}
const LANES: usize = 8;
/// [`filter_lane`] on the 8 lanes of a group at once (lane `l` has its q0 at `pos + l * along`;
/// samples across the edge are `step` apart; `ROWS`: `along` is 1), for lanes whose `apply` is
/// set. Every lane is computed with both filters and the result selected per lane (0 / -1
/// masks), so the arithmetic vectorizes; lanes left alone are written back unchanged.
#[inline(always)]
fn filter_group<const ROWS: bool>(d: &mut [u8], pos: usize, along: usize, step: usize, apply: [bool; LANES], wide: bool, lim: Limits) {
type T = i16;
let mut v = [[0 as T; LANES]; 8];
let start = pos - 4 * step;
if ROWS {
for (k, vk) in v.iter_mut().enumerate() {
let Some(row) = d.get(start + k * step..).and_then(|r| r.first_chunk::<LANES>()) else { return };
*vk = row.map(|s| s as T);
}
} else {
for l in 0..LANES {
let Some(line) = d.get(start + l * along..).and_then(|r| r.first_chunk::<8>()) else { return };
for k in 0..8 {
v[k][l] = line[k] as T;
}
}
}
let (limit, blimit, thresh) = (lim.limit as T, lim.blimit as T, lim.thresh as T);
let le = |a: T, b: T| -((a <= b) as T);
let sel = |m: T, a: T, b: T| (a & m) | (b & !m);
let (p3, p2, p1, p0, q0, q1, q2, q3) = (&v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7]);
let apply: [T; LANES] = apply.map(|a| -(a as T));
let can_flat = -(wide as T);
let mut mask = [0 as T; LANES];
let mut hev = [0 as T; LANES];
let mut flat = [0 as T; LANES];
for l in 0..LANES {
let d10 = (p1[l] - p0[l]).abs();
let e10 = (q1[l] - q0[l]).abs();
mask[l] = apply[l]
& le((p3[l] - p2[l]).abs(), limit)
& le((p2[l] - p1[l]).abs(), limit)
& le(d10, limit)
& le(e10, limit)
& le((q2[l] - q1[l]).abs(), limit)
& le((q3[l] - q2[l]).abs(), limit)
& le((p0[l] - q0[l]).abs() * 2 + (p1[l] - q1[l]).abs() / 2, blimit);
hev[l] = !(le(d10, thresh) & le(e10, thresh));
flat[l] = can_flat
& mask[l]
& le(d10, 1)
& le(e10, 1)
& le((p2[l] - p0[l]).abs(), 1)
& le((q2[l] - q0[l]).abs(), 1)
& le((p3[l] - p0[l]).abs(), 1)
& le((q3[l] - q0[l]).abs(), 1);
}
if mask.iter().fold(0, |a, &m| a | m) == 0 {
return;
}
let mut out = v;
let c = |x: T| x.clamp(-128, 127);
for l in 0..LANES {
let (ps1, ps0, qs0, qs1) = (p1[l] - 128, p0[l] - 128, q0[l] - 128, q1[l] - 128);
let filter = c((c(ps1 - qs1) & hev[l]) + 3 * (qs0 - ps0));
let f1 = c(filter + 4) >> 3;
let f2 = c(filter + 3) >> 3;
let f = (f1 + 1) >> 1;
let narrow = mask[l] & !flat[l];
let outer = narrow & !hev[l];
out[4][l] = sel(narrow, c(qs0 - f1) + 128, out[4][l]);
out[3][l] = sel(narrow, c(ps0 + f2) + 128, out[3][l]);
out[5][l] = sel(outer, c(qs1 - f) + 128, out[5][l]);
out[2][l] = sel(outer, c(ps1 + f) + 128, out[2][l]);
}
if flat.iter().fold(0, |a, &f| a | f) != 0 {
// 8-tap: positions p2..q2 (indices 1..7), each the rounded mean of a 7-wide window
// (indices clamped to 0..8) plus itself, as a sliding sum.
let s = |k: isize| &v[k.clamp(0, 7) as usize];
let mut sum = [0 as T; LANES];
for j in -2isize..=4 {
let a = s(j);
for l in 0..LANES {
sum[l] += a[l];
}
}
for k in 1usize..7 {
let ki = k as isize;
let (cur, add, sub) = (s(ki), s(ki + 4), s(ki - 3));
for l in 0..LANES {
let w8 = (sum[l] + cur[l] + 4) >> 3;
out[k][l] = sel(flat[l], w8, out[k][l]);
sum[l] += add[l] - sub[l];
}
}
}
if ROWS {
for (k, ok) in out.iter().enumerate().take(7).skip(1) {
if let Some(row) = d.get_mut(start + k * step..).and_then(|r| r.first_chunk_mut::<LANES>()) {
for l in 0..LANES {
row[l] = ok[l] as u8;
}
}
}
} else {
for l in 0..LANES {
if let Some(line) = d.get_mut(start + l * along..).and_then(|r| r.first_chunk_mut::<8>()) {
for k in 1..7 {
line[k] = out[k][l] as u8;
}
}
}
}
}
/// Filter mask, narrow (4-tap) and 8-tap flat filters of spec 8.8.5 across one edge position;
/// `pos` is q0, samples across the edge are `step` apart.
#[cfg(test)]
fn filter_lane(d: &mut [u8], pos: usize, step: usize, wide: bool, lim: Limits) {
let at = |k: isize| d[(pos as isize + k * step as isize) as usize] as i32;
let (p3, p2, p1, p0) = (at(-4), at(-3), at(-2), at(-1));
let (q0, q1, q2, q3) = (at(0), at(1), at(2), at(3));
let Limits { limit, blimit, thresh } = lim;
let mask = (p3 - p2).abs() <= limit
&& (p2 - p1).abs() <= limit
&& (p1 - p0).abs() <= limit
&& (q1 - q0).abs() <= limit
&& (q2 - q1).abs() <= limit
&& (q3 - q2).abs() <= limit
&& (p0 - q0).abs() * 2 + (p1 - q1).abs() / 2 <= blimit;
if !mask {
return;
}
let flat =
wide && (p1 - p0).abs() <= 1 && (q1 - q0).abs() <= 1 && (p2 - p0).abs() <= 1 && (q2 - q0).abs() <= 1 && (p3 - p0).abs() <= 1 && (q3 - q0).abs() <= 1;
let mut set = |k: isize, v: i32| d[(pos as isize + k * step as isize) as usize] = v as u8;
if flat {
// 8-tap: positions p2..q2, each the rounded mean of a 7-wide clamped window plus itself.
let s = [p3, p2, p1, p0, q0, q1, q2, q3];
let px = |k: isize| s[(k.clamp(-4, 3) + 4) as usize];
for k in -3isize..3 {
let mut sum = px(k);
for j in -3..=3 {
sum += px(k + j);
}
set(k, (sum + 4) >> 3);
}
return;
}
let c = |v: i32| v.clamp(-128, 127);
let hev = (p1 - p0).abs() > thresh || (q1 - q0).abs() > thresh;
let (ps1, ps0, qs0, qs1) = (p1 - 128, p0 - 128, q0 - 128, q1 - 128);
let filter = c(if hev { c(ps1 - qs1) } else { 0 } + 3 * (qs0 - ps0));
let f1 = c(filter + 4) >> 3;
let f2 = c(filter + 3) >> 3;
set(0, c(qs0 - f1) + 128);
set(-1, c(ps0 + f2) + 128);
if !hev {
let f = (f1 + 1) >> 1;
set(1, c(qs1 - f) + 128);
set(-2, c(ps1 + f) + 128);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn group_matches_per_lane_filter() {
let mut seed = 0x1234_5678u32;
let mut rnd = || {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
seed
};
for case in 0..20000 {
let stride = 24;
let base: Vec<u8> = {
let flatish = case % 3 == 0;
let c = (rnd() % 256) as i32;
(0..stride * 24)
.map(|_| if flatish { (c + (rnd() % 3) as i32 - 1).clamp(0, 255) as u8 } else { (c + (rnd() % 40) as i32 - 20).clamp(0, 255) as u8 })
.collect()
};
let l = (rnd() % 64) as i32;
let lim = Limits { limit: l.max(1), blimit: 2 * (l + 2) + l.max(1), thresh: l >> 4 };
let wide = rnd() & 1 == 1;
let apply: [bool; LANES] = std::array::from_fn(|_| rnd() % 4 != 0);
for pass in 0..2 {
let (mut a, mut b) = (base.clone(), base.clone());
let pos = 8 * stride + 8;
for (l, &ap) in apply.iter().enumerate() {
if ap {
let (p, st) = if pass == 0 { (pos + l * stride, 1) } else { (pos + l, stride) };
filter_lane(&mut a, p, st, wide, lim);
}
}
if pass == 0 {
filter_group::<false>(&mut b, pos, stride, 1, apply, wide, lim);
} else {
filter_group::<true>(&mut b, pos, 1, stride, apply, wide, lim);
}
assert_eq!(a, b, "case {case} pass {pass}");
}
}
}
}