1use std::collections::{HashMap, VecDeque};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct AtlasRect {
16 pub x: u32,
18 pub y: u32,
20 pub w: u32,
22 pub h: u32,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub struct AtlasHandle(u64);
32
33#[derive(Debug)]
37struct Shelf {
38 y: u32,
40 height: u32,
42 cursor_x: u32,
44}
45
46pub struct TextureAtlas {
62 pub width: u32,
64 pub height: u32,
66 next_y: u32,
68 shelves: Vec<Shelf>,
70 allocations: HashMap<AtlasHandle, AtlasRect>,
72 lru_order: VecDeque<AtlasHandle>,
74 free_list: Vec<AtlasRect>,
76 next_id: u64,
78 used_area: u64,
80}
81
82impl TextureAtlas {
83 pub fn new(width: u32, height: u32) -> Self {
85 Self {
86 width,
87 height,
88 next_y: 0,
89 shelves: Vec::new(),
90 allocations: HashMap::new(),
91 lru_order: VecDeque::new(),
92 free_list: Vec::new(),
93 next_id: 0,
94 used_area: 0,
95 }
96 }
97
98 pub fn insert(&mut self, w: u32, h: u32) -> Option<AtlasHandle> {
103 if w == 0 || h == 0 || w > self.width || h > self.height {
104 return None;
105 }
106
107 if let Some(rect) = self.alloc_from_free_list(w, h) {
109 return Some(self.register(rect));
110 }
111
112 if let Some(rect) = self.alloc_from_shelves(w, h) {
114 return Some(self.register(rect));
115 }
116
117 if let Some(rect) = self.alloc_new_shelf(w, h) {
119 return Some(self.register(rect));
120 }
121
122 self.evict_lru()?;
124
125 if let Some(rect) = self.alloc_from_free_list(w, h) {
126 return Some(self.register(rect));
127 }
128 if let Some(rect) = self.alloc_from_shelves(w, h) {
129 return Some(self.register(rect));
130 }
131 if let Some(rect) = self.alloc_new_shelf(w, h) {
132 return Some(self.register(rect));
133 }
134
135 None
136 }
137
138 pub fn get(&self, h: AtlasHandle) -> Option<AtlasRect> {
140 self.allocations.get(&h).copied()
141 }
142
143 pub fn utilization(&self) -> f32 {
147 let total = u64::from(self.width) * u64::from(self.height);
148 if total == 0 {
149 return 0.0;
150 }
151 (self.used_area as f32) / (total as f32)
152 }
153
154 pub fn resize(&mut self, new_width: u32, new_height: u32) {
159 self.width = new_width;
160 self.height = new_height;
161 self.next_y = 0;
162 self.shelves.clear();
163 self.allocations.clear();
164 self.lru_order.clear();
165 self.free_list.clear();
166 self.next_id = 0;
167 self.used_area = 0;
168 }
169
170 fn alloc_from_free_list(&mut self, w: u32, h: u32) -> Option<AtlasRect> {
174 let pos = self.free_list.iter().position(|r| r.w >= w && r.h >= h)?;
175 let free_rect = self.free_list.remove(pos);
176 Some(AtlasRect {
177 x: free_rect.x,
178 y: free_rect.y,
179 w,
180 h,
181 })
182 }
183
184 fn alloc_from_shelves(&mut self, w: u32, h: u32) -> Option<AtlasRect> {
186 for shelf in &mut self.shelves {
187 if shelf.height >= h && self.width.saturating_sub(shelf.cursor_x) >= w {
188 let rect = AtlasRect {
189 x: shelf.cursor_x,
190 y: shelf.y,
191 w,
192 h,
193 };
194 shelf.cursor_x += w;
195 return Some(rect);
196 }
197 }
198 None
199 }
200
201 fn alloc_new_shelf(&mut self, w: u32, h: u32) -> Option<AtlasRect> {
203 if self.height.saturating_sub(self.next_y) < h || self.width < w {
204 return None;
205 }
206 let shelf = Shelf {
207 y: self.next_y,
208 height: h,
209 cursor_x: w,
210 };
211 let rect = AtlasRect {
212 x: 0,
213 y: self.next_y,
214 w,
215 h,
216 };
217 self.next_y += h;
218 self.shelves.push(shelf);
219 Some(rect)
220 }
221
222 fn register(&mut self, rect: AtlasRect) -> AtlasHandle {
224 let handle = AtlasHandle(self.next_id);
225 self.next_id += 1;
226 self.used_area += u64::from(rect.w) * u64::from(rect.h);
227 self.allocations.insert(handle, rect);
228 self.lru_order.push_back(handle);
229 handle
230 }
231
232 fn evict_lru(&mut self) -> Option<()> {
235 loop {
237 let candidate = self.lru_order.pop_front()?;
238 if let Some(rect) = self.allocations.remove(&candidate) {
239 self.used_area = self
240 .used_area
241 .saturating_sub(u64::from(rect.w) * u64::from(rect.h));
242 self.free_list.push(rect);
243 return Some(());
244 }
245 }
246 }
247}
248
249#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn atlas_packs_100_rects_no_overlap() {
257 let mut atlas = TextureAtlas::new(128, 128);
258 let mut rects: Vec<(AtlasHandle, AtlasRect)> = Vec::new();
259
260 for _ in 0..100 {
261 if let Some(h) = atlas.insert(4, 4) {
262 let r = atlas.get(h).expect("handle must be valid");
263 rects.push((h, r));
264 }
265 }
266
267 assert!(!rects.is_empty());
269
270 for i in 0..rects.len() {
272 for j in (i + 1)..rects.len() {
273 let a = rects[i].1;
274 let b = rects[j].1;
275 let overlap =
276 a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
277 assert!(!overlap, "rects {i} and {j} overlap: {a:?} vs {b:?}");
278 }
279 }
280 }
281
282 #[test]
283 fn atlas_utilization_above_threshold() {
284 let mut atlas = TextureAtlas::new(32, 32);
286 let mut count = 0u32;
287 while atlas.insert(1, 1).is_some() {
288 count += 1;
289 if count > 1024 * 4 {
290 break; }
292 }
293 assert!(
295 atlas.utilization() >= 0.70,
296 "utilization was {}",
297 atlas.utilization()
298 );
299 }
300
301 #[test]
302 fn atlas_lru_eviction_keeps_invariants() {
303 let mut atlas = TextureAtlas::new(4, 4);
305 let h0 = atlas.insert(4, 1).expect("first insert");
307 let h1 = atlas.insert(4, 1).expect("second insert");
308 let h2 = atlas.insert(4, 1).expect("third insert");
309 let h3 = atlas.insert(4, 1).expect("fourth insert");
310 let h4 = atlas.insert(4, 1).expect("insert with eviction");
312 assert!(
314 atlas.get(h0).is_none(),
315 "LRU handle h0 must have been evicted"
316 );
317 assert!(atlas.get(h4).is_some(), "new handle h4 must be live");
318 assert!(atlas.get(h1).is_some());
320 assert!(atlas.get(h2).is_some());
321 assert!(atlas.get(h3).is_some());
322 let live_rects: Vec<AtlasRect> = [h1, h2, h3, h4]
324 .iter()
325 .filter_map(|&h| atlas.get(h))
326 .collect();
327 for i in 0..live_rects.len() {
328 for j in (i + 1)..live_rects.len() {
329 let a = live_rects[i];
330 let b = live_rects[j];
331 let overlap =
332 a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
333 assert!(!overlap, "live rects {i} and {j} overlap: {a:?} vs {b:?}");
334 }
335 }
336 }
337
338 #[test]
339 fn atlas_resize_clears() {
340 let mut atlas = TextureAtlas::new(16, 16);
341 let h = atlas.insert(4, 4).expect("insert before resize");
342 atlas.resize(32, 32);
343 assert!(
345 atlas.get(h).is_none(),
346 "handle must be invalid after resize"
347 );
348 assert!(
349 (atlas.utilization() - 0.0).abs() < f32::EPSILON,
350 "utilization must be 0 after resize"
351 );
352 assert!(
354 atlas.insert(4, 4).is_some(),
355 "insert after resize must succeed"
356 );
357 }
358}