use glam::Vec3;
use indicatrix::renderer::denoise::{AtrousDenoiser, AtrousParams, GBuffers, taper_strength};
struct Xorshift32(u32);
impl Xorshift32 {
const fn new(seed: u32) -> Self {
Self(if seed == 0 { 0xdead_beef } else { seed })
}
const fn next_u32(&mut self) -> u32 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
self.0 = x;
x
}
fn next_f32(&mut self) -> f32 {
(f64::from(self.next_u32()) / f64::from(u32::MAX)) as f32
}
fn next_signed(&mut self) -> f32 {
self.next_f32().mul_add(2.0, -1.0)
}
}
fn mse(a: &[Vec3], b: &[Vec3]) -> f64 {
assert_eq!(a.len(), b.len());
let mut sum = 0.0f64;
for (x, y) in a.iter().zip(b.iter()) {
let d = *x - *y;
sum += f64::from(d.length_squared());
}
sum / a.len() as f64
}
fn all_finite(buf: &[Vec3]) -> bool {
buf.iter()
.all(|v| v.x.is_finite() && v.y.is_finite() && v.z.is_finite())
}
#[test]
fn filtering_reduces_error_against_ground_truth() {
let width = 48;
let height = 48;
let len = width * height;
let color_a = Vec3::new(0.6, 0.5, 0.2);
let color_b = Vec3::new(0.1, 0.3, 0.7);
let mut ground_truth = vec![Vec3::ZERO; len];
let mut facet_id = vec![0i32; len];
for y in 0..height {
for x in 0..width {
let idx = y * width + x;
if x < width / 2 {
ground_truth[idx] = color_a;
facet_id[idx] = 0;
} else {
ground_truth[idx] = color_b;
facet_id[idx] = 1;
}
}
}
let depth = vec![1.0f32; len];
let normal = vec![Vec3::Z; len];
let mut rng = Xorshift32::new(12345);
let mut noisy = ground_truth.clone();
for c in &mut noisy {
let n = Vec3::new(rng.next_signed(), rng.next_signed(), rng.next_signed()) * 0.4;
*c = (*c + n).max(Vec3::ZERO);
}
let inputs = GBuffers {
color: &noisy,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 1,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
let mse_before = mse(&noisy, &ground_truth);
let mse_after = mse(&filtered, &ground_truth);
assert!(
mse_after < mse_before * 0.5,
"filtering should substantially reduce error against ground truth: mse_before={mse_before:.6}, mse_after={mse_after:.6}"
);
}
#[test]
fn facet_edges_survive_filtering() {
let width = 64;
let height = 32;
let len = width * height;
let split = width / 2;
let color_a = Vec3::new(0.9, 0.05, 0.05);
let color_b = Vec3::new(0.05, 0.05, 0.9);
let mut color = vec![Vec3::ZERO; len];
let mut facet_id = vec![0i32; len];
for y in 0..height {
for x in 0..width {
let idx = y * width + x;
if x < split {
color[idx] = color_a;
facet_id[idx] = 7;
} else {
color[idx] = color_b;
facet_id[idx] = 42;
}
}
}
let depth = vec![1.0f32; len];
let normal = vec![Vec3::Z; len];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 1,
};
let mut denoiser = AtrousDenoiser::new();
let params = AtrousParams {
num_passes: 5,
..AtrousParams::default()
};
let filtered = denoiser.denoise(&inputs, ¶ms);
let y = height / 2;
let left_idx = y * width + (split - 1);
let right_idx = y * width + split;
let original_jump = (color[right_idx] - color[left_idx]).length();
let filtered_jump = (filtered[right_idx] - filtered[left_idx]).length();
assert!(
filtered_jump > original_jump * 0.95,
"facet boundary must stay sharp: original_jump={original_jump:.4}, filtered_jump={filtered_jump:.4}"
);
}
#[test]
fn high_sample_count_approaches_identity() {
let width = 16;
let height = 16;
let len = width * height;
let mut rng = Xorshift32::new(999);
let mut color = vec![Vec3::ZERO; len];
let mut facet_id = vec![0i32; len];
for (i, c) in color.iter_mut().enumerate() {
*c = Vec3::new(rng.next_f32(), rng.next_f32(), rng.next_f32());
facet_id[i] = (i % 3) as i32;
}
let depth = vec![1.0f32; len];
let normal = vec![Vec3::Z; len];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 100_000,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
for (f, c) in filtered.iter().zip(color.iter()) {
let d = (*f - *c).length();
assert!(
d < 1.0e-5,
"expected near-identity at high spp, got diff {d}"
);
}
}
#[test]
fn taper_strength_decreases_monotonically_with_spp() {
let n0 = 4.0;
let mut prev = taper_strength(0, n0);
assert!(
(prev - 1.0).abs() < 1.0e-6,
"taper at spp=0 should be exactly 1.0"
);
for spp in [1u32, 4, 16, 64, 256, 1024, 100_000] {
let t = taper_strength(spp, n0);
assert!(
t < prev,
"taper must strictly decrease as spp grows (spp={spp})"
);
assert!(t > 0.0, "taper must stay positive (spp={spp})");
prev = t;
}
}
#[test]
fn uniform_image_is_unchanged() {
let width = 20;
let height = 20;
let len = width * height;
let constant = Vec3::new(0.37, 0.61, 0.14);
let mut rng = Xorshift32::new(42);
let mut facet_id = vec![0i32; len];
let mut depth = vec![0.0f32; len];
let mut normal = vec![Vec3::Z; len];
for i in 0..len {
facet_id[i] = (rng.next_u32() % 4) as i32;
depth[i] = rng.next_f32();
let jitter = Vec3::new(rng.next_signed(), rng.next_signed(), 1.0).normalize();
normal[i] = jitter;
}
let color = vec![constant; len];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 1,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
for v in &filtered {
let d = (*v - constant).length();
assert!(
d < 1.0e-4,
"uniform image must be unchanged by filtering (drift {d})"
);
}
}
#[test]
fn filtering_is_deterministic() {
let width = 24;
let height = 24;
let len = width * height;
let mut rng = Xorshift32::new(7);
let mut color = vec![Vec3::ZERO; len];
let mut facet_id = vec![0i32; len];
let mut normal = vec![Vec3::Z; len];
let mut depth = vec![0.0f32; len];
for i in 0..len {
color[i] = Vec3::new(rng.next_f32(), rng.next_f32(), rng.next_f32());
facet_id[i] = (rng.next_u32() % 5) as i32;
depth[i] = rng.next_f32();
normal[i] = Vec3::new(rng.next_signed() * 0.1, rng.next_signed() * 0.1, 1.0).normalize();
}
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 3,
};
let params = AtrousParams::default();
let mut denoiser_a = AtrousDenoiser::new();
let out_a = denoiser_a.denoise(&inputs, ¶ms);
let mut denoiser_b = AtrousDenoiser::new();
let out_b = denoiser_b.denoise(&inputs, ¶ms);
assert_eq!(out_a.len(), out_b.len());
for (a, b) in out_a.iter().zip(out_b.iter()) {
assert_eq!(
a.x.to_bits(),
b.x.to_bits(),
"filtering must be bit-exact deterministic"
);
assert_eq!(a.y.to_bits(), b.y.to_bits());
assert_eq!(a.z.to_bits(), b.z.to_bits());
}
let out_c = denoiser_a.denoise(&inputs, ¶ms);
for (a, c) in out_a.iter().zip(out_c.iter()) {
assert_eq!(a.x.to_bits(), c.x.to_bits());
assert_eq!(a.y.to_bits(), c.y.to_bits());
assert_eq!(a.z.to_bits(), c.z.to_bits());
}
}
#[test]
fn one_by_one_image_does_not_panic() {
let color = [Vec3::new(0.3, 0.6, 0.9)];
let depth = [1.0f32];
let normal = [Vec3::Z];
let facet_id = [0i32];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width: 1,
height: 1,
spp: 2,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
assert_eq!(filtered.len(), 1);
assert!(all_finite(&filtered));
let d = (filtered[0] - color[0]).length();
assert!(
d < 1.0e-4,
"single-pixel image should trivially reproduce its own colour"
);
}
#[test]
fn zero_by_zero_image_does_not_panic() {
let color: [Vec3; 0] = [];
let depth: [f32; 0] = [];
let normal: [Vec3; 0] = [];
let facet_id: [i32; 0] = [];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width: 0,
height: 0,
spp: 0,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
assert_eq!(filtered, [] as [Vec3; 0]);
}
#[test]
fn zero_samples_does_not_panic_or_produce_nan() {
let width = 8;
let height = 8;
let len = width * height;
let mut rng = Xorshift32::new(2024);
let color: Vec<Vec3> = (0..len)
.map(|_| Vec3::new(rng.next_f32(), rng.next_f32(), rng.next_f32()))
.collect();
let depth = vec![0.5f32; len];
let normal = vec![Vec3::Z; len];
let facet_id = vec![0i32; len];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 0,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
assert_eq!(filtered.len(), len);
assert!(all_finite(&filtered));
}
#[test]
fn all_black_buffer_does_not_panic_or_produce_nan() {
let width = 10;
let height = 10;
let len = width * height;
let color = vec![Vec3::ZERO; len];
let depth = vec![0.0f32; len];
let normal = vec![Vec3::ZERO; len]; let facet_id = vec![0i32; len];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 5,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
assert_eq!(filtered.len(), len);
assert!(all_finite(&filtered));
for v in &filtered {
assert_eq!(*v, Vec3::ZERO);
}
}
#[test]
fn mismatched_buffer_lengths_degrade_gracefully() {
let width = 6;
let height = 6;
let len = width * height;
let color = vec![Vec3::new(1.0, 1.0, 1.0); len];
let depth = vec![0.0f32; len / 2];
let normal = vec![Vec3::Z; len / 2];
let facet_id = vec![0i32; len / 2];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 1,
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, &AtrousParams::default());
assert_eq!(filtered.len(), len);
assert!(all_finite(&filtered));
}
#[test]
fn parallel_denoise_is_bit_identical_across_thread_counts() {
let width = 173;
let height = 101;
let len = width * height;
let mut rng = Xorshift32::new(0x5eed_c0de);
let mut color = vec![Vec3::ZERO; len];
let mut depth = vec![0.0f32; len];
let mut normal = vec![Vec3::Z; len];
let mut facet_id = vec![0i32; len];
for y in 0..height {
for x in 0..width {
let idx = y * width + x;
facet_id[idx] = ((x / 6 + y / 4) % 7) as i32;
depth[idx] = rng.next_f32() * 3.0;
normal[idx] =
Vec3::new(rng.next_signed() * 0.4, rng.next_signed() * 0.4, 1.0).normalize();
color[idx] = Vec3::new(rng.next_f32(), rng.next_f32(), rng.next_f32());
}
}
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 1,
};
let params = AtrousParams::default();
let mut reference_denoiser = AtrousDenoiser::new();
let mut reference = Vec::new();
reference_denoiser.denoise_into_with_threads(&inputs, ¶ms, &mut reference, 1);
for threads in [2usize, 8, 16] {
let mut denoiser = AtrousDenoiser::new();
let mut out = Vec::new();
denoiser.denoise_into_with_threads(&inputs, ¶ms, &mut out, threads);
assert_eq!(out.len(), reference.len());
for (i, (a, b)) in reference.iter().zip(out.iter()).enumerate() {
assert_eq!(
a.x.to_bits(),
b.x.to_bits(),
"threads={threads}: pixel {i} x component differs"
);
assert_eq!(
a.y.to_bits(),
b.y.to_bits(),
"threads={threads}: pixel {i} y component differs"
);
assert_eq!(
a.z.to_bits(),
b.z.to_bits(),
"threads={threads}: pixel {i} z component differs"
);
}
}
let mut auto_denoiser = AtrousDenoiser::new();
let mut auto_out = Vec::new();
auto_denoiser.denoise_into(&inputs, ¶ms, &mut auto_out);
for (a, b) in reference.iter().zip(auto_out.iter()) {
assert_eq!(a.x.to_bits(), b.x.to_bits());
assert_eq!(a.y.to_bits(), b.y.to_bits());
assert_eq!(a.z.to_bits(), b.z.to_bits());
}
}
#[test]
fn zero_passes_param_does_not_panic() {
let width = 4;
let height = 4;
let len = width * height;
let color = vec![Vec3::new(0.2, 0.4, 0.6); len];
let depth = vec![1.0f32; len];
let normal = vec![Vec3::Z; len];
let facet_id = vec![0i32; len];
let inputs = GBuffers {
color: &color,
depth: &depth,
normal: &normal,
facet_id: &facet_id,
width,
height,
spp: 1,
};
let params = AtrousParams {
num_passes: 0,
..AtrousParams::default()
};
let mut denoiser = AtrousDenoiser::new();
let filtered = denoiser.denoise(&inputs, ¶ms);
assert_eq!(filtered.len(), len);
assert!(all_finite(&filtered));
}