1use crate::render_graph::full_mip_levels;
12use alloc::vec;
13use alloc::vec::Vec;
14
15pub struct MipLevel {
18 pub width: u32,
20 pub height: u32,
22 pub pixels: Vec<u8>,
24}
25
26pub fn generate_mip_chain(width: u32, height: u32, rgba8: &[u8]) -> Vec<MipLevel> {
31 let count = full_mip_levels(width, height);
32 let base_len = width as usize * height as usize * 4;
33 let mut levels: Vec<MipLevel> = Vec::with_capacity(count as usize);
34 levels.push(MipLevel {
35 width,
36 height,
37 pixels: rgba8[..base_len].to_vec(),
38 });
39 for _ in 1..count {
40 let prev = levels.last().unwrap();
41 let dw = (prev.width / 2).max(1);
42 let dh = (prev.height / 2).max(1);
43 let mut pixels = vec![0u8; dw as usize * dh as usize * 4];
44 downsample_box(prev, dw, dh, &mut pixels);
45 levels.push(MipLevel {
46 width: dw,
47 height: dh,
48 pixels,
49 });
50 }
51 levels
52}
53
54fn downsample_box(src: &MipLevel, dw: u32, dh: u32, dst: &mut [u8]) {
58 let sw = src.width as usize;
59 let sh = src.height as usize;
60 for y in 0..dh as usize {
61 let sy0 = (2 * y).min(sh - 1);
62 let sy1 = (2 * y + 1).min(sh - 1);
63 for x in 0..dw as usize {
64 let sx0 = (2 * x).min(sw - 1);
65 let sx1 = (2 * x + 1).min(sw - 1);
66 let i00 = (sy0 * sw + sx0) * 4;
67 let i01 = (sy0 * sw + sx1) * 4;
68 let i10 = (sy1 * sw + sx0) * 4;
69 let i11 = (sy1 * sw + sx1) * 4;
70 let d = (y * dw as usize + x) * 4;
71 for c in 0..4 {
72 let sum = src.pixels[i00 + c] as u32
73 + src.pixels[i01 + c] as u32
74 + src.pixels[i10 + c] as u32
75 + src.pixels[i11 + c] as u32;
76 dst[d + c] = ((sum + 2) / 4) as u8;
78 }
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn level_count_matches_floor_log2_plus_one() {
89 assert_eq!(full_mip_levels(1, 1), 1);
90 assert_eq!(full_mip_levels(2, 2), 2);
91 assert_eq!(full_mip_levels(256, 256), 9);
92 assert_eq!(full_mip_levels(512, 512), 10);
93 assert_eq!(full_mip_levels(640, 384), 10); assert_eq!(full_mip_levels(1, 8), 4); }
97
98 #[test]
99 fn chain_dimensions_halve_to_one() {
100 let px = vec![0u8; 4 * 4 * 4];
101 let chain = generate_mip_chain(4, 4, &px);
102 let dims: Vec<(u32, u32)> = chain.iter().map(|m| (m.width, m.height)).collect();
103 assert_eq!(dims, vec![(4, 4), (2, 2), (1, 1)]);
104 assert_eq!(chain.len() as u32, full_mip_levels(4, 4));
105 }
106
107 #[test]
108 fn non_square_chain_floors_each_axis_independently() {
109 let px = vec![0u8; 4 * 2 * 4];
110 let chain = generate_mip_chain(4, 2, &px);
111 let dims: Vec<(u32, u32)> = chain.iter().map(|m| (m.width, m.height)).collect();
112 assert_eq!(dims, vec![(4, 2), (2, 1), (1, 1)]);
114 }
115
116 #[test]
117 fn two_by_two_averages_to_single_texel() {
118 let px = vec![
120 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, ];
125 let chain = generate_mip_chain(2, 2, &px);
126 assert_eq!(chain.len(), 2);
127 let mip1 = &chain[1];
128 assert_eq!((mip1.width, mip1.height), (1, 1));
129 assert_eq!(mip1.pixels, vec![6, 6, 6, 6]);
130 }
131
132 #[test]
133 fn rounds_to_nearest() {
134 let dark = vec![0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 1, 1, 1, 255];
136 let c = generate_mip_chain(2, 2, &dark);
137 assert_eq!(&c[1].pixels[0..3], &[0, 0, 0]);
138
139 let bright = vec![0, 0, 0, 255, 1, 1, 1, 255, 1, 1, 1, 255, 1, 1, 1, 255];
140 let c = generate_mip_chain(2, 2, &bright);
141 assert_eq!(&c[1].pixels[0..3], &[1, 1, 1]);
142 }
143}