pub mod kernels;
pub mod motion;
pub mod prefilter;
mod align;
mod denoiser;
mod dispatch;
mod noise;
mod params;
mod pending;
#[cfg(all(test, any(feature = "vulkan", feature = "metal")))]
mod tests;
pub(crate) use denoiser::RingView;
pub use denoiser::{GpuOutput, NlmDenoiser};
pub use motion::{MotionCompensationMode, MotionEstimation, MotionSearch};
pub use params::{
ChannelMode,
HqParams,
MAX_PATCH_RADIUS,
MAX_SEARCH_RADIUS,
MAX_TEMPORAL_RADIUS,
MIN_FRAME_DIM,
NlmParams,
hq_default_strength,
validate_dimensions,
};
pub use pending::Pending;
pub use prefilter::{DEFAULT_PILOT_STRENGTH_SCALE, PrefilterMode};
pub const BLOCK_X: u32 = 32;
pub const BLOCK_Y: u32 = 8;
pub const BLOCK_X_THIN: u32 = 32;
pub const BLOCK_Y_THIN: u32 = 16;
pub(crate) const MAX_GRID_1D: u32 = 65535;
pub(crate) const BLOCK_1D: u32 = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Depth {
Eight,
Ten,
Twelve,
}
#[derive(Debug, thiserror::Error)]
#[error("unsupported bit depth {0}, av-denoise supports 8, 10, and 12-bit")]
pub struct UnsupportedDepthError(pub usize);
impl Depth {
pub fn from_bits(bits: usize) -> Result<Self, UnsupportedDepthError> {
match bits {
8 => Ok(Depth::Eight),
10 => Ok(Depth::Ten),
12 => Ok(Depth::Twelve),
other => Err(UnsupportedDepthError(other)),
}
}
pub fn bits(self) -> usize {
match self {
Depth::Eight => 8,
Depth::Ten => 10,
Depth::Twelve => 12,
}
}
pub fn bytes_per_sample(self) -> usize {
match self {
Depth::Eight => 1,
Depth::Ten | Depth::Twelve => 2,
}
}
pub fn max_value(self) -> f32 {
((1u32 << self.bits()) - 1) as f32
}
pub fn neutral_chroma(self) -> u16 {
1 << (self.bits() - 1)
}
}
pub fn normalize(input: &[u16], depth: Depth) -> Vec<f32> {
let max = depth.max_value();
input.iter().map(|&v| v as f32 / max).collect()
}
pub fn denormalize(input: &[f32], depth: Depth) -> Vec<u16> {
let max = depth.max_value();
input
.iter()
.map(|&v| (v * max).round().clamp(0.0, max) as u16)
.collect()
}
#[cfg(test)]
mod depth_tests {
use super::*;
#[test]
fn from_bits_accepts_supported_depths() {
assert_eq!(Depth::from_bits(8).unwrap(), Depth::Eight);
assert_eq!(Depth::from_bits(10).unwrap(), Depth::Ten);
assert_eq!(Depth::from_bits(12).unwrap(), Depth::Twelve);
}
#[test]
fn from_bits_rejects_unsupported_depths() {
for bits in [0, 9, 14, 16] {
let err = Depth::from_bits(bits).expect_err("expected rejection");
assert!(
err.to_string().contains(&bits.to_string()),
"error should name the depth, got {err}"
);
}
}
#[test]
fn depth_properties_match_the_format() {
assert_eq!(Depth::Eight.bytes_per_sample(), 1);
assert_eq!(Depth::Ten.bytes_per_sample(), 2);
assert_eq!(Depth::Twelve.bytes_per_sample(), 2);
assert_eq!(Depth::Eight.max_value(), 255.0);
assert_eq!(Depth::Ten.max_value(), 1023.0);
assert_eq!(Depth::Twelve.max_value(), 4095.0);
assert_eq!(Depth::Eight.neutral_chroma(), 128);
assert_eq!(Depth::Ten.neutral_chroma(), 512);
assert_eq!(Depth::Twelve.neutral_chroma(), 2048);
}
#[test]
fn normalized_scale_is_identical_across_depths() {
const TOL: f32 = 1.0 / 255.0;
let eight = normalize(&[16, 235], Depth::Eight);
let ten = normalize(&[64, 940], Depth::Ten);
let twelve = normalize(&[256, 3760], Depth::Twelve);
for (a, b) in eight.iter().zip(ten.iter()) {
assert!((a - b).abs() < TOL, "8-bit {a} vs 10-bit {b}");
}
for (a, b) in eight.iter().zip(twelve.iter()) {
assert!((a - b).abs() < TOL, "8-bit {a} vs 12-bit {b}");
}
}
#[test]
fn normalization_round_trips_at_every_depth() {
for depth in [Depth::Eight, Depth::Ten, Depth::Twelve] {
let max = depth.max_value() as u16;
let original: Vec<u16> = vec![0, 1, 16, 64, 128, 235, max / 2, max - 1, max];
let restored = denormalize(&normalize(&original, depth), depth);
assert_eq!(original, restored, "round trip failed at {depth:?}");
}
}
#[test]
fn denormalize_clamps_out_of_range_input() {
let out = denormalize(&[-0.5, 0.0, 1.0, 1.5], Depth::Ten);
assert_eq!(out, vec![0, 0, 1023, 1023]);
}
}