#![cfg(all(feature = "decode", feature = "encode"))]
use rusty_dds::*;
fn xorshift(state: &mut u64) -> u64 {
*state ^= *state << 13;
*state ^= *state >> 7;
*state ^= *state << 17;
*state
}
fn log_psnr(a: &[f32], b: &[f32]) -> f64 {
assert_eq!(a.len(), b.len());
let mut sse = 0f64;
let mut n = 0usize;
for (&x, &y) in a.iter().zip(b) {
let lx = (x.max(1e-6) as f64).log2();
let ly = (y.max(1e-6) as f64).log2();
sse += (lx - ly) * (lx - ly);
n += 1;
}
if sse == 0.0 {
return f64::INFINITY;
}
let rmse = (sse / n as f64).sqrt();
20.0 * (36.0 / rmse).log10()
}
fn roundtrip(pixels: &[f32], w: u32, h: u32) -> Vec<f32> {
let dds = Dds::encode_bc6h_uf16(pixels, w, h).expect("encode");
assert_eq!(dds.get_dxgi_format(), Some(DxgiFormat::BC6H_UF16));
let img = dds
.decode_rgba_f32(SubresourceId::mip_layer(0, 0))
.expect("decode");
assert_eq!((img.width, img.height), (w, h));
img.pixels
}
#[test]
fn hdr_gradient_roundtrip_floor() {
let (w, h) = (64u32, 64u32);
let mut px = Vec::with_capacity((w * h * 4) as usize);
for y in 0..h {
for x in 0..w {
let t = (x as f32 + 1.0) / w as f32;
let s = (y as f32 + 1.0) / h as f32;
let base = 2f32.powf(t * 14.0 - 4.0); px.extend_from_slice(&[base, base * (0.5 + 0.5 * s), base * 0.25, 1.0]);
}
}
let out = roundtrip(&px, w, h);
let (a, b): (Vec<f32>, Vec<f32>) = (
px.chunks(4).flat_map(|c| c[..3].to_vec()).collect(),
out.chunks(4).flat_map(|c| c[..3].to_vec()).collect(),
);
let psnr = log_psnr(&a, &b);
assert!(psnr > 40.0, "gradient log-PSNR too low: {psnr:.2} dB");
}
#[test]
fn hdr_noise_roundtrip_floor() {
let (w, h) = (32u32, 32u32);
let mut state = 0xC0FFEE;
let mut px = Vec::with_capacity((w * h * 4) as usize);
for _ in 0..w * h {
let r = xorshift(&mut state);
let e0 = ((r & 0xFF) as f32 / 255.0) * 8.0 - 2.0;
let e1 = (((r >> 8) & 0xFF) as f32 / 255.0) * 8.0 - 2.0;
let e2 = (((r >> 16) & 0xFF) as f32 / 255.0) * 8.0 - 2.0;
px.extend_from_slice(&[2f32.powf(e0), 2f32.powf(e1), 2f32.powf(e2), 1.0]);
}
let out = roundtrip(&px, w, h);
let (a, b): (Vec<f32>, Vec<f32>) = (
px.chunks(4).flat_map(|c| c[..3].to_vec()).collect(),
out.chunks(4).flat_map(|c| c[..3].to_vec()).collect(),
);
let psnr = log_psnr(&a, &b);
assert!(psnr > 20.0, "noise log-PSNR too low: {psnr:.2} dB");
}
#[test]
fn flat_block_reconstructs_near_exact() {
for (v, floor) in [
(0.001f32, 0.02),
(0.5, 0.016),
(1.0, 0.016),
(37.5, 0.016),
(1000.0, 0.016),
] {
let px: Vec<f32> = (0..16).flat_map(|_| [v, v, v, 1.0]).collect();
let out = roundtrip(&px, 4, 4);
for c in out.chunks(4) {
for ch in 0..3 {
let rel = (c[ch] - v).abs() / v;
assert!(rel < floor, "flat {v}: got {} (rel {rel})", c[ch]);
}
}
}
}
#[test]
fn uf16_clamps_negative_nan_and_huge() {
let px: Vec<f32> = vec![
-5.0, f32::NAN, 1e30, 0.25, 0.0, 0.0, 0.0, 1.0,
]
.into_iter()
.chain(std::iter::repeat(0.0).take(14 * 4))
.collect();
let out = roundtrip(&px, 4, 4);
assert_eq!(out[0], 0.0, "negative must clamp to 0");
assert_eq!(out[1], 0.0, "NaN must clamp to 0");
assert!(
out[2] > 60000.0 && out[2].is_finite(),
"huge must clamp near 65504, got {}",
out[2]
);
}
#[test]
fn npot_and_truncation_fail_closed() {
let px: Vec<f32> = (0..10 * 6).flat_map(|i| [i as f32, 1.0, 2.0, 1.0]).collect();
let out = roundtrip(&px, 10, 6);
assert_eq!(out.len(), 10 * 6 * 4);
assert!(Dds::encode_bc6h_uf16(&px[..10], 10, 6).is_err());
assert!(Dds::encode_bc6h_uf16(&px, 0, 6).is_err());
}
fn read_radiance_hdr(path: &std::path::Path) -> Option<(usize, usize, Vec<f32>)> {
let data = std::fs::read(path).ok()?;
let mut pos = 0usize;
let mut line = || {
let start = pos;
while pos < data.len() && data[pos] != b'\n' {
pos += 1;
}
let s = std::str::from_utf8(&data[start..pos]).ok().map(|s| s.to_string());
pos += 1;
s
};
if !line()?.starts_with("#?") {
return None;
}
loop {
let l = line()?;
if l.is_empty() {
break;
}
}
let res = line()?;
let mut it = res.split_whitespace();
let (dy, h, dx, w) = (it.next()?, it.next()?, it.next()?, it.next()?);
if dy != "-Y" || dx != "+X" {
return None;
}
let h: usize = h.parse().ok()?;
let w: usize = w.parse().ok()?;
let mut out = vec![0f32; w * h * 4];
let mut rgbe_row = vec![0u8; w * 4];
for y in 0..h {
if pos + 4 > data.len() {
return None;
}
if data[pos] == 2
&& data[pos + 1] == 2
&& ((data[pos + 2] as usize) << 8 | data[pos + 3] as usize) == w
{
pos += 4;
for c in 0..4 {
let mut x = 0usize;
while x < w {
let n = data.get(pos).copied()? as usize;
pos += 1;
if n > 128 {
let v = data.get(pos).copied()?;
pos += 1;
for _ in 0..n - 128 {
rgbe_row[x * 4 + c] = v;
x += 1;
}
} else {
for _ in 0..n {
rgbe_row[x * 4 + c] = data.get(pos).copied()?;
pos += 1;
x += 1;
}
}
}
}
} else {
for x in 0..w {
rgbe_row[x * 4..x * 4 + 4].copy_from_slice(data.get(pos..pos + 4)?);
pos += 4;
}
}
for x in 0..w {
let e = rgbe_row[x * 4 + 3] as i32;
let scale = if e == 0 { 0.0 } else { (2f32).powi(e - 136) };
let d = (y * w + x) * 4;
out[d] = rgbe_row[x * 4] as f32 * scale;
out[d + 1] = rgbe_row[x * 4 + 1] as f32 * scale;
out[d + 2] = rgbe_row[x * 4 + 2] as f32 * scale;
out[d + 3] = 1.0;
}
}
Some((w, h, out))
}
#[test]
fn real_hdri_corpus_roundtrip_floor() {
let root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("corpus/raw_hdr");
if !root.exists() {
eprintln!("corpus/raw_hdr not fetched; skipping");
return;
}
let mut tested = 0;
for entry in std::fs::read_dir(&root).unwrap().flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("hdr") {
continue;
}
let Some((w, h, px)) = read_radiance_hdr(&path) else {
panic!("failed to parse {}", path.display());
};
let out = roundtrip(&px, w as u32, h as u32);
let (a, b): (Vec<f32>, Vec<f32>) = (
px.chunks(4).flat_map(|c| c[..3].to_vec()).collect(),
out.chunks(4).flat_map(|c| c[..3].to_vec()).collect(),
);
let psnr = log_psnr(&a, &b);
eprintln!(
"{}: {}x{} log-PSNR {:.2} dB",
path.file_name().unwrap().to_string_lossy(),
w,
h,
psnr
);
assert!(
psnr > 35.0,
"{}: real-HDRI log-PSNR too low: {psnr:.2} dB",
path.display()
);
tested += 1;
}
assert!(tested >= 4, "expected the 4 fetched HDRIs, found {tested}");
}
#[test]
#[ignore]
fn bc6h_encode_speed_probe() {
let root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("corpus/raw_hdr");
if !root.exists() {
return;
}
for entry in std::fs::read_dir(&root).unwrap().flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("hdr") {
continue;
}
let (w, h, px) = read_radiance_hdr(&path).unwrap();
let mut best = u128::MAX;
for _ in 0..5 {
let t = std::time::Instant::now();
let dds = Dds::encode_bc6h_uf16(&px, w as u32, h as u32).unwrap();
std::hint::black_box(&dds.data);
best = best.min(t.elapsed().as_nanos());
}
let mpxs = (w * h) as f64 / (best as f64 / 1e9) / 1e6;
eprintln!(
"{}: {}x{} encode best {} ms ({:.1} Mpx/s)",
path.file_name().unwrap().to_string_lossy(),
w, h, best / 1_000_000, mpxs
);
}
}