1use alloc::vec::Vec;
14pub use guillotiere::AllocId;
15use guillotiere::AtlasAllocator;
16use thiserror::Error;
17
18#[derive(Debug)]
20pub struct Allocation {
21 pub id: AllocId,
23 pub x: u16,
25 pub y: u16,
27}
28
29pub struct Atlas {
35 pub id: AtlasId,
37 allocator: AtlasAllocator,
39 stats: AtlasUsageStats,
41 allocation_counter: u32,
43}
44
45impl Atlas {
46 pub fn new(id: AtlasId, width: u16, height: u16) -> Self {
48 Self {
49 id,
50 allocator: AtlasAllocator::new(guillotiere::size2(i32::from(width), i32::from(height))),
51 stats: AtlasUsageStats {
52 allocated_area: 0,
53 total_area: u32::from(width) * u32::from(height),
54 allocated_count: 0,
55 },
56 allocation_counter: 0,
57 }
58 }
59
60 pub fn allocate(&mut self, width: u16, height: u16) -> Option<Allocation> {
62 let alloc = self
63 .allocator
64 .allocate(guillotiere::size2(i32::from(width), i32::from(height)))?;
65 self.stats.allocated_area += u32::from(width) * u32::from(height);
66 self.stats.allocated_count += 1;
67 self.allocation_counter += 1;
68 Some(Allocation {
69 id: alloc.id,
70 x: u16::try_from(alloc.rectangle.min.x)
71 .expect("guillotiere returned an out-of-range x coordinate"),
72 y: u16::try_from(alloc.rectangle.min.y)
73 .expect("guillotiere returned an out-of-range y coordinate"),
74 })
75 }
76
77 pub fn deallocate(&mut self, alloc_id: AllocId, width: u16, height: u16) {
79 self.allocator.deallocate(alloc_id);
80 self.stats.allocated_area = self
81 .stats
82 .allocated_area
83 .saturating_sub(u32::from(width) * u32::from(height));
84 self.stats.allocated_count = self.stats.allocated_count.saturating_sub(1);
85 }
86
87 pub fn stats(&self) -> &AtlasUsageStats {
89 &self.stats
90 }
91}
92
93impl core::fmt::Debug for Atlas {
94 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95 f.debug_struct("Atlas")
96 .field("id", &self.id)
97 .field("stats", &self.stats)
98 .field("allocation_counter", &self.allocation_counter)
99 .finish_non_exhaustive()
100 }
101}
102
103pub struct MultiAtlasManager {
109 atlases: Vec<Atlas>,
111 config: AtlasConfig,
113 round_robin_counter: usize,
115}
116
117impl MultiAtlasManager {
118 pub fn new(config: AtlasConfig) -> Self {
120 let mut manager = Self {
121 atlases: Vec::new(),
122 config,
123 round_robin_counter: 0,
124 };
125
126 for _ in 0..config.initial_atlas_count {
127 manager
128 .create_atlas()
129 .expect("Failed to create initial atlas");
130 }
131
132 manager
133 }
134
135 pub fn config(&self) -> &AtlasConfig {
137 &self.config
138 }
139
140 pub fn create_atlas(&mut self) -> Result<AtlasId, AtlasError> {
142 if self.atlases.len() >= self.config.max_atlases {
143 return Err(AtlasError::AtlasLimitReached {
144 max_atlases: self.config.max_atlases,
145 diagnostics: AtlasSpaceDiagnostics::Unavailable,
146 });
147 }
148
149 let atlas_id = AtlasId::new(self.next_atlas_id());
150
151 let atlas = Atlas::new(atlas_id, self.config.atlas_size.0, self.config.atlas_size.1);
152 self.atlases.push(atlas);
153
154 Ok(atlas_id)
155 }
156
157 pub fn next_atlas_id(&self) -> u32 {
159 u32::try_from(self.atlases.len()).unwrap()
160 }
161
162 pub fn try_allocate(&mut self, width: u16, height: u16) -> Result<AtlasAllocation, AtlasError> {
164 if width > self.config.atlas_size.0 || height > self.config.atlas_size.1 {
166 return Err(AtlasError::TextureTooLarge {
167 width: u32::from(width),
168 height: u32::from(height),
169 max_width: self.config.atlas_size.0,
170 max_height: self.config.atlas_size.1,
171 });
172 }
173
174 match self.config.allocation_strategy {
176 AllocationStrategy::FirstFit => self.allocate_first_fit(width, height),
177 AllocationStrategy::BestFit => self.allocate_best_fit(width, height),
178 AllocationStrategy::LeastUsed => self.allocate_least_used(width, height),
179 AllocationStrategy::RoundRobin => self.allocate_round_robin(width, height),
180 }
181 }
182
183 fn space_diagnostics(&self, width: u16, height: u16) -> AtlasSpaceDiagnostics {
184 let mut atlases = Vec::new();
185
186 for atlas in &self.atlases {
187 let mut free_area = 0_u64;
188 let mut free_rectangle_count = 0;
189 let mut largest_free_width = 0;
190 let mut largest_free_height = 0;
191 let mut largest_free_area = 0_u64;
192 atlas.allocator.for_each_free_rectangle(|rect| {
193 let rect_width = u16::try_from(rect.width())
194 .expect("guillotiere returned an out-of-range rectangle width");
195 let rect_height = u16::try_from(rect.height())
196 .expect("guillotiere returned an out-of-range rectangle height");
197 let rect_area = u64::from(rect_width) * u64::from(rect_height);
198 free_area += rect_area;
199 free_rectangle_count += 1;
200
201 if rect_area > largest_free_area {
202 largest_free_area = rect_area;
203 largest_free_width = rect_width;
204 largest_free_height = rect_height;
205 }
206 });
207
208 atlases.push(AtlasLayerDiagnostics {
209 atlas_id: atlas.id,
210 total_area: u64::from(atlas.stats.total_area),
211 free_area,
212 free_rectangle_count,
213 largest_free_width,
214 largest_free_height,
215 });
216 }
217
218 AtlasSpaceDiagnostics::Allocation {
219 width,
220 height,
221 atlas_width: self.config.atlas_size.0,
222 atlas_height: self.config.atlas_size.1,
223 max_atlases: self.config.max_atlases,
224 atlases,
225 }
226 }
227
228 fn no_space_available(&self, width: u16, height: u16) -> AtlasError {
229 AtlasError::NoSpaceAvailable(self.space_diagnostics(width, height))
230 }
231
232 fn atlas_limit_reached(&self, width: u16, height: u16) -> AtlasError {
233 AtlasError::AtlasLimitReached {
234 max_atlases: self.config.max_atlases,
235 diagnostics: self.space_diagnostics(width, height),
236 }
237 }
238
239 fn allocate_first_fit(
241 &mut self,
242 width: u16,
243 height: u16,
244 ) -> Result<AtlasAllocation, AtlasError> {
245 for atlas in &mut self.atlases {
246 if let Some(allocation) = atlas.allocate(width, height) {
247 return Ok(AtlasAllocation {
248 atlas_id: atlas.id,
249 allocation,
250 });
251 }
252 }
253
254 if self.config.auto_grow {
256 let atlas_id = self
257 .create_atlas()
258 .map_err(|_| self.atlas_limit_reached(width, height))?;
259 let atlas = self.atlases.last_mut().unwrap();
260 if let Some(allocation) = atlas.allocate(width, height) {
261 return Ok(AtlasAllocation {
262 atlas_id,
263 allocation,
264 });
265 }
266 }
267
268 Err(self.no_space_available(width, height))
269 }
270
271 fn allocate_best_fit(
274 &mut self,
275 width: u16,
276 height: u16,
277 ) -> Result<AtlasAllocation, AtlasError> {
278 let mut best_atlas_idx = None;
279 let mut best_remaining_space = u32::MAX;
280
281 for (idx, atlas) in self.atlases.iter().enumerate() {
283 let stats = atlas.stats();
284 let remaining_space = stats.total_area - stats.allocated_area;
285
286 if remaining_space >= u32::from(width) * u32::from(height)
287 && remaining_space < best_remaining_space
288 {
289 best_remaining_space = remaining_space;
290 best_atlas_idx = Some(idx);
291 }
292 }
293
294 if let Some(idx) = best_atlas_idx {
295 let atlas = &mut self.atlases[idx];
296 if let Some(allocation) = atlas.allocate(width, height) {
297 return Ok(AtlasAllocation {
298 atlas_id: atlas.id,
299 allocation,
300 });
301 }
302 }
303
304 self.allocate_first_fit(width, height)
306 }
307
308 fn allocate_least_used(
310 &mut self,
311 width: u16,
312 height: u16,
313 ) -> Result<AtlasAllocation, AtlasError> {
314 let mut best_atlas_idx = None;
315 let mut lowest_usage = f32::MAX;
316
317 for (idx, atlas) in self.atlases.iter().enumerate() {
319 let usage = atlas.stats().usage_percentage();
320 if usage < lowest_usage {
321 lowest_usage = usage;
322 best_atlas_idx = Some(idx);
323 }
324 }
325
326 if let Some(idx) = best_atlas_idx
327 && let Some(allocation) = self.atlases[idx].allocate(width, height)
328 {
329 let atlas_id = self.atlases[idx].id;
330 return Ok(AtlasAllocation {
331 atlas_id,
332 allocation,
333 });
334 }
335
336 self.allocate_first_fit(width, height)
338 }
339
340 fn allocate_round_robin(
342 &mut self,
343 width: u16,
344 height: u16,
345 ) -> Result<AtlasAllocation, AtlasError> {
346 if self.atlases.is_empty() {
347 return self.allocate_first_fit(width, height);
348 }
349
350 let start_idx = self.round_robin_counter % self.atlases.len();
351
352 for i in 0..self.atlases.len() {
354 let idx = (start_idx + i) % self.atlases.len();
355
356 if let Some(allocation) = self.atlases[idx].allocate(width, height) {
357 let atlas_id = self.atlases[idx].id;
358 self.round_robin_counter = (idx + 1) % self.atlases.len();
359 return Ok(AtlasAllocation {
360 atlas_id,
361 allocation,
362 });
363 }
364 }
365
366 if self.config.auto_grow {
368 let atlas_id = self
369 .create_atlas()
370 .map_err(|_| self.atlas_limit_reached(width, height))?;
371 let atlas = self.atlases.last_mut().unwrap();
372 if let Some(allocation) = atlas.allocate(width, height) {
373 self.round_robin_counter = self.atlases.len() - 1;
374 return Ok(AtlasAllocation {
375 atlas_id,
376 allocation,
377 });
378 }
379 }
380
381 Err(self.no_space_available(width, height))
382 }
383
384 pub fn deallocate(
386 &mut self,
387 atlas_id: AtlasId,
388 alloc_id: AllocId,
389 width: u16,
390 height: u16,
391 ) -> Result<(), AtlasError> {
392 let atlas = self
395 .atlases
396 .get_mut(atlas_id.0 as usize)
397 .ok_or(AtlasError::AtlasNotFound(atlas_id))?;
398 atlas.deallocate(alloc_id, width, height);
399 Ok(())
400 }
401
402 pub fn atlas_stats(&self) -> Vec<(AtlasId, &AtlasUsageStats)> {
404 self.atlases
405 .iter()
406 .map(|atlas| (atlas.id, atlas.stats()))
407 .collect()
408 }
409
410 pub fn atlas_count(&self) -> usize {
412 self.atlases.len()
413 }
414}
415
416impl core::fmt::Debug for MultiAtlasManager {
417 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
418 f.debug_struct("MultiAtlasManager")
419 .field("atlas_count", &self.atlases.len())
420 .field("config", &self.config)
421 .field("next_atlas_id", &self.next_atlas_id())
422 .field("round_robin_counter", &self.round_robin_counter)
423 .field("atlases", &self.atlases)
424 .finish()
425 }
426}
427
428#[derive(Debug, Clone, Error)]
430pub enum AtlasError {
431 #[error("No space available in any atlas{0}")]
433 NoSpaceAvailable(AtlasSpaceDiagnostics),
434 #[error("Maximum atlas count reached ({max_atlases}){diagnostics}")]
436 AtlasLimitReached {
437 max_atlases: usize,
439 diagnostics: AtlasSpaceDiagnostics,
441 },
442 #[error("Texture too large ({width}x{height}) for atlas (maximum {max_width}x{max_height})")]
444 TextureTooLarge {
445 width: u32,
447 height: u32,
449 max_width: u16,
451 max_height: u16,
453 },
454 #[error("Atlas with Id {0:?} not found")]
456 AtlasNotFound(AtlasId),
457}
458
459#[derive(Clone)]
461pub enum AtlasSpaceDiagnostics {
462 Unavailable,
464 Allocation {
466 width: u16,
468 height: u16,
470 atlas_width: u16,
472 atlas_height: u16,
474 max_atlases: usize,
476 atlases: Vec<AtlasLayerDiagnostics>,
478 },
479}
480
481impl core::fmt::Debug for AtlasSpaceDiagnostics {
482 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
483 let Self::Allocation {
484 width,
485 height,
486 atlas_width,
487 atlas_height,
488 max_atlases,
489 atlases,
490 } = self
491 else {
492 return f.write_str("Unavailable");
493 };
494
495 f.debug_struct("Allocation")
496 .field("requested", &Dimensions(*width, *height))
497 .field("layer_size", &Dimensions(*atlas_width, *atlas_height))
498 .field("max_atlases", max_atlases)
499 .field("atlas_layers", atlases)
500 .finish()
501 }
502}
503
504impl core::fmt::Display for AtlasSpaceDiagnostics {
505 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
506 let Self::Allocation {
507 width,
508 height,
509 atlas_width: _,
510 atlas_height: _,
511 max_atlases,
512 atlases,
513 } = self
514 else {
515 return Ok(());
516 };
517
518 let total_area = atlases.iter().map(|atlas| atlas.total_area).sum::<u64>();
519 let free_area = atlases.iter().map(|atlas| atlas.free_area).sum::<u64>();
520 let used_percentage = if total_area == 0 {
521 0.0
522 } else {
523 (1.0 - free_area as f64 / total_area as f64) * 100.0
524 };
525 write!(
526 f,
527 ": failed to allocate {width}x{height} across {} atlas layers \
528 (maximum {max_atlases}; {used_percentage:.1}% used)",
529 atlases.len(),
530 )
531 }
532}
533
534#[derive(Clone)]
536pub struct AtlasLayerDiagnostics {
537 pub atlas_id: AtlasId,
539 pub total_area: u64,
541 pub free_area: u64,
543 pub free_rectangle_count: usize,
545 pub largest_free_width: u16,
547 pub largest_free_height: u16,
549}
550
551impl AtlasLayerDiagnostics {
552 pub fn utilization_percentage(&self) -> f64 {
554 if self.total_area == 0 {
555 0.0
556 } else {
557 (1.0 - self.free_area as f64 / self.total_area as f64) * 100.0
558 }
559 }
560
561 pub fn fragmentation_percentage(&self) -> f64 {
563 if self.free_area == 0 {
564 0.0
565 } else {
566 let largest_free_area =
567 u64::from(self.largest_free_width) * u64::from(self.largest_free_height);
568 (1.0 - largest_free_area as f64 / self.free_area as f64) * 100.0
569 }
570 }
571}
572
573impl core::fmt::Debug for AtlasLayerDiagnostics {
574 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
575 f.debug_struct("Layer")
576 .field("atlas_id", &self.atlas_id)
577 .field("utilization", &Percentage(self.utilization_percentage()))
578 .field("capacity", &self.total_area)
579 .field("free", &self.free_area)
580 .field(
581 "largest_free_rectangle",
582 &Dimensions(self.largest_free_width, self.largest_free_height),
583 )
584 .field("free_rectangles", &self.free_rectangle_count)
585 .field(
586 "fragmentation",
587 &Percentage(self.fragmentation_percentage()),
588 )
589 .finish()
590 }
591}
592
593struct Dimensions(u16, u16);
594
595impl core::fmt::Debug for Dimensions {
596 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
597 write!(f, "{}x{}", self.0, self.1)
598 }
599}
600
601struct Percentage(f64);
602
603impl core::fmt::Debug for Percentage {
604 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
605 write!(f, "{:.1}%", self.0)
606 }
607}
608
609#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
611pub struct AtlasId(pub u32);
612
613impl AtlasId {
614 pub fn new(id: u32) -> Self {
616 Self(id)
617 }
618
619 pub fn as_u32(self) -> u32 {
621 self.0
622 }
623}
624
625#[derive(Debug, Clone)]
627pub struct AtlasUsageStats {
628 pub allocated_area: u32,
630 pub total_area: u32,
632 pub allocated_count: u32,
634}
635
636impl AtlasUsageStats {
637 pub fn usage_percentage(&self) -> f32 {
639 if self.total_area == 0 {
640 0.0
641 } else {
642 self.allocated_area as f32 / self.total_area as f32
643 }
644 }
645}
646
647#[derive(Debug)]
649pub struct AtlasAllocation {
650 pub atlas_id: AtlasId,
652 pub allocation: Allocation,
654}
655
656#[derive(Debug, Clone, Copy)]
663pub struct AtlasConfig {
664 pub initial_atlas_count: usize,
668 pub max_atlases: usize,
670 pub atlas_size: (u16, u16),
672 pub auto_grow: bool,
674 pub allocation_strategy: AllocationStrategy,
676}
677
678impl Default for AtlasConfig {
679 fn default() -> Self {
680 Self {
681 initial_atlas_count: 0,
682 max_atlases: 8,
683 atlas_size: (4096, 4096),
684 auto_grow: true,
685 allocation_strategy: AllocationStrategy::FirstFit,
686 }
687 }
688}
689
690#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
692pub enum AllocationStrategy {
693 #[default]
695 FirstFit,
696 BestFit,
698 LeastUsed,
700 RoundRobin,
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
709 fn test_atlas_creation() {
710 let mut manager = MultiAtlasManager::new(AtlasConfig {
711 initial_atlas_count: 0,
712 ..Default::default()
713 });
714
715 let atlas_id = manager.create_atlas().unwrap();
716 assert_eq!(atlas_id.as_u32(), 0);
717 assert_eq!(manager.atlas_count(), 1);
718 }
719
720 #[test]
721 fn test_default_lazily_creates_first_atlas() {
722 let mut manager = MultiAtlasManager::new(AtlasConfig::default());
723 assert_eq!(manager.atlas_count(), 0);
724
725 let allocation = manager.try_allocate(100, 100).unwrap();
726 assert_eq!(allocation.atlas_id.as_u32(), 0);
727 assert_eq!(manager.atlas_count(), 1);
728 }
729
730 #[test]
731 fn test_allocation_strategies() {
732 let mut manager = MultiAtlasManager::new(AtlasConfig {
733 initial_atlas_count: 1,
734 max_atlases: 3,
735 atlas_size: (256, 256),
736 allocation_strategy: AllocationStrategy::FirstFit,
737 auto_grow: true,
738 });
739
740 let allocation = manager.try_allocate(100, 100).unwrap();
742 assert_eq!(allocation.atlas_id.as_u32(), 0);
743 }
744
745 #[test]
746 fn test_atlas_limit() {
747 let mut manager = MultiAtlasManager::new(AtlasConfig {
748 initial_atlas_count: 1,
749 max_atlases: 1,
750 atlas_size: (256, 256),
751 allocation_strategy: AllocationStrategy::FirstFit,
752 auto_grow: false,
753 });
754
755 assert!(matches!(
756 manager.create_atlas(),
757 Err(AtlasError::AtlasLimitReached {
758 max_atlases: 1,
759 diagnostics: AtlasSpaceDiagnostics::Unavailable,
760 })
761 ));
762 }
763
764 #[test]
765 fn test_no_space_diagnostics() {
766 let mut manager = MultiAtlasManager::new(AtlasConfig {
767 initial_atlas_count: 1,
768 max_atlases: 1,
769 atlas_size: (256, 256),
770 auto_grow: false,
771 ..Default::default()
772 });
773 manager.try_allocate(128, 256).unwrap();
774
775 let Err(AtlasError::NoSpaceAvailable(AtlasSpaceDiagnostics::Allocation {
776 width: 129,
777 height: 256,
778 atlas_width: 256,
779 atlas_height: 256,
780 max_atlases: 1,
781 atlases,
782 })) = manager.try_allocate(129, 256)
783 else {
784 panic!("expected no-space diagnostics");
785 };
786 assert_eq!(atlases.len(), 1);
787 let atlas = &atlases[0];
788 assert_eq!(atlas.atlas_id, AtlasId::new(0));
789 assert_eq!(atlas.total_area, 65_536);
790 assert_eq!(atlas.free_area, 32_768);
791 assert_eq!(atlas.free_rectangle_count, 1);
792 assert_eq!(
793 (atlas.largest_free_width, atlas.largest_free_height),
794 (128, 256)
795 );
796 assert_eq!(atlas.fragmentation_percentage(), 0.0);
797 }
798
799 #[test]
800 fn test_atlas_limit_diagnostics() {
801 let mut manager = MultiAtlasManager::new(AtlasConfig {
802 initial_atlas_count: 1,
803 max_atlases: 1,
804 atlas_size: (256, 256),
805 auto_grow: true,
806 ..Default::default()
807 });
808 manager.try_allocate(128, 256).unwrap();
809
810 let Err(AtlasError::AtlasLimitReached {
811 max_atlases: 1,
812 diagnostics:
813 AtlasSpaceDiagnostics::Allocation {
814 width: 129,
815 height: 256,
816 atlas_width: 256,
817 atlas_height: 256,
818 max_atlases: 1,
819 atlases,
820 },
821 }) = manager.try_allocate(129, 256)
822 else {
823 panic!("expected atlas-limit diagnostics");
824 };
825 assert_eq!(atlases.len(), 1);
826 }
827
828 #[test]
829 fn test_fragmentation_per_atlas_layer() {
830 let atlases = [
831 AtlasLayerDiagnostics {
832 atlas_id: AtlasId::new(0),
833 total_area: 100,
834 free_area: 100,
835 free_rectangle_count: 1,
836 largest_free_width: 10,
837 largest_free_height: 10,
838 },
839 AtlasLayerDiagnostics {
840 atlas_id: AtlasId::new(1),
841 total_area: 100,
842 free_area: 100,
843 free_rectangle_count: 2,
844 largest_free_width: 5,
845 largest_free_height: 10,
846 },
847 ];
848
849 assert_eq!(atlases[0].fragmentation_percentage(), 0.0);
850 assert_eq!(atlases[1].fragmentation_percentage(), 50.0);
851 }
852
853 #[test]
854 fn test_texture_too_large() {
855 let mut manager = MultiAtlasManager::new(AtlasConfig {
856 atlas_size: (256, 256),
857 ..Default::default()
858 });
859
860 let result = manager.try_allocate(300, 300);
861 assert!(matches!(
862 result,
863 Err(AtlasError::TextureTooLarge {
864 width: 300,
865 height: 300,
866 max_width: 256,
867 max_height: 256,
868 })
869 ));
870 }
871
872 #[test]
873 fn test_first_fit_allocation_strategy() {
874 let mut manager = MultiAtlasManager::new(AtlasConfig {
875 initial_atlas_count: 3,
876 max_atlases: 3,
877 atlas_size: (256, 256),
878 allocation_strategy: AllocationStrategy::FirstFit,
879 auto_grow: false,
880 });
881
882 let allocation0 = manager.try_allocate(100, 100).unwrap();
884 assert_eq!(allocation0.atlas_id.as_u32(), 0);
885
886 let allocation1 = manager.try_allocate(50, 50).unwrap();
888 assert_eq!(allocation1.atlas_id.as_u32(), 0);
889
890 let allocation2 = manager.try_allocate(80, 80).unwrap();
892 assert_eq!(allocation2.atlas_id.as_u32(), 0);
893
894 let allocation3 = manager.try_allocate(200, 200).unwrap();
897 assert_eq!(allocation3.atlas_id.as_u32(), 1);
898
899 let allocation4 = manager.try_allocate(20, 20).unwrap();
901 assert_eq!(allocation4.atlas_id.as_u32(), 0);
902 }
903
904 #[test]
905 fn test_best_fit_allocation_strategy() {
906 let mut manager = MultiAtlasManager::new(AtlasConfig {
907 initial_atlas_count: 3,
908 max_atlases: 3,
909 atlas_size: (256, 256),
910 allocation_strategy: AllocationStrategy::BestFit,
911 auto_grow: false,
912 });
913
914 let allocation0 = manager.try_allocate(150, 150).unwrap();
916 assert_eq!(allocation0.atlas_id.as_u32(), 0);
917
918 let allocation1 = manager.try_allocate(100, 100).unwrap();
921 assert_eq!(allocation1.atlas_id.as_u32(), 0);
922
923 let allocation2 = manager.try_allocate(100, 100).unwrap();
926 assert_eq!(allocation2.atlas_id.as_u32(), 0);
927
928 let allocation3 = manager.try_allocate(200, 200).unwrap();
931 assert_eq!(allocation3.atlas_id.as_u32(), 1);
932
933 let allocation4 = manager.try_allocate(80, 80).unwrap();
936 assert_eq!(allocation4.atlas_id.as_u32(), 0);
937
938 let allocation5 = manager.try_allocate(80, 80).unwrap();
941 assert_eq!(allocation5.atlas_id.as_u32(), 2);
942 }
943
944 #[test]
945 fn test_least_used_allocation_strategy() {
946 let mut manager = MultiAtlasManager::new(AtlasConfig {
947 initial_atlas_count: 3,
948 max_atlases: 3,
949 atlas_size: (256, 256),
950 allocation_strategy: AllocationStrategy::LeastUsed,
951 auto_grow: false,
952 });
953
954 let allocation0 = manager.try_allocate(100, 100).unwrap();
956 assert_eq!(allocation0.atlas_id.as_u32(), 0);
957
958 let allocation1 = manager.try_allocate(50, 50).unwrap();
960 assert_eq!(allocation1.atlas_id.as_u32(), 1);
961
962 let allocation2 = manager.try_allocate(30, 30).unwrap();
964 assert_eq!(allocation2.atlas_id.as_u32(), 2);
965
966 let allocation3 = manager.try_allocate(30, 30).unwrap();
968 assert_eq!(allocation3.atlas_id.as_u32(), 2);
969 }
970
971 #[test]
972 fn test_round_robin_allocation_strategy() {
973 let mut manager = MultiAtlasManager::new(AtlasConfig {
974 initial_atlas_count: 3,
975 max_atlases: 3,
976 atlas_size: (256, 256),
977 allocation_strategy: AllocationStrategy::RoundRobin,
978 auto_grow: false,
979 });
980
981 let allocation0 = manager.try_allocate(50, 50).unwrap();
983 assert_eq!(allocation0.atlas_id.as_u32(), 0);
984
985 let allocation1 = manager.try_allocate(50, 50).unwrap();
986 assert_eq!(allocation1.atlas_id.as_u32(), 1);
987
988 let allocation2 = manager.try_allocate(50, 50).unwrap();
989 assert_eq!(allocation2.atlas_id.as_u32(), 2);
990
991 let allocation3 = manager.try_allocate(50, 50).unwrap();
993 assert_eq!(allocation3.atlas_id.as_u32(), 0);
994
995 let allocation4 = manager.try_allocate(50, 50).unwrap();
997 assert_eq!(allocation4.atlas_id.as_u32(), 1);
998 }
999
1000 #[test]
1001 fn test_auto_grow() {
1002 let mut manager = MultiAtlasManager::new(AtlasConfig {
1003 initial_atlas_count: 1,
1004 max_atlases: 3,
1005 atlas_size: (256, 256),
1006 allocation_strategy: AllocationStrategy::FirstFit,
1007 auto_grow: true,
1008 });
1009
1010 let allocation0 = manager.try_allocate(256, 256).unwrap();
1011 assert_eq!(allocation0.atlas_id.as_u32(), 0);
1012
1013 let allocation1 = manager.try_allocate(256, 256).unwrap();
1014 assert_eq!(allocation1.atlas_id.as_u32(), 1);
1015
1016 let allocation2 = manager.try_allocate(256, 256).unwrap();
1017 assert_eq!(allocation2.atlas_id.as_u32(), 2);
1018 }
1019}