#![cfg_attr(not(asm_loopfilter), forbid(unsafe_code))]
use crate::include::common::bitdepth::AsPrimitive;
use crate::include::common::bitdepth::BitDepth;
use crate::include::common::bitdepth::DynPixel;
use crate::include::common::intops::iclip;
use crate::include::dav1d::picture::PicOffset;
use crate::src::align::Align16;
use crate::src::cpu::CpuFlags;
use crate::src::ffi_safe::FFISafe;
use crate::src::internal::Rav1dFrameData;
use crate::src::lf_mask::Av1FilterLUT;
use crate::src::strided::Strided as _;
use crate::src::with_offset::WithOffset;
use crate::src::wrap_fn_ptr::wrap_fn_ptr;
use std::sync::atomic::AtomicU8;
use std::sync::atomic::Ordering::Relaxed;
#[allow(non_camel_case_types)]
type ptrdiff_t = isize;
use std::cmp;
use std::ffi::c_int;
use strum::FromRepr;
#[cfg(all(
asm_loopfilter,
not(any(target_arch = "riscv64", target_arch = "riscv32"))
))]
use crate::include::common::bitdepth::bd_fn;
#[cfg(not(asm_loopfilter))]
use crate::src::enum_map::DefaultValue;
wrap_fn_ptr!(pub unsafe extern "C" fn loopfilter_sb(
dst_ptr: *mut DynPixel,
stride: ptrdiff_t,
mask: &[u32; 3],
lvl_ptr: *const [u8; 4],
b4_stride: ptrdiff_t,
lut: &Align16<Av1FilterLUT>,
w: c_int,
bitdepth_max: c_int,
_dst: *const FFISafe<PicOffset>,
_lvl: *const FFISafe<WithOffset<&[AtomicU8]>>,
) -> ());
#[cfg(not(asm_loopfilter))]
fn loopfilter_sb_scalar<BD: BitDepth>(
dst: PicOffset,
mask: &[u32; 3],
lvl: WithOffset<&[AtomicU8]>,
b4_stride: usize,
lut: &Align16<Av1FilterLUT>,
wh: c_int,
bd: BD,
is_y: bool,
is_v: bool,
) {
match (is_y, is_v) {
(true, false) => loop_filter_sb128_rust::<BD, { HV::H as usize }, { YUV::Y as usize }>(
dst, mask, lvl, b4_stride, lut, wh, bd,
),
(true, true) => loop_filter_sb128_rust::<BD, { HV::V as usize }, { YUV::Y as usize }>(
dst, mask, lvl, b4_stride, lut, wh, bd,
),
(false, false) => loop_filter_sb128_rust::<BD, { HV::H as usize }, { YUV::UV as usize }>(
dst, mask, lvl, b4_stride, lut, wh, bd,
),
(false, true) => loop_filter_sb128_rust::<BD, { HV::V as usize }, { YUV::UV as usize }>(
dst, mask, lvl, b4_stride, lut, wh, bd,
),
}
}
#[cfg(not(asm_loopfilter))]
fn loopfilter_sb_direct<BD: BitDepth>(
f: &Rav1dFrameData,
dst: PicOffset,
mask: &[u32; 3],
lvl: WithOffset<&[AtomicU8]>,
w: usize,
is_y: bool,
is_v: bool,
) {
#[cfg(any(debug_assertions, feature = "__probe_sites"))]
if is_v && (mask[0] | mask[1] | mask[2]) != 0 {
use crate::include::dav1d::headers::Rav1dPixelLayout;
let pxstride = dst.pixel_stride::<BD>();
if pxstride > 0 {
let pxstride = pxstride as usize;
let base = dst.data.with_offset::<BD>().offset;
let row = (dst.offset - base) / pxstride;
let ss_ver = (!is_y && f.cur.p.layout == Rav1dPixelLayout::I420) as u8;
let sb_h = ((f.sb_step as usize) * 4) >> ss_ver;
let reach = lf_run_reach(is_y, mask);
assert!(
row % sb_h + reach <= sb_h,
"V-run window leaves the superblock row: row {row} (+{reach}) \
in a {sb_h}-row superblock row, is_y={is_y}, mask={mask:08x?}"
);
}
}
#[cfg(any(debug_assertions, feature = "__probe_sites"))]
if !is_v && (mask[0] | mask[1] | mask[2]) != 0 {
let pxstride = dst.pixel_stride::<BD>();
if pxstride > 0 {
let pxstride = pxstride as usize;
let base = dst.data.with_offset::<BD>().offset;
let col = (dst.offset - base) % pxstride;
let reach = lf_run_reach(is_y, mask);
assert!(
col + reach <= pxstride,
"H-run window leaves the picture row: column {col} (+{reach}) \
in a {pxstride}-pixel row, is_y={is_y}, mask={mask:08x?}"
);
}
}
let stride = dst.stride();
let b4_stride = f.b4_stride;
let lut = &f.lf.lim_lut;
let wh = w as c_int;
let bd_max = f.bitdepth_max;
#[cfg(feature = "__simd_test")]
let saved_buf = {
let (guard, _) = dst.full_guard::<BD>();
guard.to_vec()
};
let simd_handled = {
#[cfg(target_arch = "x86_64")]
{
crate::src::safe_simd::loopfilter::loopfilter_sb_dispatch::<BD>(
dst, stride, mask, lvl, b4_stride, lut, wh, bd_max, is_y, is_v,
)
}
#[cfg(target_arch = "aarch64")]
{
crate::src::safe_simd::loopfilter_arm::loopfilter_sb_dispatch::<BD>(
dst, stride, mask, lvl, b4_stride, lut, wh, bd_max, is_y, is_v,
)
}
#[cfg(target_arch = "wasm32")]
{
crate::src::safe_simd::loopfilter::loopfilter_sb_dispatch::<BD>(
dst, stride, mask, lvl, b4_stride, lut, wh, bd_max, is_y, is_v,
)
}
#[cfg(not(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "wasm32"
)))]
{
let _ = (stride, &mask, &lvl, b4_stride, lut, wh, bd_max, is_y, is_v);
false
}
};
if simd_handled {
#[cfg(feature = "__simd_test")]
{
let (guard, _) = dst.full_guard::<BD>();
let simd_buf = guard.to_vec();
drop(guard);
{
let (mut guard, _) = dst.full_guard_mut::<BD>();
guard.copy_from_slice(&saved_buf);
}
let bd = BD::from_c(bd_max);
loopfilter_sb_scalar::<BD>(dst, mask, lvl, b4_stride as usize, lut, wh, bd, is_y, is_v);
let (guard, _) = dst.full_guard::<BD>();
let scalar_buf = guard.to_vec();
drop(guard);
let pxstride = dst.pixel_stride::<BD>().unsigned_abs();
let mut diffs = 0u32;
let mut first_diff = None;
for (i, (&sv, &rv)) in simd_buf.iter().zip(scalar_buf.iter()).enumerate() {
let sv = sv.as_::<i32>();
let rv = rv.as_::<i32>();
if sv != rv {
diffs += 1;
if first_diff.is_none() {
let y = i / pxstride;
let x = i % pxstride;
first_diff = Some((i, x, y, sv, rv));
}
}
}
if let Some((idx, x, y, sv, rv)) = first_diff {
let msg = format!(
"LF_MISMATCH diffs={} first=({},{}) idx={} simd={} scalar={} is_y={} is_v={} w={}",
diffs, x, y, idx, sv, rv, is_y, is_v, w
);
if cfg!(feature = "__simd_test_log") {
eprintln!("{msg}");
} else {
panic!("{msg}");
}
}
{
let (mut guard, _) = dst.full_guard_mut::<BD>();
guard.copy_from_slice(&simd_buf);
}
}
return;
}
#[allow(unreachable_code)]
{
let b4_stride = b4_stride as usize;
let bd = BD::from_c(bd_max);
loopfilter_sb_scalar::<BD>(dst, mask, lvl, b4_stride, lut, wh, bd, is_y, is_v);
}
}
impl loopfilter_sb::Fn {
#[allow(dead_code)]
pub fn call<BD: BitDepth>(
&self,
f: &Rav1dFrameData,
dst: PicOffset,
mask: &[u32; 3],
lvl: WithOffset<&[AtomicU8]>,
w: usize,
is_y: bool,
is_v: bool,
) {
cfg_if::cfg_if! {
if #[cfg(asm_loopfilter)] {
let _ = (is_y, is_v);
let dst_ptr = dst.as_mut_ptr::<BD>().cast();
let stride = dst.stride();
assert!(lvl.offset <= lvl.data.len());
let lvl_ptr = unsafe { (lvl.data.as_ptr() as *const u8).add(lvl.offset) };
let lvl_ptr = lvl_ptr.cast::<[u8; 4]>();
let b4_stride = f.b4_stride;
let lut = &f.lf.lim_lut;
let w = w as c_int;
let bd = f.bitdepth_max;
let dst = FFISafe::new(&dst);
let lvl = FFISafe::new(&lvl);
unsafe {
self.get()(
dst_ptr, stride, mask, lvl_ptr, b4_stride, lut, w, bd, dst, lvl,
)
}
} else {
loopfilter_sb_direct::<BD>(f, dst, mask, lvl, w, is_y, is_v)
}
}
}
#[cfg(asm_loopfilter)]
const fn default<BD: BitDepth, const HV: usize, const YUV: usize>() -> Self {
Self::new(loop_filter_sb128_c_erased::<BD, { HV }, { YUV }>)
}
}
pub struct LoopFilterHVDSPContext {
pub h: loopfilter_sb::Fn,
pub v: loopfilter_sb::Fn,
}
pub struct LoopFilterYUVDSPContext {
pub y: LoopFilterHVDSPContext,
pub uv: LoopFilterHVDSPContext,
}
pub struct Rav1dLoopFilterDSPContext {
pub loop_filter_sb: LoopFilterYUVDSPContext,
}
trait LfTaps<BD: BitDepth> {
fn get(&self, idx: isize, k: isize) -> i32;
fn set(&mut self, idx: isize, k: isize, px: BD::Pixel);
}
struct DirectTaps<'a> {
dst: PicOffset<'a>,
stridea: ptrdiff_t,
strideb: ptrdiff_t,
}
impl<BD: BitDepth> LfTaps<BD> for DirectTaps<'_> {
#[inline(always)]
fn get(&self, idx: isize, k: isize) -> i32 {
(*(self.dst + (self.stridea * idx + self.strideb * k)).index_mut::<BD>()).as_::<i32>()
}
#[inline(always)]
fn set(&mut self, idx: isize, k: isize, px: BD::Pixel) {
*(self.dst + (self.stridea * idx + self.strideb * k)).index_mut::<BD>() = px;
}
}
struct CompactTaps<'a, BD: BitDepth> {
buf: &'a mut [BD::Pixel; LF_BLOCK_LEN],
base: usize,
stridea: isize,
strideb: isize,
len: usize,
}
impl<BD: BitDepth> CompactTaps<'_, BD> {
#[inline(always)]
fn at(&self, idx: isize, k: isize) -> usize {
let raw = self
.base
.wrapping_add_signed(self.stridea * idx + self.strideb * k);
debug_assert!(raw < self.len, "tap ({idx},{k}) outside the opened block");
raw & (LF_BLOCK_LEN - 1)
}
}
impl<BD: BitDepth> LfTaps<BD> for CompactTaps<'_, BD> {
#[inline(always)]
fn get(&self, idx: isize, k: isize) -> i32 {
self.buf[self.at(idx, k)].as_::<i32>()
}
#[inline(always)]
fn set(&mut self, idx: isize, k: isize, px: BD::Pixel) {
let i = self.at(idx, k);
self.buf[i] = px;
}
}
const LF_TAP_REACH: isize = 7;
const LF_BLOCK_MAX: usize = 4 * 2 * LF_TAP_REACH as usize;
pub(crate) const LF_BATCH_MAX: usize = 4;
pub(crate) const LF_BLOCK_LEN: usize = (LF_BLOCK_MAX * LF_BATCH_MAX).next_power_of_two();
pub(crate) const LF_BW: usize = 16;
const _: () = assert!(LF_BW * LF_BW == LF_BLOCK_LEN);
struct LfScratch<BD: BitDepth> {
buf: [BD::Pixel; LF_BLOCK_LEN],
pristine: [BD::Pixel; LF_BLOCK_LEN],
}
impl<BD: BitDepth> LfScratch<BD> {
fn new() -> Self {
Self {
buf: [BD::Pixel::from(0u8); LF_BLOCK_LEN],
pristine: [BD::Pixel::from(0u8); LF_BLOCK_LEN],
}
}
}
#[cfg(feature = "__probe_lf_hull")]
fn lf_hull_reads() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| matches!(std::env::var("RAV1D_LF_HULL").as_deref(), Ok("1")))
}
#[cfg(not(feature = "__probe_lf_hull"))]
#[inline(always)]
fn lf_hull_reads() -> bool {
false
}
#[cfg(feature = "__probe_lf_hull")]
fn lf_force_per_row() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| matches!(std::env::var("RAV1D_LF_PERROW").as_deref(), Ok("1")))
}
#[cfg(not(feature = "__probe_lf_hull"))]
#[inline(always)]
fn lf_force_per_row() -> bool {
false
}
#[cfg(feature = "__probe_lf_hull")]
fn lf_double_reads() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| matches!(std::env::var("RAV1D_LF_DOUBLE").as_deref(), Ok("1")))
}
#[cfg(not(feature = "__probe_lf_hull"))]
#[inline(always)]
fn lf_double_reads() -> bool {
false
}
#[cfg(feature = "__pad_text")]
pub(crate) mod text_pad {
#[inline(never)]
pub(crate) extern "C" fn unit<const K: usize>(x: &mut [u64; 32]) -> u64 {
let mut acc = K as u64;
for i in 0..32 {
acc = acc
.wrapping_mul(0x9E37_79B9_7F4A_7C15)
.wrapping_add(x[i] ^ (i as u64));
x[i] = acc;
acc ^= acc >> 29;
}
acc
}
#[cfg_attr(feature = "__pad_small", allow(unused_macros))]
macro_rules! rung {
($name:ident, $base:expr) => {
#[used]
static $name: [extern "C" fn(&mut [u64; 32]) -> u64; 8] = [
unit::<{ $base }>,
unit::<{ $base + 1 }>,
unit::<{ $base + 2 }>,
unit::<{ $base + 3 }>,
unit::<{ $base + 4 }>,
unit::<{ $base + 5 }>,
unit::<{ $base + 6 }>,
unit::<{ $base + 7 }>,
];
};
}
#[cfg(feature = "__pad_small")]
#[used]
static PAD_S: [extern "C" fn(&mut [u64; 32]) -> u64; 2] = [unit::<900>, unit::<901>];
#[cfg(not(feature = "__pad_small"))]
rung!(PAD1, 0);
#[cfg(feature = "__pad2")]
rung!(PAD2, 100);
#[cfg(feature = "__pad3")]
rung!(PAD3, 200);
#[cfg(feature = "__pad4")]
rung!(PAD4, 300);
}
struct LfBlock<'a, 'b, BD: BitDepth> {
scratch: &'b mut LfScratch<BD>,
origin: PicOffset<'a>,
stride: isize,
w: usize,
h: usize,
base: usize,
stridea: isize,
strideb: isize,
#[cfg_attr(not(target_arch = "aarch64"), allow(dead_code))]
is_v: bool,
}
#[inline(always)]
fn lf_reach(wd: c_int) -> isize {
if wd >= 16 {
7
} else if wd > 6 {
4
} else if wd > 4 {
3
} else {
2
}
}
#[inline(always)]
fn lf_group_wd(is_y: bool, vmask: &[u32; 3], xy: u32) -> c_int {
if is_y {
let idx = if vmask[2] & xy != 0 {
2
} else {
(vmask[1] & xy != 0) as c_int
};
4 << idx
} else {
4 + 2 * (vmask[1] & xy != 0) as c_int
}
}
#[cfg_attr(
not(any(target_arch = "x86_64", target_arch = "wasm32", test)),
allow(dead_code)
)]
#[inline]
pub(crate) fn lf_run_reach(is_y: bool, vmask: &[u32; 3]) -> usize {
let wd = if is_y {
if vmask[2] != 0 {
16
} else if vmask[1] != 0 {
8
} else {
4
}
} else if vmask[1] != 0 {
6
} else {
4
};
lf_reach(wd) as usize
}
#[cfg_attr(
not(any(target_arch = "x86_64", target_arch = "wasm32", test)),
allow(dead_code)
)]
#[inline]
pub(crate) fn lf_compact_window(
is_v: bool,
is_y: bool,
vmask: &[u32; 3],
max_iter: usize,
offset: usize,
pxstride: usize,
) -> (usize, usize, usize, usize) {
let r = lf_run_reach(is_y, vmask);
if is_v {
let w = max_iter * 4;
(w, 2 * r, offset.saturating_sub(r * pxstride), r * w)
} else {
(2 * r, max_iter * 4, offset.saturating_sub(r), r)
}
}
impl<'a, 'b, BD: BitDepth> LfBlock<'a, 'b, BD> {
#[inline]
fn open(
scratch: &'b mut LfScratch<BD>,
dst: PicOffset<'a>,
is_v: bool,
stride: isize,
wd: c_int,
groups: usize,
) -> Option<Self> {
let reach = lf_reach(wd);
let (w, h, origin_delta, stridea, strideb, base) = if is_v {
(
4 * groups,
2 * reach as usize,
-reach * stride,
1isize,
LF_BW as isize,
reach as usize * LF_BW,
)
} else {
(
2 * reach as usize,
4 * groups,
-reach,
LF_BW as isize,
1isize,
reach as usize,
)
};
let first = dst.offset as isize + origin_delta;
let last = first + (h as isize - 1) * stride;
if first < 0 || last < 0 {
return None;
}
if first.max(last) as usize + w > dst.data.pixel_len::<BD>() {
return None;
}
let origin = PicOffset {
data: dst.data,
offset: first as usize,
};
match w {
4 => Self::fill::<4>(scratch, origin, stride, h),
6 => Self::fill::<6>(scratch, origin, stride, h),
8 => Self::fill::<8>(scratch, origin, stride, h),
12 => Self::fill::<12>(scratch, origin, stride, h),
14 => Self::fill::<14>(scratch, origin, stride, h),
16 => Self::fill::<16>(scratch, origin, stride, h),
_ => {
for row in 0..h {
let off = origin.offset.wrapping_add_signed(row as isize * stride);
let guard = PicOffset {
data: origin.data,
offset: off,
}
.slice::<BD>(w);
scratch.buf[row * LF_BW..][..w].copy_from_slice(&guard);
scratch.pristine[row * LF_BW..][..w].copy_from_slice(&guard);
}
}
}
Some(Self {
scratch,
origin,
stride,
w,
h,
base,
stridea,
strideb,
is_v,
})
}
#[inline(always)]
fn fill<const W: usize>(
scratch: &mut LfScratch<BD>,
origin: PicOffset,
stride: isize,
h: usize,
) {
if (!origin.data.uses_row_guards() || lf_hull_reads()) && !lf_force_per_row() {
return Self::fill_hull::<W>(scratch, origin, stride, h);
}
Self::fill_threaded::<W>(scratch, origin, stride, h)
}
#[inline(never)]
fn fill_threaded<const W: usize>(
scratch: &mut LfScratch<BD>,
origin: PicOffset,
stride: isize,
h: usize,
) {
{
let ps = core::mem::size_of::<BD::Pixel>();
origin.data.dm().probe_eval_rect(
core::panic::Location::caller(),
false,
origin.offset * ps,
W * ps,
h,
stride * ps as isize,
);
}
if Self::fill_rect::<W>(scratch, origin, stride, h) {
return;
}
for row in 0..h {
let off = origin.offset.wrapping_add_signed(row as isize * stride);
if lf_double_reads() {
let extra = PicOffset {
data: origin.data,
offset: off,
}
.slice::<BD>(W);
core::hint::black_box(&extra[0]);
}
let guard = PicOffset {
data: origin.data,
offset: off,
}
.slice::<BD>(W);
let src: &[BD::Pixel; W] = (&guard[..W]).try_into().expect("guard is W long");
let dst: &mut [BD::Pixel; W] = (&mut scratch.buf[row * LF_BW..][..W])
.try_into()
.expect("scratch row is LF_BW >= W long");
*dst = *src;
let pri: &mut [BD::Pixel; W] = (&mut scratch.pristine[row * LF_BW..][..W])
.try_into()
.expect("scratch row is LF_BW >= W long");
*pri = *src;
}
}
#[inline(always)]
fn fill_rect<const W: usize>(
scratch: &mut LfScratch<BD>,
origin: PicOffset,
stride: isize,
h: usize,
) -> bool {
let Some(rect) = origin
.data
.dm()
.index_rect_as::<BD::Pixel>(origin.offset, W, h, stride)
else {
return false;
};
for row in 0..h {
let src: &[BD::Pixel; W] = rect.row(row).try_into().expect("row is W long");
let dst: &mut [BD::Pixel; W] = (&mut scratch.buf[row * LF_BW..][..W])
.try_into()
.expect("scratch row is LF_BW >= W long");
*dst = *src;
let pri: &mut [BD::Pixel; W] = (&mut scratch.pristine[row * LF_BW..][..W])
.try_into()
.expect("scratch row is LF_BW >= W long");
*pri = *src;
}
true
}
#[inline(always)]
fn fill_hull<const W: usize>(
scratch: &mut LfScratch<BD>,
origin: PicOffset,
stride: isize,
h: usize,
) {
debug_assert!(h > 0);
let astride = stride.unsigned_abs();
let lo = if stride >= 0 {
origin.offset
} else {
origin.offset - (h - 1) * astride
};
let total = (h - 1) * astride + W;
let guard = origin.data.slice::<BD, _>((lo.., ..total));
{
let ps = core::mem::size_of::<BD::Pixel>();
guard.probe_declare_rows(lo * ps, W * ps, h, stride * ps as isize);
}
for row in 0..h {
let idx = if stride >= 0 {
row * astride
} else {
(h - 1 - row) * astride
};
let src: &[BD::Pixel; W] = (&guard[idx..][..W])
.try_into()
.expect("the hull covers W pixels at every row offset");
let dst: &mut [BD::Pixel; W] = (&mut scratch.buf[row * LF_BW..][..W])
.try_into()
.expect("scratch row is LF_BW >= W long");
*dst = *src;
let pri: &mut [BD::Pixel; W] = (&mut scratch.pristine[row * LF_BW..][..W])
.try_into()
.expect("scratch row is LF_BW >= W long");
*pri = *src;
}
}
#[inline]
fn taps(&mut self, g: usize) -> CompactTaps<'_, BD> {
CompactTaps {
len: (self.h - 1) * LF_BW + self.w,
buf: &mut self.scratch.buf,
base: self.base.wrapping_add_signed(4 * g as isize * self.stridea),
stridea: self.stridea,
strideb: self.strideb,
}
}
#[inline]
fn filter_run(&mut self, params: &[(u8, u8, u8, c_int)], wd: c_int, bd: BD) {
#[cfg(all(target_arch = "aarch64", not(feature = "asm")))]
{
use zerocopy::IntoBytes as _;
if crate::src::safe_simd::loopfilter_arm::lf_compact_run_neon(
BD::BPC,
self.scratch.buf.as_mut_bytes(),
self.base,
self.is_v,
4 * params.len(),
params,
wd,
bd.bitdepth() - 8,
bd.bitdepth_max().into(),
) {
return;
}
}
for (j, &(e, i, h, _)) in params.iter().enumerate() {
loop_filter::<BD, _>(&mut self.taps(j), e, i, h, wd, bd);
}
}
#[inline(always)]
fn changed_span(&self, row: usize) -> Option<(usize, usize)> {
#[cfg(all(target_arch = "aarch64", not(feature = "asm")))]
{
use zerocopy::IntoBytes as _;
if let Some(span) = crate::src::safe_simd::loopfilter_arm::lf_diff_span(
BD::BPC,
self.scratch.buf.as_bytes(),
self.scratch.pristine.as_bytes(),
row,
self.w,
) {
return span;
}
}
let work = &self.scratch.buf[row * LF_BW..][..self.w];
let orig = &self.scratch.pristine[row * LF_BW..][..self.w];
let first = work.iter().zip(orig).position(|(a, b)| a != b)?;
let last = work
.iter()
.zip(orig)
.rposition(|(a, b)| a != b)
.expect("a differing pixel exists, so rposition finds one");
Some((first, last))
}
#[inline]
fn close(self) {
for row in 0..self.h {
let Some((first, last)) = self.changed_span(row) else {
continue; };
let work = &self.scratch.buf[row * LF_BW..][..self.w];
let off = self
.origin
.offset
.wrapping_add_signed(row as isize * self.stride)
+ first;
let mut guard = PicOffset {
data: self.origin.data,
offset: off,
}
.slice_mut::<BD>(last + 1 - first);
guard.copy_from_slice(&work[first..=last]);
}
}
}
#[inline(never)]
fn loop_filter<BD: BitDepth, T: LfTaps<BD>>(taps: &mut T, e: u8, i: u8, h: u8, wd: c_int, bd: BD) {
let bitdepth_min_8 = bd.bitdepth() - 8;
let [f, e, i, h] = [1, e, i, h].map(|n| (n as i32) << bitdepth_min_8);
for idx in 0..4 {
let get_dst = |stride_index: isize| T::get(&*taps, idx, stride_index);
let mut p6 = 0;
let mut p5 = 0;
let mut p4 = 0;
let mut p3 = 0;
let mut p2 = 0;
let p1 = get_dst(-2);
let p0 = get_dst(-1);
let q0 = get_dst(0);
let q1 = get_dst(1);
let mut q2 = 0;
let mut q3 = 0;
let mut q4 = 0;
let mut q5 = 0;
let mut q6 = 0;
let mut flat8out = false;
let mut flat8in = false;
let mut fm = (p1 - p0).abs() <= i
&& (q1 - q0).abs() <= i
&& (p0 - q0).abs() * 2 + ((p1 - q1).abs() >> 1) <= e;
if wd > 4 {
p2 = get_dst(-3);
q2 = get_dst(2);
fm &= (p2 - p1).abs() <= i && (q2 - q1).abs() <= i;
if wd > 6 {
p3 = get_dst(-4);
q3 = get_dst(3);
fm &= (p3 - p2).abs() <= i && (q3 - q2).abs() <= i;
}
}
if !fm {
continue;
}
if wd >= 16 {
p6 = get_dst(-7);
p5 = get_dst(-6);
p4 = get_dst(-5);
q4 = get_dst(4);
q5 = get_dst(5);
q6 = get_dst(6);
flat8out = (p6 - p0).abs() <= f
&& (p5 - p0).abs() <= f
&& (p4 - p0).abs() <= f
&& (q4 - q0).abs() <= f
&& (q5 - q0).abs() <= f
&& (q6 - q0).abs() <= f;
}
if wd >= 6 {
flat8in = (p2 - p0).abs() <= f
&& (p1 - p0).abs() <= f
&& (q1 - q0).abs() <= f
&& (q2 - q0).abs() <= f;
}
if wd >= 8 {
flat8in &= (p3 - p0).abs() <= f && (q3 - q0).abs() <= f;
}
macro_rules! set_dst {
($k:expr, $v:expr $(,)?) => {
T::set(&mut *taps, idx, $k, ($v).as_::<BD::Pixel>())
};
}
macro_rules! set_dst_clipped {
($k:expr, $v:expr $(,)?) => {
T::set(&mut *taps, idx, $k, bd.iclip_pixel($v))
};
}
if wd >= 16 && flat8out && flat8in {
set_dst!(
-6,
p6 + p6 + p6 + p6 + p6 + p6 * 2 + p5 * 2 + p4 * 2 + p3 + p2 + p1 + p0 + q0 + 8 >> 4,
);
set_dst!(
-5,
p6 + p6 + p6 + p6 + p6 + p5 * 2 + p4 * 2 + p3 * 2 + p2 + p1 + p0 + q0 + q1 + 8 >> 4,
);
set_dst!(
-4,
p6 + p6 + p6 + p6 + p5 + p4 * 2 + p3 * 2 + p2 * 2 + p1 + p0 + q0 + q1 + q2 + 8 >> 4,
);
set_dst!(
-3,
p6 + p6 + p6 + p5 + p4 + p3 * 2 + p2 * 2 + p1 * 2 + p0 + q0 + q1 + q2 + q3 + 8 >> 4,
);
set_dst!(
-2,
p6 + p6 + p5 + p4 + p3 + p2 * 2 + p1 * 2 + p0 * 2 + q0 + q1 + q2 + q3 + q4 + 8 >> 4,
);
set_dst!(
-1,
p6 + p5 + p4 + p3 + p2 + p1 * 2 + p0 * 2 + q0 * 2 + q1 + q2 + q3 + q4 + q5 + 8 >> 4,
);
set_dst!(
0,
p5 + p4 + p3 + p2 + p1 + p0 * 2 + q0 * 2 + q1 * 2 + q2 + q3 + q4 + q5 + q6 + 8 >> 4,
);
set_dst!(
1,
p4 + p3 + p2 + p1 + p0 + q0 * 2 + q1 * 2 + q2 * 2 + q3 + q4 + q5 + q6 + q6 + 8 >> 4,
);
set_dst!(
2,
p3 + p2 + p1 + p0 + q0 + q1 * 2 + q2 * 2 + q3 * 2 + q4 + q5 + q6 + q6 + q6 + 8 >> 4,
);
set_dst!(
3,
p2 + p1 + p0 + q0 + q1 + q2 * 2 + q3 * 2 + q4 * 2 + q5 + q6 + q6 + q6 + q6 + 8 >> 4,
);
set_dst!(
4,
p1 + p0 + q0 + q1 + q2 + q3 * 2 + q4 * 2 + q5 * 2 + q6 + q6 + q6 + q6 + q6 + 8 >> 4,
);
set_dst!(
5,
p0 + q0 + q1 + q2 + q3 + q4 * 2 + q5 * 2 + q6 * 2 + q6 + q6 + q6 + q6 + q6 + 8 >> 4,
);
} else if wd >= 8 && flat8in {
set_dst!(-3, p3 + p3 + p3 + 2 * p2 + p1 + p0 + q0 + 4 >> 3);
set_dst!(-2, p3 + p3 + p2 + 2 * p1 + p0 + q0 + q1 + 4 >> 3);
set_dst!(-1, p3 + p2 + p1 + 2 * p0 + q0 + q1 + q2 + 4 >> 3);
set_dst!(0, p2 + p1 + p0 + 2 * q0 + q1 + q2 + q3 + 4 >> 3);
set_dst!(1, p1 + p0 + q0 + 2 * q1 + q2 + q3 + q3 + 4 >> 3);
set_dst!(2, p0 + q0 + q1 + 2 * q2 + q3 + q3 + q3 + 4 >> 3);
} else if wd == 6 && flat8in {
set_dst!(-2, p2 + 2 * p2 + 2 * p1 + 2 * p0 + q0 + 4 >> 3);
set_dst!(-1, p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4 >> 3);
set_dst!(0, p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4 >> 3);
set_dst!(1, p0 + 2 * q0 + 2 * q1 + 2 * q2 + q2 + 4 >> 3);
} else {
let hev = (p1 - p0).abs() > h || (q1 - q0).abs() > h;
fn iclip_diff(v: c_int, bitdepth_min_8: u8) -> i32 {
iclip(
v,
-128 * (1 << bitdepth_min_8),
128 * (1 << bitdepth_min_8) - 1,
)
}
if hev {
let f = iclip_diff(p1 - q1, bitdepth_min_8);
let f = iclip_diff(3 * (q0 - p0) + f, bitdepth_min_8);
let f1 = cmp::min(f + 4, (128 << bitdepth_min_8) - 1) >> 3;
let f2 = cmp::min(f + 3, (128 << bitdepth_min_8) - 1) >> 3;
set_dst_clipped!(-1, p0 + f2);
set_dst_clipped!(0, q0 - f1);
} else {
let f = iclip_diff(3 * (q0 - p0), bitdepth_min_8);
let f1 = cmp::min(f + 4, (128 << bitdepth_min_8) - 1) >> 3;
let f2 = cmp::min(f + 3, (128 << bitdepth_min_8) - 1) >> 3;
set_dst_clipped!(-1, p0 + f2);
set_dst_clipped!(0, q0 - f1);
let f = (f1 + 1) >> 1;
set_dst_clipped!(-2, p1 + f);
set_dst_clipped!(1, q1 - f);
}
}
}
}
#[derive(FromRepr)]
enum HV {
H,
V,
}
#[derive(FromRepr)]
enum YUV {
Y,
UV,
}
fn loop_filter_sb128_rust<BD: BitDepth, const HV: usize, const YUV: usize>(
dst: PicOffset,
vmask: &[u32; 3],
lvl: WithOffset<&[AtomicU8]>,
b4_stride: usize,
lut: &Align16<Av1FilterLUT>,
_wh: c_int,
bd: BD,
) {
let hv = HV::from_repr(HV).unwrap();
let yuv = YUV::from_repr(YUV).unwrap();
let stride = dst.pixel_stride::<BD>();
let (stridea, strideb) = match hv {
HV::H => (stride, 1),
HV::V => (1, stride),
};
let (b4_stridea, b4_strideb) = match hv {
HV::H => (b4_stride, 1),
HV::V => (1, b4_stride),
};
let vm = match yuv {
YUV::Y => vmask[0] | vmask[1] | vmask[2],
YUV::UV => vmask[0] | vmask[1],
};
let is_v = matches!(hv, HV::V);
let mut params = [(0u8, 0u8, 0u8, 0 as c_int); 32];
let mut filters = [false; 32];
let mut n_groups = 0usize;
{
let mut xy = 1u32;
let mut lvl = lvl;
while vm & !xy.wrapping_sub(1) != 0 {
if vm & xy != 0 {
let l = lvl.data[lvl.offset].load(Relaxed);
let l = if l != 0 {
l
} else {
let lvl = lvl - 4 * b4_strideb;
lvl.data[lvl.offset].load(Relaxed)
};
if l != 0 {
let idx = lf_group_wd(matches!(yuv, YUV::Y), vmask, xy);
params[n_groups] = (e_of(lut, l), i_of(lut, l), l >> 4, idx);
filters[n_groups] = true;
}
}
n_groups += 1;
xy <<= 1;
lvl += 4 * b4_stridea;
}
}
let mut scratch = LfScratch::new();
let group_step = 4 * stridea;
let mut g = 0usize;
while g < n_groups {
if !filters[g] {
g += 1;
continue;
}
let wd = params[g].3;
let mut n = 1;
while n < LF_BATCH_MAX && g + n < n_groups && filters[g + n] && params[g + n].3 == wd {
n += 1;
}
let run_dst = dst + group_step * g as isize;
match LfBlock::<BD>::open(&mut scratch, run_dst, is_v, stride, wd, n) {
Some(mut block) => {
block.filter_run(¶ms[g..g + n], wd, bd);
block.close();
}
None => {
for j in 0..n {
let (e, i, h, _) = params[g + j];
let one_dst = dst + group_step * (g + j) as isize;
match LfBlock::<BD>::open(&mut scratch, one_dst, is_v, stride, wd, 1) {
Some(mut block) => {
block.filter_run(¶ms[g + j..g + j + 1], wd, bd);
block.close();
}
None => {
let mut taps = DirectTaps {
dst: one_dst,
stridea,
strideb,
};
loop_filter::<BD, _>(&mut taps, e, i, h, wd, bd);
}
}
}
}
}
g += n;
}
}
#[inline(always)]
fn e_of(lut: &Align16<Av1FilterLUT>, l: u8) -> u8 {
lut.e[l as usize]
}
#[inline(always)]
fn i_of(lut: &Align16<Av1FilterLUT>, l: u8) -> u8 {
lut.i[l as usize]
}
#[cfg(asm_loopfilter)]
#[deny(unsafe_op_in_unsafe_fn)]
unsafe extern "C" fn loop_filter_sb128_c_erased<BD: BitDepth, const HV: usize, const YUV: usize>(
_dst_ptr: *mut DynPixel,
_stride: ptrdiff_t,
vmask: &[u32; 3],
_lvl_ptr: *const [u8; 4],
b4_stride: isize,
lut: &Align16<Av1FilterLUT>,
wh: c_int,
bitdepth_max: c_int,
dst: *const FFISafe<PicOffset>,
lvl: *const FFISafe<WithOffset<&[AtomicU8]>>,
) {
let dst = *unsafe { FFISafe::get(dst) };
let lvl = *unsafe { FFISafe::get(lvl) };
let b4_stride = b4_stride as usize;
let bd = BD::from_c(bitdepth_max);
loop_filter_sb128_rust::<BD, { HV }, { YUV }>(dst, vmask, lvl, b4_stride, lut, wh, bd)
}
impl Rav1dLoopFilterDSPContext {
pub const fn default<BD: BitDepth>() -> Self {
cfg_if::cfg_if! {
if #[cfg(asm_loopfilter)] {
use HV::*;
use YUV::*;
Self {
loop_filter_sb: LoopFilterYUVDSPContext {
y: LoopFilterHVDSPContext {
h: loopfilter_sb::Fn::default::<BD, { H as _ }, { Y as _ }>(),
v: loopfilter_sb::Fn::default::<BD, { V as _ }, { Y as _ }>(),
},
uv: LoopFilterHVDSPContext {
h: loopfilter_sb::Fn::default::<BD, { H as _ }, { UV as _ }>(),
v: loopfilter_sb::Fn::default::<BD, { V as _ }, { UV as _ }>(),
},
},
}
} else {
Self {
loop_filter_sb: LoopFilterYUVDSPContext {
y: LoopFilterHVDSPContext {
h: loopfilter_sb::Fn::DEFAULT,
v: loopfilter_sb::Fn::DEFAULT,
},
uv: LoopFilterHVDSPContext {
h: loopfilter_sb::Fn::DEFAULT,
v: loopfilter_sb::Fn::DEFAULT,
},
},
}
}
}
}
#[cfg(all(asm_loopfilter, any(target_arch = "x86", target_arch = "x86_64")))]
#[inline(always)]
const fn init_x86<BD: BitDepth>(mut self, flags: CpuFlags) -> Self {
if !flags.contains(CpuFlags::SSSE3) {
return self;
}
self.loop_filter_sb.y.h = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_y, ssse3);
self.loop_filter_sb.y.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_y, ssse3);
self.loop_filter_sb.uv.h = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_uv, ssse3);
self.loop_filter_sb.uv.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_uv, ssse3);
#[cfg(target_arch = "x86_64")]
{
if !flags.contains(CpuFlags::AVX2) {
return self;
}
self.loop_filter_sb.y.h = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_y, avx2);
self.loop_filter_sb.y.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_y, avx2);
self.loop_filter_sb.uv.h = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_uv, avx2);
self.loop_filter_sb.uv.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_uv, avx2);
if !flags.contains(CpuFlags::AVX512ICL) {
return self;
}
self.loop_filter_sb.y.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_y, avx512icl);
self.loop_filter_sb.uv.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_uv, avx512icl);
if !flags.contains(CpuFlags::SLOW_GATHER) {
self.loop_filter_sb.y.h = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_y, avx512icl);
self.loop_filter_sb.uv.h =
bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_uv, avx512icl);
}
}
self
}
#[cfg(all(asm_loopfilter, any(target_arch = "arm", target_arch = "aarch64")))]
#[inline(always)]
const fn init_arm<BD: BitDepth>(mut self, flags: CpuFlags) -> Self {
if !flags.contains(CpuFlags::NEON) {
return self;
}
self.loop_filter_sb.y.h = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_y, neon);
self.loop_filter_sb.y.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_y, neon);
self.loop_filter_sb.uv.h = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_h_sb_uv, neon);
self.loop_filter_sb.uv.v = bd_fn!(loopfilter_sb::decl_fn, BD, lpf_v_sb_uv, neon);
self
}
#[inline(always)]
const fn init<BD: BitDepth>(self, flags: CpuFlags) -> Self {
#[cfg(asm_loopfilter)]
{
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
return self.init_x86::<BD>(flags);
}
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
{
return self.init_arm::<BD>(flags);
}
}
#[allow(unreachable_code)] {
let _ = flags;
self
}
}
pub const fn new<BD: BitDepth>(flags: CpuFlags) -> Self {
Self::default::<BD>().init::<BD>(flags)
}
}
#[cfg(test)]
mod run_reach {
use super::*;
const PATS: [u32; 8] = [
0,
1,
2,
0x8000_0000,
0xffff_ffff,
0x5555_5555,
0xaaaa_aaaa,
0x0001_0000,
];
fn oracle(is_y: bool, vmask: &[u32; 3]) -> usize {
let vm = if is_y {
vmask[0] | vmask[1] | vmask[2]
} else {
vmask[0] | vmask[1]
};
let mut want = 0usize;
for bit in 0..32 {
let xy = 1u32 << bit;
if vm & xy != 0 {
want = want.max(lf_reach(lf_group_wd(is_y, vmask, xy)) as usize);
}
}
want
}
#[test]
fn run_reach_equals_the_widest_group_it_can_meet() {
let mut covered = [false; 8];
for &m0 in &PATS {
for &m1 in &PATS {
for &m2 in &PATS {
let vmask = [m0, m1, m2];
for is_y in [true, false] {
let want = oracle(is_y, &vmask);
if want == 0 {
continue;
}
assert_eq!(
lf_run_reach(is_y, &vmask),
want,
"is_y={is_y} mask={vmask:08x?}"
);
covered[want] = true;
}
}
}
}
assert!(
covered[2] && covered[3] && covered[4] && covered[7],
"reaches exercised: {covered:?}"
);
}
#[test]
fn run_reach_fits_the_transform_that_selected_it() {
for (wd, rows_below) in [(4, 4usize), (6, 4), (8, 8), (16, 16)] {
assert!(
lf_reach(wd) as usize <= rows_below,
"wd {wd} reaches {} into {rows_below} rows",
lf_reach(wd)
);
}
}
}
#[cfg(test)]
mod compact_window {
use super::*;
const STRIDE: usize = 384;
#[cfg(not(feature = "unchecked"))]
const ROWS: usize = 256;
const COL: usize = STRIDE - 4;
const ROW0: usize = 160;
const MAX_ITER: usize = 8;
const CHROMA_MASKS: [[u32; 3]; 2] = [[0xffff_ffff, 0, 0], [0xffff_ffff, 0xffff_ffff, 0]];
#[test]
fn h_window_fits_the_transform_that_selected_it() {
for (label, is_y, vmask, cols_right) in [
("luma wd4", true, [1u32, 0, 0], 4usize),
("luma wd8", true, [1, 1, 0], 8),
("luma wd16", true, [1, 1, 1], 16),
("chroma wd4", false, [1, 0, 0], 4),
("chroma wd6", false, [1, 1, 0], 4),
] {
let offset = 64 * STRIDE + 64;
let (w, h, start, base) = lf_compact_window(false, is_y, &vmask, 1, offset, STRIDE);
assert_eq!(h, 4, "{label}: one group is 4 rows");
assert_eq!(start + base, offset, "{label}: the edge sits at `base`");
assert!(
base <= cols_right,
"{label}: reads {base} columns before the edge, transform leaves {cols_right}"
);
assert!(
w - base <= cols_right,
"{label}: reads {} columns after the edge, transform leaves {cols_right}",
w - base
);
}
}
#[test]
fn issue_524_h_window_stays_inside_its_picture_row() {
for vmask in CHROMA_MASKS {
let offset = ROW0 * STRIDE + COL;
let (w, h, start, _base) =
lf_compact_window(false, false, &vmask, MAX_ITER, offset, STRIDE);
assert_eq!(h, MAX_ITER * 4);
for row in 0..h {
let row_start = start + row * STRIDE;
let col = row_start % STRIDE;
assert!(
col + w <= STRIDE,
"mask={vmask:08x?} row {row}: guard [{row_start}..{}) laps {} bytes \
into picture row {}",
row_start + w,
col + w - STRIDE,
row_start / STRIDE + 1
);
}
}
}
#[cfg(not(feature = "unchecked"))]
#[test]
fn issue_524_h_window_does_not_collide_with_the_next_rows_stitch() {
use crate::include::common::bitdepth::BitDepth8;
use crate::include::dav1d::picture::Rav1dPictureDataComponent;
use crate::src::with_offset::WithOffset;
use std::panic::{self, AssertUnwindSafe};
let next_sbrow = ROW0 + MAX_ITER * 4;
assert!(next_sbrow < ROWS);
for vmask in CHROMA_MASKS {
let mut px = vec![0u8; STRIDE * ROWS];
let pic = Rav1dPictureDataComponent::wrap_buf::<BitDepth8>(&mut px, STRIDE);
let (w, h, start, _base) =
lf_compact_window(false, false, &vmask, MAX_ITER, ROW0 * STRIDE + COL, STRIDE);
let held = pic.slice_mut::<BitDepth8, _>((next_sbrow * STRIDE.., ..STRIDE));
let at = WithOffset {
data: &pic,
offset: start,
};
let prev = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let read = panic::catch_unwind(AssertUnwindSafe(|| {
at.compact_read_per_row::<BitDepth8>(w, h);
}));
panic::set_hook(prev);
drop(held);
assert!(
read.is_ok(),
"mask={vmask:08x?}: the H compact read window [{start}..) {w}x{h} \
overlaps the mutable borrow of picture row {next_sbrow}"
);
}
}
#[cfg(not(feature = "unchecked"))]
#[test]
fn issue_524_harness_detects_a_window_that_does_lap() {
use crate::include::common::bitdepth::BitDepth8;
use crate::include::dav1d::picture::Rav1dPictureDataComponent;
use crate::src::with_offset::WithOffset;
use std::panic::{self, AssertUnwindSafe};
let next_sbrow = ROW0 + MAX_ITER * 4;
let mut px = vec![0u8; STRIDE * ROWS];
let pic = Rav1dPictureDataComponent::wrap_buf::<BitDepth8>(&mut px, STRIDE);
let (w, h, start) = (3 + 5, MAX_ITER * 4, ROW0 * STRIDE + COL - 3);
let held = pic.slice_mut::<BitDepth8, _>((next_sbrow * STRIDE.., ..STRIDE));
let at = WithOffset {
data: &pic,
offset: start,
};
let prev = panic::take_hook();
panic::set_hook(Box::new(|_| {}));
let read = panic::catch_unwind(AssertUnwindSafe(|| {
at.compact_read_per_row::<BitDepth8>(w, h);
}));
panic::set_hook(prev);
drop(held);
assert!(
read.is_err(),
"the pre-#524 window [{start}..) {w}x{h} laps into picture row \
{next_sbrow} but the guards did not report it"
);
}
}
#[cfg(all(test, target_arch = "aarch64", not(feature = "asm")))]
mod neon_parity {
use super::*;
use crate::include::common::bitdepth::BitDepth8;
use crate::include::common::bitdepth::BitDepth16;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u32 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
(x >> 32) as u32
}
fn below(&mut self, n: u32) -> u32 {
self.next() % n
}
}
#[derive(Default, Debug)]
struct Coverage {
nofilter: u32,
narrow_hev: u32,
narrow_flat: u32,
flat6: u32,
flat8: u32,
wide: u32,
}
fn classify<BD: BitDepth>(
buf: &[BD::Pixel; LF_BLOCK_LEN],
base: usize,
stridea: isize,
strideb: isize,
lane: usize,
e: u8,
i: u8,
h: u8,
wd: c_int,
bd: BD,
cov: &mut Coverage,
) {
let bd8 = bd.bitdepth() - 8;
let [f, e, i, h] = [1, e, i, h].map(|n| (n as i32) << bd8);
let at = |k: isize| -> i32 {
buf[base.wrapping_add_signed(stridea * lane as isize + strideb * k)
& (LF_BLOCK_LEN - 1)]
.as_::<i32>()
};
let (p6, p5, p4, p3, p2, p1, p0) = (at(-7), at(-6), at(-5), at(-4), at(-3), at(-2), at(-1));
let (q0, q1, q2, q3, q4, q5, q6) = (at(0), at(1), at(2), at(3), at(4), at(5), at(6));
let mut fm = (p1 - p0).abs() <= i
&& (q1 - q0).abs() <= i
&& (p0 - q0).abs() * 2 + ((p1 - q1).abs() >> 1) <= e;
if wd > 4 {
fm &= (p2 - p1).abs() <= i && (q2 - q1).abs() <= i;
if wd > 6 {
fm &= (p3 - p2).abs() <= i && (q3 - q2).abs() <= i;
}
}
if !fm {
cov.nofilter += 1;
return;
}
let mut flat8in = false;
if wd >= 6 {
flat8in = (p2 - p0).abs() <= f
&& (p1 - p0).abs() <= f
&& (q1 - q0).abs() <= f
&& (q2 - q0).abs() <= f;
}
if wd >= 8 {
flat8in &= (p3 - p0).abs() <= f && (q3 - q0).abs() <= f;
}
let flat8out = wd >= 16
&& (p6 - p0).abs() <= f
&& (p5 - p0).abs() <= f
&& (p4 - p0).abs() <= f
&& (q4 - q0).abs() <= f
&& (q5 - q0).abs() <= f
&& (q6 - q0).abs() <= f;
if wd >= 16 && flat8out && flat8in {
cov.wide += 1;
} else if wd >= 8 && flat8in {
cov.flat8 += 1;
} else if wd == 6 && flat8in {
cov.flat6 += 1;
} else if (p1 - p0).abs() > h || (q1 - q0).abs() > h {
cov.narrow_hev += 1;
} else {
cov.narrow_flat += 1;
}
}
fn one_cell<BD: BitDepth>(bd: BD, wd: c_int, is_v: bool, groups: usize, cov: &mut Coverage) {
let bd_max: u16 = bd.bitdepth_max().into();
let reach = lf_reach(wd) as usize;
let (w, h, stridea, strideb, base) = if is_v {
(4 * groups, 2 * reach, 1isize, LF_BW as isize, reach * LF_BW)
} else {
(2 * reach, 4 * groups, LF_BW as isize, 1isize, reach)
};
let mut rng = Rng(0x9E37_79B9_7F4A_7C15
^ ((wd as u64) << 40)
^ ((groups as u64) << 20)
^ (is_v as u64)
^ ((bd_max as u64) << 8));
for trial in 0..3000u32 {
let amp = 1u32 + (trial % 24) * (bd_max as u32 + 1) / 24;
let plateau = rng.below(bd_max as u32 + 1);
let mut buf = [BD::Pixel::from(0u8); LF_BLOCK_LEN];
for (idx, px) in buf.iter_mut().enumerate() {
let _ = idx;
let v: u16 = if trial % 8 == 7 {
rng.below(bd_max as u32 + 1) as u16
} else {
(plateau as i64 + rng.below(2 * amp + 1) as i64 - amp as i64)
.clamp(0, bd_max as i64) as u16
};
*px = v.as_::<BD::Pixel>();
}
let mut params = [(0u8, 0u8, 0u8, wd); LF_BATCH_MAX];
for p in params.iter_mut().take(groups) {
*p = (
rng.below(256) as u8,
rng.below(64) as u8,
rng.below(16) as u8,
wd,
);
}
for lane in 0..4 * groups {
let (e, i, h, _) = params[lane / 4];
classify::<BD>(&buf, base, stridea, strideb, lane, e, i, h, wd, bd, cov);
}
let mut simd = buf;
use zerocopy::IntoBytes as _;
let ok = crate::src::safe_simd::loopfilter_arm::lf_compact_run_neon(
BD::BPC,
simd.as_mut_bytes(),
base,
is_v,
4 * groups,
¶ms[..groups],
wd,
bd.bitdepth() - 8,
bd_max,
);
assert!(ok, "kernel refused wd={wd} is_v={is_v} groups={groups}");
let mut reference = buf;
for (g, &(e, i, hh, _)) in params[..groups].iter().enumerate() {
let mut taps = CompactTaps {
len: (h - 1) * LF_BW + w,
buf: &mut reference,
base: base.wrapping_add_signed(4 * g as isize * stridea),
stridea,
strideb,
};
loop_filter::<BD, _>(&mut taps, e, i, hh, wd, bd);
}
for row in 0..h {
for col in 0..w {
let idx = row * LF_BW + col;
assert_eq!(
simd[idx].as_::<i32>(),
reference[idx].as_::<i32>(),
"bd={} wd={wd} is_v={is_v} groups={groups} trial={trial} \
row={row} col={col} params={:?}",
bd.bitdepth(),
¶ms[..groups],
);
}
}
}
}
fn sweep<BD: BitDepth>(bd: BD, widths: &[c_int]) {
let _guard = crate::src::safe_simd::token_test_lock();
for &wd in widths {
for &is_v in &[false, true] {
for groups in 1..=LF_BATCH_MAX {
let mut cov = Coverage::default();
one_cell::<BD>(bd, wd, is_v, groups, &mut cov);
assert!(
cov.narrow_hev > 0 && cov.narrow_flat > 0,
"wd={wd} is_v={is_v} groups={groups} never took a narrow branch: {cov:?}"
);
match wd {
6 => assert!(cov.flat6 > 0, "wd=6 never flat: {cov:?}"),
8 => assert!(cov.flat8 > 0, "wd=8 never flat: {cov:?}"),
16 => assert!(
cov.wide > 0 && cov.flat8 > 0,
"wd=16 missed a flat branch: {cov:?}"
),
_ => {}
}
assert!(cov.nofilter > 0, "wd={wd} never skipped a lane: {cov:?}");
}
}
}
}
const ALL_WD: [c_int; 4] = [4, 6, 8, 16];
fn diff_span_cell<BD: BitDepth>(bd: BD) {
use zerocopy::IntoBytes as _;
let _guard = crate::src::safe_simd::token_test_lock();
let bd_max: u16 = bd.bitdepth_max().into();
let mut rng = Rng(0xDEAD_BEEF_1234_5678 ^ bd_max as u64);
let mut fired = 0u32;
let mut empty = 0u32;
for _ in 0..20000u32 {
let mut work = [BD::Pixel::from(0u8); LF_BLOCK_LEN];
let mut pristine = [BD::Pixel::from(0u8); LF_BLOCK_LEN];
for (a, b) in work.iter_mut().zip(pristine.iter_mut()) {
let v = rng.below(bd_max as u32 + 1) as u16;
*b = v.as_::<BD::Pixel>();
*a = if rng.below(4) == 0 {
(rng.below(bd_max as u32 + 1) as u16).as_::<BD::Pixel>()
} else {
v.as_::<BD::Pixel>()
};
}
let w = 1 + rng.below(LF_BW as u32) as usize;
let row = rng.below(LF_BW as u32) as usize;
let a = &work[row * LF_BW..][..w];
let b = &pristine[row * LF_BW..][..w];
let want = a
.iter()
.zip(b)
.position(|(x, y)| x != y)
.map(|first| (first, a.iter().zip(b).rposition(|(x, y)| x != y).unwrap()));
let got = crate::src::safe_simd::loopfilter_arm::lf_diff_span(
BD::BPC,
work.as_bytes(),
pristine.as_bytes(),
row,
w,
)
.expect("aarch64 always has NEON");
assert_eq!(got, want, "bd={} row={row} w={w}", bd.bitdepth());
if want.is_some() {
fired += 1;
} else {
empty += 1;
}
}
assert!(
fired > 100 && empty > 100,
"diff span not exercised both ways"
);
}
#[test]
fn neon_diff_span_matches_scalar() {
diff_span_cell::<BitDepth8>(BitDepth8::new(()));
diff_span_cell::<BitDepth16>(BitDepth16::new(1023));
diff_span_cell::<BitDepth16>(BitDepth16::new(4095));
}
#[test]
fn neon_matches_scalar_8bpc() {
sweep::<BitDepth8>(BitDepth8::new(()), &ALL_WD);
}
#[test]
fn neon_matches_scalar_10bpc() {
sweep::<BitDepth16>(BitDepth16::new(1023), &ALL_WD);
}
#[test]
fn neon_matches_scalar_12bpc() {
sweep::<BitDepth16>(BitDepth16::new(4095), &ALL_WD);
}
}
#[cfg(all(test, target_arch = "x86_64", feature = "bitdepth_8"))]
pub(crate) fn loop_filter_scalar_for_test(
buf: &mut [u8],
base: usize,
strides: [isize; 2],
lanes: usize,
levels: [u8; 3],
width: usize,
) {
use crate::include::common::bitdepth::BitDepth8;
struct Taps<'a> {
buf: &'a mut [u8],
base: usize,
strides: [isize; 2],
}
impl Taps<'_> {
fn at(&self, lane: isize, tap: isize) -> usize {
self.base
.checked_add_signed(lane * self.strides[0] + tap * self.strides[1])
.unwrap()
}
}
impl LfTaps<BitDepth8> for Taps<'_> {
fn get(&self, lane: isize, tap: isize) -> i32 {
i32::from(self.buf[self.at(lane, tap)])
}
fn set(&mut self, lane: isize, tap: isize, value: u8) {
let at = self.at(lane, tap);
self.buf[at] = value;
}
}
assert_eq!(lanes % 4, 0);
let [e, i, h] = levels;
for lane in (0..lanes).step_by(4) {
let mut taps = Taps {
buf,
base: base.checked_add_signed(lane as isize * strides[0]).unwrap(),
strides,
};
loop_filter(&mut taps, e, i, h, width as c_int, BitDepth8::new(()));
}
}