1use crate::render::uniforms::HizSpdParams;
10
11pub const LEVELS: u32 = 6;
13
14pub const TILE: u32 = 32;
16
17pub const MAX_MIPS: u32 = LEVELS + LEVELS - 1;
20
21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
23pub struct Dispatch {
24 pub base_mip: u32,
27 pub params: HizSpdParams,
29 pub groups: (u32, u32),
31}
32
33#[derive(Copy, Clone, Debug, PartialEq, Eq)]
36pub struct Plan {
37 pub phase1: Dispatch,
39 pub tail: Option<Dispatch>,
41}
42
43pub fn level_size(base: (u32, u32), level: u32) -> (u32, u32) {
45 ((base.0 >> level).max(1), (base.1 >> level).max(1))
46}
47
48impl Plan {
49 pub fn new(width: u32, height: u32, mip_count: u32, sample_count: u32) -> Self {
53 let base = (width.max(1), height.max(1));
54 let mips = mip_count.clamp(1, MAX_MIPS);
55 let phase1 = Dispatch {
56 base_mip: 0,
57 params: HizSpdParams {
58 base_width: base.0,
59 base_height: base.1,
60 level_count: mips.min(LEVELS),
61 sample_count,
62 },
63 groups: (base.0.div_ceil(TILE), base.1.div_ceil(TILE)),
64 };
65 let tail = (mips > LEVELS).then(|| {
68 let tail_base = level_size(base, LEVELS - 1);
69 let groups = (tail_base.0.div_ceil(TILE), tail_base.1.div_ceil(TILE));
70 Dispatch {
71 base_mip: LEVELS - 1,
72 params: HizSpdParams {
73 base_width: tail_base.0,
74 base_height: tail_base.1,
75 level_count: tail_level_count(tail_base, groups, mips - (LEVELS - 1)),
76 sample_count,
77 },
78 groups,
79 }
80 });
81 Self { phase1, tail }
82 }
83
84 pub fn mip_count(&self) -> u32 {
88 match self.tail {
89 Some(t) => t.base_mip + t.params.level_count,
90 None => self.phase1.params.level_count,
91 }
92 }
93
94 pub fn bound_mips(base_mip: u32, mip_count: u32) -> impl Iterator<Item = u32> {
99 (0..LEVELS).map(move |i| (base_mip + i).min(mip_count.saturating_sub(1)))
100 }
101}
102
103fn tail_level_count(base: (u32, u32), groups: (u32, u32), requested: u32) -> u32 {
108 let mut count = 1;
109 for level in 1..requested.min(LEVELS) {
110 let size = level_size(base, level);
111 if size.0 < groups.0 || size.1 < groups.1 {
112 break;
113 }
114 count = level + 1;
115 }
116 count
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
127 fn plan_at_1024x768() {
128 let p = Plan::new(1024, 768, 11, 1);
129 assert_eq!(p.phase1.groups, (32, 24));
130 assert_eq!(p.phase1.params.level_count, LEVELS);
131 let tail = p.tail.expect("11 mips needs a tail");
132 assert_eq!(tail.base_mip, LEVELS - 1);
133 assert_eq!((tail.params.base_width, tail.params.base_height), (32, 24));
134 assert_eq!(tail.params.level_count, 6);
135 assert_eq!(tail.groups, (1, 1));
136 assert_eq!(p.mip_count(), 11);
137 }
138
139 #[test]
141 fn shallow_pyramid_has_no_tail() {
142 let p = Plan::new(32, 32, LEVELS, 1);
143 assert!(p.tail.is_none());
144 assert_eq!(p.phase1.groups, (1, 1));
145 assert_eq!(p.phase1.params.level_count, LEVELS);
146 }
147
148 #[test]
149 fn one_mip_writes_only_the_base() {
150 let p = Plan::new(1920, 1080, 1, 1);
151 assert_eq!(p.phase1.params.level_count, 1);
152 assert!(p.tail.is_none());
153 }
154
155 #[test]
159 fn tail_levels_are_never_shared_between_groups() {
160 for (w, h) in [
161 (1024u32, 768u32),
162 (1920, 1080),
163 (2560, 1440),
164 (3840, 2160),
165 (7680, 4320),
166 ] {
167 let mips = 32 - w.max(h).leading_zeros();
168 let Some(tail) = Plan::new(w, h, mips, 1).tail else {
169 continue;
170 };
171 let base = (tail.params.base_width, tail.params.base_height);
172 let coarsest = tail.params.level_count - 1;
173 let size = level_size(base, coarsest);
174 assert!(
175 size.0 >= tail.groups.0 && size.1 >= tail.groups.1,
176 "{w}x{h}: level {coarsest} is {size:?} for {:?} groups",
177 tail.groups
178 );
179 }
180 }
181
182 #[test]
186 fn deep_pyramid_clamps_to_what_two_dispatches_reach() {
187 assert_eq!(MAX_MIPS, 11);
188 let p = Plan::new(8192, 8192, 14, 1);
189 let tail = p.tail.expect("deep pyramid needs a tail");
190 assert_eq!(tail.params.level_count, LEVELS);
191 assert_eq!(p.mip_count(), MAX_MIPS);
192 }
193
194 #[test]
197 fn tail_gives_up_a_level_rather_than_let_groups_collide() {
198 let p = Plan::new(7680, 4320, 13, 1);
199 let tail = p.tail.expect("deep pyramid needs a tail");
200 assert_eq!(tail.groups, (8, 5));
201 assert_eq!(tail.params.level_count, 5);
202 assert_eq!(p.mip_count(), 10);
203 }
204
205 #[test]
208 fn mip_count_reports_what_is_written() {
209 assert_eq!(Plan::new(1024, 768, 11, 1).mip_count(), 11);
210 assert_eq!(Plan::new(32, 32, LEVELS, 1).mip_count(), LEVELS);
211 assert_eq!(Plan::new(1920, 1080, 1, 1).mip_count(), 1);
212 assert!(Plan::new(3840, 2160, 12, 1).mip_count() <= 12);
213 }
214
215 #[test]
216 fn bound_mips_repeats_the_last_live_mip() {
217 assert!(Plan::bound_mips(5, 9).eq([5, 6, 7, 8, 8, 8]));
218 assert!(Plan::bound_mips(0, 11).eq([0, 1, 2, 3, 4, 5]));
219 }
220}