use crate::error::Error;
#[cfg(test)]
pub fn downsample_rgba8(
src: &[u8],
width: u32,
height: u32,
depth: u32,
) -> Result<Vec<u8>, Error> {
let mut out = Vec::new();
downsample_rgba8_into(src, width, height, depth, &mut out)?;
Ok(out)
}
pub fn downsample_rgba8_into(
src: &[u8],
width: u32,
height: u32,
depth: u32,
out_buf: &mut Vec<u8>,
) -> Result<(), Error> {
let dw = (width / 2).max(1);
let dh = (height / 2).max(1);
let dd = (depth / 2).max(1);
let n = (dw * dh * dd * 4) as usize;
if out_buf.len() >= n {
out_buf.truncate(n);
} else {
out_buf.resize(n, 0);
}
let out: &mut [u8] = out_buf;
let sw = width as usize;
let sh = height as usize;
let sd = depth as usize;
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if sd == 1
&& sw >= 2
&& sh >= 2
&& src.len() >= sw * sh * 4
&& crate::swizzle::has_ssse3()
{
let dh_us = sh / 2;
if super::blocks::simd_avx2() {
unsafe { downsample_2d_rows_avx2(src, sw, 0, dh_us, out) };
return Ok(());
}
unsafe { downsample_2d_rows_ssse3(src, sw, 0, dh_us, out) };
return Ok(());
}
if sd == 1 && sw >= 2 && sh >= 2 {
for y in 0..dh as usize {
let r0 = (y * 2) * sw * 4;
let r1 = r0 + sw * 4;
let orow = y * dw as usize * 4;
for x in 0..dw as usize {
let i0 = r0 + x * 8;
let i1 = r1 + x * 8;
let a = u64::from_le_bytes(src[i0..i0 + 8].try_into().unwrap());
let b = u64::from_le_bytes(src[i1..i1 + 8].try_into().unwrap());
const M: u64 = 0x00FF_00FF_00FF_00FF;
let lo = (a & M) + (b & M);
let hi = ((a >> 8) & M) + ((b >> 8) & M);
let lo = (lo & 0xFFFF_FFFF) + (lo >> 32);
let hi = (hi & 0xFFFF_FFFF) + (hi >> 32);
let r = (((lo & 0xFFFF) as u32 + 2) >> 2) as u8;
let bch = ((((lo >> 16) & 0xFFFF) as u32 + 2) >> 2) as u8;
let g = (((hi & 0xFFFF) as u32 + 2) >> 2) as u8;
let al = ((((hi >> 16) & 0xFFFF) as u32 + 2) >> 2) as u8;
let o = orow + x * 4;
let w32 = u32::from_le_bytes([r, g, bch, al]);
out[o..o + 4].copy_from_slice(&w32.to_le_bytes());
}
}
return Ok(());
}
if sd == 1 && (sw == 1) != (sh == 1) {
let l = sw.max(sh); let dl = l / 2;
for i in 0..dl {
let s8 = u64::from_le_bytes(src[i * 8..i * 8 + 8].try_into().unwrap());
let a = (s8 & 0xFFFF_FFFF) as u32;
let b = (s8 >> 32) as u32;
let w32 = (a | b) - (((a ^ b) >> 1) & 0x7F7F_7F7F);
out[i * 4..i * 4 + 4].copy_from_slice(&w32.to_le_bytes());
}
return Ok(());
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if sd >= 2
&& sw >= 2
&& sh >= 2
&& src.len() >= sw * sh * sd * 4
&& crate::swizzle::has_ssse3()
{
unsafe { downsample_3d_ssse3(src, sw, sh, sd, out) };
return Ok(());
}
for z in 0..dd as usize {
for y in 0..dh as usize {
for x in 0..dw as usize {
let x0 = (x * 2).min(sw - 1);
let y0 = (y * 2).min(sh - 1);
let z0 = (z * 2).min(sd - 1);
let x1 = (x0 + 1).min(sw - 1);
let y1 = (y0 + 1).min(sh - 1);
let z1 = (z0 + 1).min(sd - 1);
let mut acc = [0u32; 4];
let mut n = 0u32;
for zz in z0..=z1 {
for yy in y0..=y1 {
for xx in x0..=x1 {
let i = ((zz * sh + yy) * sw + xx) * 4;
for c in 0..4 {
acc[c] += src[i + c] as u32;
}
n += 1;
}
}
}
let o = ((z * dh as usize + y) * dw as usize + x) * 4;
let sh = n.trailing_zeros();
for c in 0..4 {
out[o + c] = ((acc[c] + (n >> 1)) >> sh) as u8;
}
}
}
}
Ok(())
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "ssse3")]
unsafe fn downsample_2d_rows_ssse3(src: &[u8], sw: usize, y0: usize, y1: usize, out: &mut [u8]) {
use std::arch::x86_64::*;
let dw = sw / 2;
let sp = src.as_ptr();
let dp = out.as_mut_ptr();
let sel = _mm_setr_epi8(0, 4, 1, 5, 2, 6, 3, 7, 8, 12, 9, 13, 10, 14, 11, 15);
let ones = _mm_set1_epi8(1);
let two = _mm_set1_epi16(2);
let pairs = dw / 2;
for y in y0..y1 {
let r0 = sp.add((y * 2) * sw * 4);
let r1 = sp.add((y * 2 + 1) * sw * 4);
let orow = dp.add((y - y0) * dw * 4);
for p in 0..pairs {
let a = _mm_loadu_si128(r0.add(p * 16) as *const __m128i);
let b = _mm_loadu_si128(r1.add(p * 16) as *const __m128i);
let ha = _mm_maddubs_epi16(_mm_shuffle_epi8(a, sel), ones);
let hb = _mm_maddubs_epi16(_mm_shuffle_epi8(b, sel), ones);
let s = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(ha, hb), two), 2);
let packed = _mm_packus_epi16(s, s);
_mm_storel_epi64(orow.add(p * 8) as *mut __m128i, packed);
}
if dw % 2 == 1 {
let x0 = (dw - 1) * 2;
let i0 = x0 * 4;
let i1 = i0 + 4;
for c in 0..4 {
let sum = *r0.add(i0 + c) as u32
+ *r0.add(i1 + c) as u32
+ *r1.add(i0 + c) as u32
+ *r1.add(i1 + c) as u32;
*orow.add((dw - 1) * 4 + c) = ((sum + 2) >> 2) as u8;
}
}
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn downsample_2d_rows_avx2(src: &[u8], sw: usize, y0: usize, y1: usize, out: &mut [u8]) {
use std::arch::x86_64::*;
let dw = sw / 2;
let sp = src.as_ptr();
let dp = out.as_mut_ptr();
let sel128 = _mm_setr_epi8(0, 4, 1, 5, 2, 6, 3, 7, 8, 12, 9, 13, 10, 14, 11, 15);
let sel = _mm256_broadcastsi128_si256(sel128);
let ones = _mm256_set1_epi8(1);
let two = _mm256_set1_epi16(2);
let ones128 = _mm_set1_epi8(1);
let two128 = _mm_set1_epi16(2);
let quads = dw / 4;
for y in y0..y1 {
let r0 = sp.add((y * 2) * sw * 4);
let r1 = sp.add((y * 2 + 1) * sw * 4);
let orow = dp.add((y - y0) * dw * 4);
for q in 0..quads {
let a = _mm256_loadu_si256(r0.add(q * 32) as *const __m256i);
let b = _mm256_loadu_si256(r1.add(q * 32) as *const __m256i);
let ha = _mm256_maddubs_epi16(_mm256_shuffle_epi8(a, sel), ones);
let hb = _mm256_maddubs_epi16(_mm256_shuffle_epi8(b, sel), ones);
let s = _mm256_srli_epi16(_mm256_add_epi16(_mm256_add_epi16(ha, hb), two), 2);
let packed = _mm256_packus_epi16(s, s);
let ordered = _mm256_permute4x64_epi64::<0b0000_1000>(packed);
_mm_storeu_si128(
orow.add(q * 16) as *mut __m128i,
_mm256_castsi256_si128(ordered),
);
}
let rem = dw - quads * 4;
if rem >= 2 {
let p = quads * 2;
let a = _mm_loadu_si128(r0.add(p * 16) as *const __m128i);
let b = _mm_loadu_si128(r1.add(p * 16) as *const __m128i);
let ha = _mm_maddubs_epi16(_mm_shuffle_epi8(a, sel128), ones128);
let hb = _mm_maddubs_epi16(_mm_shuffle_epi8(b, sel128), ones128);
let s = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(ha, hb), two128), 2);
let packed = _mm_packus_epi16(s, s);
_mm_storel_epi64(orow.add(p * 8) as *mut __m128i, packed);
}
if dw % 2 == 1 {
let i0 = (dw - 1) * 2 * 4;
let i1 = i0 + 4;
for c in 0..4 {
let sum = *r0.add(i0 + c) as u32
+ *r0.add(i1 + c) as u32
+ *r1.add(i0 + c) as u32
+ *r1.add(i1 + c) as u32;
*orow.add((dw - 1) * 4 + c) = ((sum + 2) >> 2) as u8;
}
}
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "ssse3")]
unsafe fn downsample_3d_ssse3(src: &[u8], sw: usize, sh: usize, sd: usize, out: &mut [u8]) {
use std::arch::x86_64::*;
let dw = sw / 2;
let dh = sh / 2;
let dd = sd / 2;
let sp = src.as_ptr();
let dp = out.as_mut_ptr();
let sel = _mm_setr_epi8(0, 4, 1, 5, 2, 6, 3, 7, 8, 12, 9, 13, 10, 14, 11, 15);
let ones = _mm_set1_epi8(1);
let four = _mm_set1_epi16(4);
let slice = sw * sh * 4;
let pairs = dw / 2;
for z in 0..dd {
let sa = sp.add((z * 2) * slice);
let sb = sp.add((z * 2 + 1) * slice);
let dz = dp.add(z * dh * dw * 4);
for y in 0..dh {
let a0 = sa.add((y * 2) * sw * 4);
let a1 = sa.add((y * 2 + 1) * sw * 4);
let b0 = sb.add((y * 2) * sw * 4);
let b1 = sb.add((y * 2 + 1) * sw * 4);
let orow = dz.add(y * dw * 4);
for p in 0..pairs {
let m = |r: *const u8| {
_mm_maddubs_epi16(
_mm_shuffle_epi8(
_mm_loadu_si128(r.add(p * 16) as *const __m128i),
sel,
),
ones,
)
};
let s = _mm_add_epi16(_mm_add_epi16(m(a0), m(a1)), _mm_add_epi16(m(b0), m(b1)));
let s = _mm_srli_epi16(_mm_add_epi16(s, four), 3);
let packed = _mm_packus_epi16(s, s);
_mm_storel_epi64(orow.add(p * 8) as *mut __m128i, packed);
}
if dw % 2 == 1 {
let i0 = (dw - 1) * 2 * 4;
let i1 = i0 + 4;
for c in 0..4 {
let sum = *a0.add(i0 + c) as u32
+ *a0.add(i1 + c) as u32
+ *a1.add(i0 + c) as u32
+ *a1.add(i1 + c) as u32
+ *b0.add(i0 + c) as u32
+ *b0.add(i1 + c) as u32
+ *b1.add(i0 + c) as u32
+ *b1.add(i1 + c) as u32;
*orow.add((dw - 1) * 4 + c) = ((sum + 4) >> 3) as u8;
}
}
}
}
}
#[cfg(test)]
mod tests {
fn downsample_reference(src: &[u8], width: u32, height: u32, depth: u32) -> Vec<u8> {
let dw = (width / 2).max(1) as usize;
let dh = (height / 2).max(1) as usize;
let dd = (depth / 2).max(1) as usize;
let (sw, sh, sd) = (width as usize, height as usize, depth as usize);
let mut out = vec![0u8; dw * dh * dd * 4];
for z in 0..dd {
for y in 0..dh {
for x in 0..dw {
let x0 = (x * 2).min(sw - 1);
let y0 = (y * 2).min(sh - 1);
let z0 = (z * 2).min(sd - 1);
let x1 = (x0 + 1).min(sw - 1);
let y1 = (y0 + 1).min(sh - 1);
let z1 = (z0 + 1).min(sd - 1);
let mut acc = [0u32; 4];
let mut n = 0u32;
for zz in z0..=z1 {
for yy in y0..=y1 {
for xx in x0..=x1 {
let i = ((zz * sh + yy) * sw + xx) * 4;
for c in 0..4 {
acc[c] += src[i + c] as u32;
}
n += 1;
}
}
}
let o = ((z * dh + y) * dw + x) * 4;
for c in 0..4 {
out[o + c] = ((acc[c] + n / 2) / n) as u8;
}
}
}
}
out
}
#[test]
fn downsample_matches_reference() {
let mut state = 0x5eed_0f_1e_a1_b0c5u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..4_000u32 {
let (w, h, d) = match case % 8 {
0 => (2, 2, 1),
1 => (1, 7, 1),
2 => (16, 1, 1),
3 => (5, 6, 1),
4 => (6, 5, 1),
5 => (4, 4, 4),
6 => (3, 3, 2),
_ => (
(next() % 40 + 1) as u32,
(next() % 40 + 1) as u32,
(next() % 3 + 1) as u32,
),
};
let mut src = vec![0u8; (w * h * d * 4) as usize];
for b in src.iter_mut() {
*b = next() as u8;
}
let got = super::downsample_rgba8(&src, w, h, d).unwrap();
let want = downsample_reference(&src, w, h, d);
assert_eq!(got, want, "case {case}: {w}x{h}x{d}");
}
{
let (w, h) = (1101u32, 612u32);
let mut src = vec![0u8; (w * h * 4) as usize];
let mut s3 = 0x0bad_cafe_1234_5678u64;
for b in src.iter_mut() {
s3 ^= s3 << 13;
s3 ^= s3 >> 7;
s3 ^= s3 << 17;
*b = s3 as u8;
}
assert_eq!(
super::downsample_rgba8(&src, w, h, 1).unwrap(),
downsample_reference(&src, w, h, 1),
"parallel band path"
);
}
let (w, h) = (512u32, 384u32);
let mut src = vec![0u8; (w * h * 4) as usize];
let mut s2 = 0x9e37_79b9_7f4a_7c15u64;
for b in src.iter_mut() {
s2 ^= s2 << 13;
s2 ^= s2 >> 7;
s2 ^= s2 << 17;
*b = s2 as u8;
}
assert_eq!(
super::downsample_rgba8(&src, w, h, 1).unwrap(),
downsample_reference(&src, w, h, 1)
);
}
#[test]
fn into_chain_matches_allocating_chain() {
let mut state = 0xc4a1_9_0f_2b5du64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..400u32 {
let (mut w, mut h, mut d) = (
(next() % 65 + 1) as u32,
(next() % 65 + 1) as u32,
(next() % 4 + 1) as u32,
);
let mut src = vec![0u8; (w * h * d * 4) as usize];
for b in src.iter_mut() {
*b = next() as u8;
}
let mut alloc_cur = src.clone();
let mut cur_buf: Vec<u8> = Vec::new();
let mut next_buf: Vec<u8> = Vec::new();
for level in 0..6 {
let cur: &[u8] = if level == 0 { &src } else { &cur_buf };
assert_eq!(alloc_cur, cur, "case {case} level {level}");
if w == 1 && h == 1 && d == 1 {
break;
}
alloc_cur = super::downsample_rgba8(&alloc_cur, w, h, d).unwrap();
super::downsample_rgba8_into(cur, w, h, d, &mut next_buf).unwrap();
std::mem::swap(&mut cur_buf, &mut next_buf);
w = (w / 2).max(1);
h = (h / 2).max(1);
d = (d / 2).max(1);
}
}
}
#[test]
#[ignore]
fn probe_scalar_path_ab() {
const W: u32 = 1001;
const H: u32 = 1001;
let mut state = 0xdead_beef_cafe_f00du64;
let mut src = vec![0u8; (W * H * 4) as usize];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..15 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let ref_ns = best(&mut || {
downsample_reference(black_box(&src), W, H, 1)[123] as u64 + 1
});
let mut buf = Vec::new();
let ship_ns = best(&mut || {
super::downsample_rgba8_into(black_box(&src), W, H, 1, &mut buf).unwrap();
buf[123] as u64 + 1
});
let px = ((W / 2) * (H / 2)) as f64;
eprintln!(
"scalar path 1001² -> 500²: frozen reference {:.3} ns/out-px, shipping {:.3} ns/out-px, ratio {:.2}x",
ref_ns as f64 / px,
ship_ns as f64 / px,
ref_ns as f64 / ship_ns as f64,
);
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[test]
fn avx2_rows_match_ssse3_rows() {
if !crate::encode::blocks::simd_avx2() || !crate::swizzle::has_ssse3() {
eprintln!("AVX2/SSSE3 not available; skipping");
return;
}
let mut state = 0xa2b2_c2d2_e2f2_0212u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..4_000u32 {
let sw = (next() % 36 + 2) as usize;
let sh = (next() % 16 + 2) as usize;
let mut src = vec![0u8; sw * sh * 4];
for b in src.iter_mut() {
*b = next() as u8;
}
let (dw, dh) = (sw / 2, sh / 2);
let mut out_a = vec![0u8; dw * dh * 4];
let mut out_b = vec![0u8; dw * dh * 4];
unsafe {
super::downsample_2d_rows_avx2(&src, sw, 0, dh, &mut out_a);
super::downsample_2d_rows_ssse3(&src, sw, 0, dh, &mut out_b);
}
assert_eq!(out_a, out_b, "case {case}: {sw}x{sh}");
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[test]
#[ignore]
fn probe_avx2_rows_ab() {
assert!(crate::encode::blocks::simd_avx2());
const W: usize = 2048;
const H: usize = 2048;
let mut state = 0xdead_beef_cafe_f00du64;
let mut src = vec![0u8; W * H * 4];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
let mut out = vec![0u8; (W / 2) * (H / 2) * 4];
use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..31 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let sse_ns = best(&mut || {
unsafe { super::downsample_2d_rows_ssse3(black_box(&src), W, 0, H / 2, &mut out) };
out[123] as u64 + 1
});
let avx_ns = best(&mut || {
unsafe { super::downsample_2d_rows_avx2(black_box(&src), W, 0, H / 2, &mut out) };
out[123] as u64 + 1
});
let px = ((W / 2) * (H / 2)) as f64;
eprintln!(
"level 2048² -> 1024²: ssse3 {:.3} ns/out-px, avx2 {:.3} ns/out-px, ratio {:.2}x",
sse_ns as f64 / px,
avx_ns as f64 / px,
sse_ns as f64 / avx_ns as f64,
);
}
#[test]
#[ignore]
fn probe_chain_walk_ab() {
let dds = crate::Dds::new_dxgi(crate::NewDxgiParams {
height: 4096,
width: 4096,
depth: None,
format: crate::DxgiFormat::BC1_UNorm,
mipmap_levels: Some(13),
array_layers: None,
caps2: None,
is_cubemap: false,
resource_dimension: crate::D3D10ResourceDimension::Texture2D,
alpha_mode: crate::AlphaMode::Straight,
})
.unwrap();
use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..31 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let per_level_ns = best(&mut || {
let mut sink = 0u64;
for _ in 0..100 {
for mip in 0..13u32 {
let id = crate::SubresourceId::mip_layer(mip, 0);
let r = black_box(&dds).subresource_range(id).unwrap();
sink = sink.wrapping_add(r.start as u64);
}
}
sink
});
let one_walk_ns = best(&mut || {
let mut sink = 0u64;
for _ in 0..100 {
let ranges = black_box(&dds).subresource_chain_ranges(0, 0).unwrap();
for r in &ranges {
sink = sink.wrapping_add(r.start as u64);
}
}
sink
});
eprintln!(
"chain ranges, 13 levels x100: per-level walks {:.1} ns/chain, one walk {:.1} ns/chain, ratio {:.2}x",
per_level_ns as f64 / 100.0,
one_walk_ns as f64 / 100.0,
per_level_ns as f64 / one_walk_ns as f64,
);
}
#[test]
#[ignore]
fn probe_1d_ab() {
const L: u32 = 8192;
let mut state = 0xdead_beef_cafe_f00du64;
let mut src = vec![0u8; (L * 4) as usize];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..31 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let ref_ns = best(&mut || {
downsample_reference(black_box(&src), L, 1, 1)[3] as u64 + 1
});
let mut buf = Vec::new();
let ship_ns = best(&mut || {
super::downsample_rgba8_into(black_box(&src), L, 1, 1, &mut buf).unwrap();
buf[3] as u64 + 1
});
let px = (L / 2) as f64;
eprintln!(
"1D tail 8192x1 -> 4096x1: reference {:.3} ns/out-px, shipping {:.3} ns/out-px, ratio {:.2}x",
ref_ns as f64 / px,
ship_ns as f64 / px,
ref_ns as f64 / ship_ns as f64,
);
}
#[test]
#[ignore]
fn probe_volume_ab() {
const W: u32 = 256;
const H: u32 = 256;
const D: u32 = 8;
let mut state = 0xdead_beef_cafe_f00du64;
let mut src = vec![0u8; (W * H * D * 4) as usize];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..15 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let ref_ns = best(&mut || {
downsample_reference(black_box(&src), W, H, D)[123] as u64 + 1
});
let mut buf = Vec::new();
let ship_ns = best(&mut || {
super::downsample_rgba8_into(black_box(&src), W, H, D, &mut buf).unwrap();
buf[123] as u64 + 1
});
let px = ((W / 2) * (H / 2) * (D / 2)) as f64;
eprintln!(
"volume 256x256x8 -> 128x128x4: reference {:.3} ns/out-px, shipping {:.3} ns/out-px, ratio {:.2}x",
ref_ns as f64 / px,
ship_ns as f64 / px,
ref_ns as f64 / ship_ns as f64,
);
}
#[test]
#[ignore]
fn probe_bufprep_ab() {
let sizes: Vec<usize> = (1..=10u32)
.map(|l| {
let d = 1usize << l;
((1024 / d) * (1024 / d) * 4).max(4)
})
.collect();
use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..31 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let mut buf_a: Vec<u8> = Vec::new();
let zero_ns = best(&mut || {
let mut sink = 0u64;
for &s in black_box(&sizes) {
buf_a.clear();
buf_a.resize(s, 0);
sink = sink.wrapping_add(buf_a[s / 2] as u64 + 1);
}
sink
});
let mut buf_b: Vec<u8> = Vec::new();
let trunc_ns = best(&mut || {
let mut sink = 0u64;
for &s in black_box(&sizes) {
if buf_b.len() >= s {
buf_b.truncate(s);
} else {
buf_b.resize(s, 0);
}
sink = sink.wrapping_add(buf_b[s / 2] as u64 + 1);
}
sink
});
eprintln!(
"buffer prep, 10-level 1024² chain: zeroing {:.1} us, truncate {:.1} us, ratio {:.2}x",
zero_ns as f64 / 1000.0,
trunc_ns as f64 / 1000.0,
zero_ns as f64 / trunc_ns as f64,
);
}
#[test]
#[ignore]
fn probe_face_chain_ab() {
const W: u32 = 256;
const H: u32 = 256;
const FACES: usize = 6;
const LEVELS: u32 = 9; let mut state = 0xdead_beef_cafe_f00du64;
let mut faces = Vec::new();
for _ in 0..FACES {
let mut src = vec![0u8; (W * H * 4) as usize];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
faces.push(src);
}
use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..15 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let run_face = |src: &[u8], cur_buf: &mut Vec<u8>, next_buf: &mut Vec<u8>| -> u64 {
let mut sink = 0u64;
let (mut w, mut h) = (W, H);
for level in 0..LEVELS {
let cur: &[u8] = if level == 0 { src } else { cur_buf };
sink = sink.wrapping_add(cur[0] as u64);
if level + 1 < LEVELS {
super::downsample_rgba8_into(cur, w, h, 1, next_buf).unwrap();
std::mem::swap(cur_buf, next_buf);
w = (w / 2).max(1);
h = (h / 2).max(1);
}
}
sink
};
let fresh_ns = best(&mut || {
let mut sink = 0u64;
for src in black_box(&faces) {
let mut cur_buf: Vec<u8> = Vec::new();
let mut next_buf: Vec<u8> = Vec::new();
sink = sink.wrapping_add(run_face(src, &mut cur_buf, &mut next_buf));
}
sink
});
let mut cur_buf: Vec<u8> = Vec::new();
let mut next_buf: Vec<u8> = Vec::new();
let hoisted_ns = best(&mut || {
let mut sink = 0u64;
for src in black_box(&faces) {
sink = sink.wrapping_add(run_face(src, &mut cur_buf, &mut next_buf));
}
sink
});
eprintln!(
"6-face 256² chains: fresh pair/face {:.1} us, hoisted pair {:.1} us, ratio {:.2}x",
fresh_ns as f64 / 1000.0,
hoisted_ns as f64 / 1000.0,
fresh_ns as f64 / hoisted_ns as f64,
);
}
#[test]
#[ignore]
fn probe_mip_chain_ab() {
const W: u32 = 1024;
const H: u32 = 1024;
let mut state = 0xdead_beef_cafe_f00du64;
let mut src = vec![0u8; (W * H * 4) as usize];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
let levels = 11u32; use std::hint::black_box;
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..15 {
let t = std::time::Instant::now();
let sink = black_box(f());
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, u64::MAX);
best = best.min(dt);
}
best
};
let old_ns = best(&mut || {
let mut sink = 0u64;
let mut mip = black_box(&src[..]).to_vec();
let (mut w, mut h) = (W, H);
for level in 0..levels {
sink = sink.wrapping_add(mip[0] as u64);
if level + 1 < levels {
mip = super::downsample_rgba8(&mip, w, h, 1).unwrap();
w = (w / 2).max(1);
h = (h / 2).max(1);
}
}
sink
});
let new_ns = best(&mut || {
let mut sink = 0u64;
let mut cur_buf: Vec<u8> = Vec::new();
let mut next_buf: Vec<u8> = Vec::new();
let (mut w, mut h) = (W, H);
for level in 0..levels {
let cur: &[u8] = if level == 0 { black_box(&src[..]) } else { &cur_buf };
sink = sink.wrapping_add(cur[0] as u64);
if level + 1 < levels {
super::downsample_rgba8_into(cur, w, h, 1, &mut next_buf).unwrap();
std::mem::swap(&mut cur_buf, &mut next_buf);
w = (w / 2).max(1);
h = (h / 2).max(1);
}
}
sink
});
eprintln!(
"mip chain 1024x1024 x{levels} levels: old (to_vec + alloc/level) {:.1} us, new (ping-pong) {:.1} us, ratio {:.2}x",
old_ns as f64 / 1000.0,
new_ns as f64 / 1000.0,
old_ns as f64 / new_ns as f64,
);
}
#[test]
#[ignore]
fn probe_downsample_ab() {
const W: u32 = 1024;
const H: u32 = 1024;
let mut state = 0xdead_beef_cafe_f00du64;
let mut src = vec![0u8; (W * H * 4) as usize];
for b in src.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*b = state as u8;
}
let best = |f: &mut dyn FnMut() -> u64| {
let mut best = u64::MAX;
for _ in 0..31 {
let t = std::time::Instant::now();
let sink = f();
let dt = t.elapsed().as_nanos() as u64;
assert_ne!(sink, 0);
best = best.min(dt);
}
best
};
let scalar_ns = best(&mut || downsample_reference(&src, W, H, 1)[123] as u64 + 1);
let fast_ns = best(&mut || super::downsample_rgba8(&src, W, H, 1).unwrap()[123] as u64 + 1);
let px = (W * H / 4) as f64; eprintln!(
"downsample 1024x1024->512x512: scalar {:.3} ns/out-px, kernel path {:.3} ns/out-px, ratio {:.2}x",
scalar_ns as f64 / px,
fast_ns as f64 / px,
scalar_ns as f64 / fast_ns as f64,
);
}
}