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(crate) use pending::start_readback;
59pub use pending::{Pending, TryWait};
60pub use prefilter::{DEFAULT_PILOT_STRENGTH_SCALE, PrefilterMode, parse_prefilter};
61
62pub const BLOCK_X: u32 = 32;
64pub const BLOCK_Y: u32 = 8;
66
67pub const BLOCK_X_THIN: u32 = 32;
74pub const BLOCK_Y_THIN: u32 = 16;
75
76pub(crate) const MAX_GRID_1D: u32 = 65535;
79
80pub(crate) const BLOCK_1D: u32 = 256;
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum Depth {
89 Eight,
90 Ten,
91 Twelve,
92}
93
94#[derive(Debug, thiserror::Error)]
96#[error("unsupported bit depth {0}, av-denoise supports 8, 10, and 12-bit")]
97pub struct UnsupportedDepthError(pub usize);
98
99impl Depth {
100 pub fn from_bits(bits: usize) -> Result<Self, UnsupportedDepthError> {
102 match bits {
103 8 => Ok(Depth::Eight),
104 10 => Ok(Depth::Ten),
105 12 => Ok(Depth::Twelve),
106 other => Err(UnsupportedDepthError(other)),
107 }
108 }
109
110 pub fn bits(self) -> usize {
112 match self {
113 Depth::Eight => 8,
114 Depth::Ten => 10,
115 Depth::Twelve => 12,
116 }
117 }
118
119 pub fn bytes_per_sample(self) -> usize {
123 match self {
124 Depth::Eight => 1,
125 Depth::Ten | Depth::Twelve => 2,
126 }
127 }
128
129 pub fn max_value(self) -> f32 {
132 ((1u32 << self.bits()) - 1) as f32
133 }
134
135 pub fn neutral_chroma(self) -> u16 {
137 1 << (self.bits() - 1)
138 }
139
140 pub fn wire_pack(self) -> WirePack {
143 WirePack {
144 max: self.max_value(),
145 samples_per_word: 4 / self.bytes_per_sample() as u32,
146 }
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq)]
159pub struct WirePack {
160 max: f32,
161 samples_per_word: u32,
162}
163
164impl WirePack {
165 pub fn max(self) -> f32 {
167 self.max
168 }
169
170 pub fn samples_per_word(self) -> u32 {
172 self.samples_per_word
173 }
174}
175
176pub fn normalize(input: &[u16], depth: Depth) -> Vec<f32> {
178 let max = depth.max_value();
179 input.iter().map(|&v| v as f32 / max).collect()
180}
181
182pub fn denormalize(input: &[f32], depth: Depth) -> Vec<u16> {
186 let max = depth.max_value();
187 input
188 .iter()
189 .map(|&v| (v * max).round().clamp(0.0, max) as u16)
190 .collect()
191}
192
193#[cfg(test)]
194mod depth_tests {
195 use super::*;
196
197 #[test]
198 fn from_bits_accepts_supported_depths() {
199 assert_eq!(Depth::from_bits(8).unwrap(), Depth::Eight);
200 assert_eq!(Depth::from_bits(10).unwrap(), Depth::Ten);
201 assert_eq!(Depth::from_bits(12).unwrap(), Depth::Twelve);
202 }
203
204 #[test]
205 fn from_bits_rejects_unsupported_depths() {
206 for bits in [0, 9, 14, 16] {
207 let err = Depth::from_bits(bits).expect_err("expected rejection");
208 assert!(
209 err.to_string().contains(&bits.to_string()),
210 "error should name the depth, got {err}"
211 );
212 }
213 }
214
215 #[test]
216 fn depth_properties_match_the_format() {
217 assert_eq!(Depth::Eight.bytes_per_sample(), 1);
218 assert_eq!(Depth::Ten.bytes_per_sample(), 2);
219 assert_eq!(Depth::Twelve.bytes_per_sample(), 2);
220
221 assert_eq!(Depth::Eight.max_value(), 255.0);
222 assert_eq!(Depth::Ten.max_value(), 1023.0);
223 assert_eq!(Depth::Twelve.max_value(), 4095.0);
224
225 assert_eq!(Depth::Eight.neutral_chroma(), 128);
226 assert_eq!(Depth::Ten.neutral_chroma(), 512);
227 assert_eq!(Depth::Twelve.neutral_chroma(), 2048);
228 }
229
230 #[test]
243 fn normalized_scale_is_identical_across_depths() {
244 const TOL: f32 = 1.0 / 255.0;
246
247 let eight = normalize(&[16, 235], Depth::Eight);
248 let ten = normalize(&[64, 940], Depth::Ten);
249 let twelve = normalize(&[256, 3760], Depth::Twelve);
250
251 for (a, b) in eight.iter().zip(ten.iter()) {
252 assert!((a - b).abs() < TOL, "8-bit {a} vs 10-bit {b}");
253 }
254 for (a, b) in eight.iter().zip(twelve.iter()) {
255 assert!((a - b).abs() < TOL, "8-bit {a} vs 12-bit {b}");
256 }
257 }
258
259 #[test]
260 fn normalization_round_trips_at_every_depth() {
261 for depth in [Depth::Eight, Depth::Ten, Depth::Twelve] {
262 let max = depth.max_value() as u16;
263 let original: Vec<u16> = vec![0, 1, 16, 64, 128, 235, max / 2, max - 1, max];
264 let restored = denormalize(&normalize(&original, depth), depth);
265 assert_eq!(original, restored, "round trip failed at {depth:?}");
266 }
267 }
268
269 #[test]
270 fn denormalize_clamps_out_of_range_input() {
271 let out = denormalize(&[-0.5, 0.0, 1.0, 1.5], Depth::Ten);
272 assert_eq!(out, vec![0, 0, 1023, 1023]);
273 }
274}