use dsfb_computer_graphics::fast_path::{
run_fast_path_cpu, try_run_fast_path_gpu, FAST_PATH_K, FAST_PATH_LAMBDA,
};
fn uniform_rgb(w: usize, h: usize, r: f32, g: f32, b: f32) -> Vec<[f32; 3]> {
vec![[r, g, b]; w * h]
}
fn zero_history(n: usize) -> Vec<f32> {
vec![0.0f32; n]
}
#[test]
fn fast_path_cpu_is_deterministic() {
let (w, h) = (16, 16);
let n = w * h;
let cur = uniform_rgb(w, h, 0.6, 0.4, 0.3);
let hist = uniform_rgb(w, h, 0.5, 0.5, 0.5);
let res = zero_history(n);
let drift = zero_history(n);
let out_a =
run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false);
let out_b =
run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false);
assert_eq!(out_a.trust, out_b.trust, "trust must be deterministic");
assert_eq!(
out_a.residual_history_out, out_b.residual_history_out,
"residual history must be deterministic"
);
assert_eq!(
out_a.drift_history_out, out_b.drift_history_out,
"drift history must be deterministic"
);
}
#[test]
fn fast_path_cpu_trust_always_in_unit_interval() {
let (w, h) = (32, 32);
let n = w * h;
let cur = uniform_rgb(w, h, 1.0, 1.0, 1.0);
let hist = uniform_rgb(w, h, 0.0, 0.0, 0.0);
let res = zero_history(n);
let drift = zero_history(n);
let out = run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false);
for (i, &t) in out.trust.iter().enumerate() {
assert!(
(0.0..=1.0).contains(&t),
"trust[{i}] = {t} is outside [0, 1]"
);
}
}
#[test]
fn fast_path_cpu_local_agg_trust_always_in_unit_interval() {
let (w, h) = (16, 16);
let n = w * h;
let cur = uniform_rgb(w, h, 0.9, 0.1, 0.5);
let hist = uniform_rgb(w, h, 0.1, 0.9, 0.5);
let res = vec![0.3f32; n];
let drift = vec![-0.1f32; n];
let out = run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, true);
for (i, &t) in out.trust.iter().enumerate() {
assert!(
(0.0..=1.0).contains(&t),
"local-agg trust[{i}] = {t} is outside [0, 1]"
);
}
}
#[test]
fn fast_path_cpu_zero_residual_zero_history_yields_max_trust() {
let (w, h) = (8, 8);
let n = w * h;
let color = uniform_rgb(w, h, 0.5, 0.3, 0.7);
let res = zero_history(n);
let drift = zero_history(n);
let out = run_fast_path_cpu(
&color,
&color, &res,
&drift,
w,
h,
FAST_PATH_LAMBDA,
FAST_PATH_K,
false,
);
for (i, &t) in out.trust.iter().enumerate() {
assert!(
(t - 1.0f32).abs() < 1e-6,
"trust[{i}] should be 1.0 when residual=0, got {t}"
);
}
}
#[test]
fn fast_path_cpu_constant_residual_produces_zero_slew() {
let (w, h) = (4, 4);
let n = w * h;
let cur = uniform_rgb(w, h, 0.6, 0.6, 0.6);
let hist = uniform_rgb(w, h, 0.5, 0.5, 0.5);
let mut res_h = zero_history(n);
let mut drift_h = zero_history(n);
for _ in 0..6 {
let out = run_fast_path_cpu(
&cur,
&hist,
&res_h,
&drift_h,
w,
h,
FAST_PATH_LAMBDA,
FAST_PATH_K,
false,
);
res_h = out.residual_history_out;
drift_h = out.drift_history_out;
}
for (i, &d) in drift_h.iter().enumerate() {
assert!(
d.abs() < 1e-5,
"drift[{i}] = {d} should be ~0 after constant-residual convergence"
);
}
}
#[test]
fn fast_path_cpu_local_agg_equals_scalar_for_uniform_residual() {
let (w, h) = (8, 8);
let n = w * h;
let cur = uniform_rgb(w, h, 0.7, 0.4, 0.2);
let hist = uniform_rgb(w, h, 0.5, 0.5, 0.5);
let res = zero_history(n);
let drift = zero_history(n);
let out_no_agg =
run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false);
let out_agg =
run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, true);
for i in 0..n {
let diff = (out_no_agg.trust[i] - out_agg.trust[i]).abs();
assert!(
diff < 1e-5,
"trust[{i}]: scalar={} agg={} diff={diff} — should match for uniform inputs",
out_no_agg.trust[i],
out_agg.trust[i]
);
}
}
#[test]
fn fast_path_cpu_history_buffers_update_to_r_t_and_d_t() {
let (w, h) = (1, 1);
let n = 1;
let cur = vec![[0.8f32, 0.5, 0.5]];
let hist = vec![[0.5f32, 0.5, 0.5]];
let res = zero_history(n); let drift = zero_history(n);
let out = run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false);
let expected_r = 0.1f32;
let expected_d = expected_r - 0.0;
assert!((out.residual_history_out[0] - expected_r).abs() < 1e-6, "r_t mismatch");
assert!((out.drift_history_out[0] - expected_d).abs() < 1e-6, "d_t mismatch");
}
#[test]
fn fast_path_gpu_matches_cpu_within_tolerance_if_available() {
let (w, h) = (8, 8);
let n = w * h;
let cur = uniform_rgb(w, h, 0.6, 0.4, 0.3);
let hist = uniform_rgb(w, h, 0.5, 0.5, 0.5);
let res = zero_history(n);
let drift = zero_history(n);
let cpu_out = run_fast_path_cpu(
&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false,
);
match try_run_fast_path_gpu(
&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false,
) {
Ok(Some(gpu_out)) => {
let tolerance = 1e-4f32;
for i in 0..n {
let diff = (cpu_out.trust[i] - gpu_out.trust[i]).abs();
assert!(
diff <= tolerance,
"trust[{i}]: cpu={} gpu={} diff={diff} exceeds tolerance {tolerance}",
cpu_out.trust[i],
gpu_out.trust[i]
);
}
}
Ok(None) => {
eprintln!("fast_path_gpu_matches_cpu: no wgpu adapter, test skipped");
}
Err(e) => panic!("GPU fast-path error: {e}"),
}
}
#[test]
fn fast_path_cpu_pre_allocates_before_inner_loop() {
let (w, h) = (64, 64);
let n = w * h;
let cur: Vec<[f32; 3]> = (0..n).map(|i| [i as f32 / n as f32, 0.3, 0.7]).collect();
let hist = uniform_rgb(w, h, 0.4, 0.5, 0.6);
let res = vec![0.05f32; n];
let drift = vec![0.02f32; n];
let out = run_fast_path_cpu(&cur, &hist, &res, &drift, w, h, FAST_PATH_LAMBDA, FAST_PATH_K, false);
assert_eq!(out.trust.len(), n);
assert_eq!(out.residual_history_out.len(), n);
assert_eq!(out.drift_history_out.len(), n);
}