1pub mod kernels;
26pub mod motion;
27pub mod prefilter;
28
29mod align;
30mod denoiser;
31mod dispatch;
32mod noise;
33mod params;
34mod pending;
35
36#[cfg(all(test, any(feature = "vulkan", feature = "metal")))]
42mod tests;
43
44pub(crate) use denoiser::RingView;
45pub use denoiser::{GpuOutput, NlmDenoiser};
46pub use motion::{MotionCompensationMode, MotionEstimation, MotionSearch};
47pub use params::{
48 ChannelMode,
49 HqParams,
50 MAX_PATCH_RADIUS,
51 MAX_SEARCH_RADIUS,
52 MAX_TEMPORAL_RADIUS,
53 MIN_FRAME_DIM,
54 NlmParams,
55 hq_default_strength,
56 validate_dimensions,
57};
58pub use pending::Pending;
59pub use prefilter::{DEFAULT_PILOT_STRENGTH_SCALE, PrefilterMode};
60
61pub const BLOCK_X: u32 = 32;
63pub const BLOCK_Y: u32 = 8;
65
66pub const BLOCK_X_THIN: u32 = 32;
73pub const BLOCK_Y_THIN: u32 = 16;
74
75pub(crate) const MAX_GRID_1D: u32 = 65535;
78
79pub(crate) const BLOCK_1D: u32 = 256;
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum Depth {
88 Eight,
89 Ten,
90 Twelve,
91}
92
93#[derive(Debug, thiserror::Error)]
95#[error("unsupported bit depth {0}, av-denoise supports 8, 10, and 12-bit")]
96pub struct UnsupportedDepthError(pub usize);
97
98impl Depth {
99 pub fn from_bits(bits: usize) -> Result<Self, UnsupportedDepthError> {
101 match bits {
102 8 => Ok(Depth::Eight),
103 10 => Ok(Depth::Ten),
104 12 => Ok(Depth::Twelve),
105 other => Err(UnsupportedDepthError(other)),
106 }
107 }
108
109 pub fn bits(self) -> usize {
111 match self {
112 Depth::Eight => 8,
113 Depth::Ten => 10,
114 Depth::Twelve => 12,
115 }
116 }
117
118 pub fn bytes_per_sample(self) -> usize {
122 match self {
123 Depth::Eight => 1,
124 Depth::Ten | Depth::Twelve => 2,
125 }
126 }
127
128 pub fn max_value(self) -> f32 {
131 ((1u32 << self.bits()) - 1) as f32
132 }
133
134 pub fn neutral_chroma(self) -> u16 {
136 1 << (self.bits() - 1)
137 }
138}
139
140pub fn normalize(input: &[u16], depth: Depth) -> Vec<f32> {
142 let max = depth.max_value();
143 input.iter().map(|&v| v as f32 / max).collect()
144}
145
146pub fn denormalize(input: &[f32], depth: Depth) -> Vec<u16> {
150 let max = depth.max_value();
151 input
152 .iter()
153 .map(|&v| (v * max).round().clamp(0.0, max) as u16)
154 .collect()
155}
156
157#[cfg(test)]
158mod depth_tests {
159 use super::*;
160
161 #[test]
162 fn from_bits_accepts_supported_depths() {
163 assert_eq!(Depth::from_bits(8).unwrap(), Depth::Eight);
164 assert_eq!(Depth::from_bits(10).unwrap(), Depth::Ten);
165 assert_eq!(Depth::from_bits(12).unwrap(), Depth::Twelve);
166 }
167
168 #[test]
169 fn from_bits_rejects_unsupported_depths() {
170 for bits in [0, 9, 14, 16] {
171 let err = Depth::from_bits(bits).expect_err("expected rejection");
172 assert!(
173 err.to_string().contains(&bits.to_string()),
174 "error should name the depth, got {err}"
175 );
176 }
177 }
178
179 #[test]
180 fn depth_properties_match_the_format() {
181 assert_eq!(Depth::Eight.bytes_per_sample(), 1);
182 assert_eq!(Depth::Ten.bytes_per_sample(), 2);
183 assert_eq!(Depth::Twelve.bytes_per_sample(), 2);
184
185 assert_eq!(Depth::Eight.max_value(), 255.0);
186 assert_eq!(Depth::Ten.max_value(), 1023.0);
187 assert_eq!(Depth::Twelve.max_value(), 4095.0);
188
189 assert_eq!(Depth::Eight.neutral_chroma(), 128);
190 assert_eq!(Depth::Ten.neutral_chroma(), 512);
191 assert_eq!(Depth::Twelve.neutral_chroma(), 2048);
192 }
193
194 #[test]
207 fn normalized_scale_is_identical_across_depths() {
208 const TOL: f32 = 1.0 / 255.0;
210
211 let eight = normalize(&[16, 235], Depth::Eight);
212 let ten = normalize(&[64, 940], Depth::Ten);
213 let twelve = normalize(&[256, 3760], Depth::Twelve);
214
215 for (a, b) in eight.iter().zip(ten.iter()) {
216 assert!((a - b).abs() < TOL, "8-bit {a} vs 10-bit {b}");
217 }
218 for (a, b) in eight.iter().zip(twelve.iter()) {
219 assert!((a - b).abs() < TOL, "8-bit {a} vs 12-bit {b}");
220 }
221 }
222
223 #[test]
224 fn normalization_round_trips_at_every_depth() {
225 for depth in [Depth::Eight, Depth::Ten, Depth::Twelve] {
226 let max = depth.max_value() as u16;
227 let original: Vec<u16> = vec![0, 1, 16, 64, 128, 235, max / 2, max - 1, max];
228 let restored = denormalize(&normalize(&original, depth), depth);
229 assert_eq!(original, restored, "round trip failed at {depth:?}");
230 }
231 }
232
233 #[test]
234 fn denormalize_clamps_out_of_range_input() {
235 let out = denormalize(&[-0.5, 0.0, 1.0, 1.5], Depth::Ten);
236 assert_eq!(out, vec![0, 0, 1023, 1023]);
237 }
238}