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: u32,
25 pub y: u32,
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: u32, height: u32) -> Self {
48 Self {
49 id,
50 allocator: AtlasAllocator::new(guillotiere::size2(width as i32, height as i32)),
51 stats: AtlasUsageStats {
52 allocated_area: 0,
53 total_area: width * height,
54 allocated_count: 0,
55 },
56 allocation_counter: 0,
57 }
58 }
59
60 #[expect(
62 clippy::cast_sign_loss,
63 reason = "coordinates are always non-negative for valid allocations"
64 )]
65 pub fn allocate(&mut self, width: u32, height: u32) -> Option<Allocation> {
66 let alloc = self
67 .allocator
68 .allocate(guillotiere::size2(width as i32, height as i32))?;
69 self.stats.allocated_area += width * height;
70 self.stats.allocated_count += 1;
71 self.allocation_counter += 1;
72 Some(Allocation {
73 id: alloc.id,
74 x: alloc.rectangle.min.x as u32,
75 y: alloc.rectangle.min.y as u32,
76 })
77 }
78
79 pub fn deallocate(&mut self, alloc_id: AllocId, width: u32, height: u32) {
81 self.allocator.deallocate(alloc_id);
82 self.stats.allocated_area = self.stats.allocated_area.saturating_sub(width * height);
83 self.stats.allocated_count = self.stats.allocated_count.saturating_sub(1);
84 }
85
86 pub fn stats(&self) -> &AtlasUsageStats {
88 &self.stats
89 }
90}
91
92impl core::fmt::Debug for Atlas {
93 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94 f.debug_struct("Atlas")
95 .field("id", &self.id)
96 .field("stats", &self.stats)
97 .field("allocation_counter", &self.allocation_counter)
98 .finish_non_exhaustive()
99 }
100}
101
102pub struct MultiAtlasManager {
108 atlases: Vec<Atlas>,
110 config: AtlasConfig,
112 round_robin_counter: usize,
114}
115
116impl MultiAtlasManager {
117 pub fn new(config: AtlasConfig) -> Self {
119 let mut manager = Self {
120 atlases: Vec::new(),
121 config,
122 round_robin_counter: 0,
123 };
124
125 for _ in 0..config.initial_atlas_count {
126 manager
127 .create_atlas()
128 .expect("Failed to create initial atlas");
129 }
130
131 manager
132 }
133
134 pub fn config(&self) -> &AtlasConfig {
136 &self.config
137 }
138
139 pub fn create_atlas(&mut self) -> Result<AtlasId, AtlasError> {
141 if self.atlases.len() >= self.config.max_atlases {
142 return Err(AtlasError::AtlasLimitReached {
143 max_atlases: self.config.max_atlases,
144 diagnostics: AtlasSpaceDiagnostics::Unavailable,
145 });
146 }
147
148 let atlas_id = AtlasId::new(self.next_atlas_id());
149
150 let atlas = Atlas::new(atlas_id, self.config.atlas_size.0, self.config.atlas_size.1);
151 self.atlases.push(atlas);
152
153 Ok(atlas_id)
154 }
155
156 pub fn next_atlas_id(&self) -> u32 {
158 u32::try_from(self.atlases.len()).unwrap()
159 }
160
161 pub fn try_allocate(&mut self, width: u32, height: u32) -> Result<AtlasAllocation, AtlasError> {
163 self.try_allocate_excluding(width, height, None)
164 }
165
166 pub fn try_allocate_excluding(
169 &mut self,
170 width: u32,
171 height: u32,
172 exclude_atlas_id: Option<AtlasId>,
173 ) -> Result<AtlasAllocation, AtlasError> {
174 if width > self.config.atlas_size.0 || height > self.config.atlas_size.1 {
176 return Err(AtlasError::TextureTooLarge {
177 width,
178 height,
179 max_width: self.config.atlas_size.0,
180 max_height: self.config.atlas_size.1,
181 });
182 }
183
184 match self.config.allocation_strategy {
186 AllocationStrategy::FirstFit => {
187 self.allocate_first_fit(width, height, exclude_atlas_id)
188 }
189 AllocationStrategy::BestFit => self.allocate_best_fit(width, height, exclude_atlas_id),
190 AllocationStrategy::LeastUsed => {
191 self.allocate_least_used(width, height, exclude_atlas_id)
192 }
193 AllocationStrategy::RoundRobin => {
194 self.allocate_round_robin(width, height, exclude_atlas_id)
195 }
196 }
197 }
198
199 fn space_diagnostics(&self, width: u32, height: u32) -> AtlasSpaceDiagnostics {
200 let mut atlases = Vec::new();
201
202 for atlas in &self.atlases {
203 let mut free_area = 0_u64;
204 let mut free_rectangle_count = 0;
205 let mut largest_free_width = 0;
206 let mut largest_free_height = 0;
207 let mut largest_free_area = 0_u64;
208 atlas.allocator.for_each_free_rectangle(|rect| {
209 let rect_width = rect.width() as u32;
210 let rect_height = rect.height() as u32;
211 let rect_area = u64::from(rect_width) * u64::from(rect_height);
212 free_area += rect_area;
213 free_rectangle_count += 1;
214
215 if rect_area > largest_free_area {
216 largest_free_area = rect_area;
217 largest_free_width = rect_width;
218 largest_free_height = rect_height;
219 }
220 });
221
222 atlases.push(AtlasLayerDiagnostics {
223 atlas_id: atlas.id,
224 total_area: u64::from(atlas.stats.total_area),
225 free_area,
226 free_rectangle_count,
227 largest_free_width,
228 largest_free_height,
229 });
230 }
231
232 AtlasSpaceDiagnostics::Allocation {
233 width,
234 height,
235 atlas_width: self.config.atlas_size.0,
236 atlas_height: self.config.atlas_size.1,
237 max_atlases: self.config.max_atlases,
238 atlases,
239 }
240 }
241
242 fn no_space_available(&self, width: u32, height: u32) -> AtlasError {
243 AtlasError::NoSpaceAvailable(self.space_diagnostics(width, height))
244 }
245
246 fn atlas_limit_reached(&self, width: u32, height: u32) -> AtlasError {
247 AtlasError::AtlasLimitReached {
248 max_atlases: self.config.max_atlases,
249 diagnostics: self.space_diagnostics(width, height),
250 }
251 }
252
253 fn allocate_first_fit(
255 &mut self,
256 width: u32,
257 height: u32,
258 exclude_atlas_id: Option<AtlasId>,
259 ) -> Result<AtlasAllocation, AtlasError> {
260 for atlas in &mut self.atlases {
261 if Some(atlas.id) == exclude_atlas_id {
262 continue;
263 }
264
265 if let Some(allocation) = atlas.allocate(width, height) {
266 return Ok(AtlasAllocation {
267 atlas_id: atlas.id,
268 allocation,
269 });
270 }
271 }
272
273 if self.config.auto_grow {
275 let atlas_id = self
276 .create_atlas()
277 .map_err(|_| self.atlas_limit_reached(width, height))?;
278 let atlas = self.atlases.last_mut().unwrap();
279 if let Some(allocation) = atlas.allocate(width, height) {
280 return Ok(AtlasAllocation {
281 atlas_id,
282 allocation,
283 });
284 }
285 }
286
287 Err(self.no_space_available(width, height))
288 }
289
290 fn allocate_best_fit(
293 &mut self,
294 width: u32,
295 height: u32,
296 exclude_atlas_id: Option<AtlasId>,
297 ) -> Result<AtlasAllocation, AtlasError> {
298 let mut best_atlas_idx = None;
299 let mut best_remaining_space = u32::MAX;
300
301 for (idx, atlas) in self.atlases.iter().enumerate() {
303 if Some(atlas.id) == exclude_atlas_id {
304 continue;
305 }
306
307 let stats = atlas.stats();
308 let remaining_space = stats.total_area - stats.allocated_area;
309
310 if remaining_space >= width * height && remaining_space < best_remaining_space {
311 best_remaining_space = remaining_space;
312 best_atlas_idx = Some(idx);
313 }
314 }
315
316 if let Some(idx) = best_atlas_idx {
317 let atlas = &mut self.atlases[idx];
318 if let Some(allocation) = atlas.allocate(width, height) {
319 return Ok(AtlasAllocation {
320 atlas_id: atlas.id,
321 allocation,
322 });
323 }
324 }
325
326 self.allocate_first_fit(width, height, exclude_atlas_id)
328 }
329
330 fn allocate_least_used(
332 &mut self,
333 width: u32,
334 height: u32,
335 exclude_atlas_id: Option<AtlasId>,
336 ) -> Result<AtlasAllocation, AtlasError> {
337 let mut best_atlas_idx = None;
338 let mut lowest_usage = f32::MAX;
339
340 for (idx, atlas) in self.atlases.iter().enumerate() {
342 if Some(atlas.id) == exclude_atlas_id {
343 continue;
344 }
345
346 let usage = atlas.stats().usage_percentage();
347 if usage < lowest_usage {
348 lowest_usage = usage;
349 best_atlas_idx = Some(idx);
350 }
351 }
352
353 if let Some(idx) = best_atlas_idx
354 && let Some(allocation) = self.atlases[idx].allocate(width, height)
355 {
356 let atlas_id = self.atlases[idx].id;
357 return Ok(AtlasAllocation {
358 atlas_id,
359 allocation,
360 });
361 }
362
363 self.allocate_first_fit(width, height, exclude_atlas_id)
365 }
366
367 fn allocate_round_robin(
369 &mut self,
370 width: u32,
371 height: u32,
372 exclude_atlas_id: Option<AtlasId>,
373 ) -> Result<AtlasAllocation, AtlasError> {
374 if self.atlases.is_empty() {
375 return self.allocate_first_fit(width, height, exclude_atlas_id);
376 }
377
378 let start_idx = self.round_robin_counter % self.atlases.len();
379
380 for i in 0..self.atlases.len() {
382 let idx = (start_idx + i) % self.atlases.len();
383
384 if Some(self.atlases[idx].id) == exclude_atlas_id {
385 continue;
386 }
387
388 if let Some(allocation) = self.atlases[idx].allocate(width, height) {
389 let atlas_id = self.atlases[idx].id;
390 self.round_robin_counter = (idx + 1) % self.atlases.len();
391 return Ok(AtlasAllocation {
392 atlas_id,
393 allocation,
394 });
395 }
396 }
397
398 if self.config.auto_grow {
400 let atlas_id = self
401 .create_atlas()
402 .map_err(|_| self.atlas_limit_reached(width, height))?;
403 let atlas = self.atlases.last_mut().unwrap();
404 if let Some(allocation) = atlas.allocate(width, height) {
405 self.round_robin_counter = self.atlases.len() - 1;
406 return Ok(AtlasAllocation {
407 atlas_id,
408 allocation,
409 });
410 }
411 }
412
413 Err(self.no_space_available(width, height))
414 }
415
416 pub fn deallocate(
418 &mut self,
419 atlas_id: AtlasId,
420 alloc_id: AllocId,
421 width: u32,
422 height: u32,
423 ) -> Result<(), AtlasError> {
424 let atlas = self
427 .atlases
428 .get_mut(atlas_id.0 as usize)
429 .ok_or(AtlasError::AtlasNotFound(atlas_id))?;
430 atlas.deallocate(alloc_id, width, height);
431 Ok(())
432 }
433
434 pub fn atlas_stats(&self) -> Vec<(AtlasId, &AtlasUsageStats)> {
436 self.atlases
437 .iter()
438 .map(|atlas| (atlas.id, atlas.stats()))
439 .collect()
440 }
441
442 pub fn atlas_count(&self) -> usize {
444 self.atlases.len()
445 }
446}
447
448impl core::fmt::Debug for MultiAtlasManager {
449 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
450 f.debug_struct("MultiAtlasManager")
451 .field("atlas_count", &self.atlases.len())
452 .field("config", &self.config)
453 .field("next_atlas_id", &self.next_atlas_id())
454 .field("round_robin_counter", &self.round_robin_counter)
455 .field("atlases", &self.atlases)
456 .finish()
457 }
458}
459
460#[derive(Debug, Clone, Error)]
462pub enum AtlasError {
463 #[error("No space available in any atlas{0}")]
465 NoSpaceAvailable(AtlasSpaceDiagnostics),
466 #[error("Maximum atlas count reached ({max_atlases}){diagnostics}")]
468 AtlasLimitReached {
469 max_atlases: usize,
471 diagnostics: AtlasSpaceDiagnostics,
473 },
474 #[error("Texture too large ({width}x{height}) for atlas (maximum {max_width}x{max_height})")]
476 TextureTooLarge {
477 width: u32,
479 height: u32,
481 max_width: u32,
483 max_height: u32,
485 },
486 #[error("Atlas with Id {0:?} not found")]
488 AtlasNotFound(AtlasId),
489}
490
491#[derive(Clone)]
493pub enum AtlasSpaceDiagnostics {
494 Unavailable,
496 Allocation {
498 width: u32,
500 height: u32,
502 atlas_width: u32,
504 atlas_height: u32,
506 max_atlases: usize,
508 atlases: Vec<AtlasLayerDiagnostics>,
510 },
511}
512
513impl core::fmt::Debug for AtlasSpaceDiagnostics {
514 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
515 let Self::Allocation {
516 width,
517 height,
518 atlas_width,
519 atlas_height,
520 max_atlases,
521 atlases,
522 } = self
523 else {
524 return f.write_str("Unavailable");
525 };
526
527 f.debug_struct("Allocation")
528 .field("requested", &Dimensions(*width, *height))
529 .field("layer_size", &Dimensions(*atlas_width, *atlas_height))
530 .field("max_atlases", max_atlases)
531 .field("atlas_layers", atlases)
532 .finish()
533 }
534}
535
536impl core::fmt::Display for AtlasSpaceDiagnostics {
537 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
538 let Self::Allocation {
539 width,
540 height,
541 atlas_width: _,
542 atlas_height: _,
543 max_atlases,
544 atlases,
545 } = self
546 else {
547 return Ok(());
548 };
549
550 let total_area = atlases.iter().map(|atlas| atlas.total_area).sum::<u64>();
551 let free_area = atlases.iter().map(|atlas| atlas.free_area).sum::<u64>();
552 let used_percentage = if total_area == 0 {
553 0.0
554 } else {
555 (1.0 - free_area as f64 / total_area as f64) * 100.0
556 };
557 write!(
558 f,
559 ": failed to allocate {width}x{height} across {} atlas layers \
560 (maximum {max_atlases}; {used_percentage:.1}% used)",
561 atlases.len(),
562 )
563 }
564}
565
566#[derive(Clone)]
568pub struct AtlasLayerDiagnostics {
569 pub atlas_id: AtlasId,
571 pub total_area: u64,
573 pub free_area: u64,
575 pub free_rectangle_count: usize,
577 pub largest_free_width: u32,
579 pub largest_free_height: u32,
581}
582
583impl AtlasLayerDiagnostics {
584 pub fn utilization_percentage(&self) -> f64 {
586 if self.total_area == 0 {
587 0.0
588 } else {
589 (1.0 - self.free_area as f64 / self.total_area as f64) * 100.0
590 }
591 }
592
593 pub fn fragmentation_percentage(&self) -> f64 {
595 if self.free_area == 0 {
596 0.0
597 } else {
598 let largest_free_area =
599 u64::from(self.largest_free_width) * u64::from(self.largest_free_height);
600 (1.0 - largest_free_area as f64 / self.free_area as f64) * 100.0
601 }
602 }
603}
604
605impl core::fmt::Debug for AtlasLayerDiagnostics {
606 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
607 f.debug_struct("Layer")
608 .field("atlas_id", &self.atlas_id)
609 .field("utilization", &Percentage(self.utilization_percentage()))
610 .field("capacity", &self.total_area)
611 .field("free", &self.free_area)
612 .field(
613 "largest_free_rectangle",
614 &Dimensions(self.largest_free_width, self.largest_free_height),
615 )
616 .field("free_rectangles", &self.free_rectangle_count)
617 .field(
618 "fragmentation",
619 &Percentage(self.fragmentation_percentage()),
620 )
621 .finish()
622 }
623}
624
625struct Dimensions(u32, u32);
626
627impl core::fmt::Debug for Dimensions {
628 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
629 write!(f, "{}x{}", self.0, self.1)
630 }
631}
632
633struct Percentage(f64);
634
635impl core::fmt::Debug for Percentage {
636 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
637 write!(f, "{:.1}%", self.0)
638 }
639}
640
641#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
643pub struct AtlasId(pub u32);
644
645impl AtlasId {
646 pub fn new(id: u32) -> Self {
648 Self(id)
649 }
650
651 pub fn as_u32(self) -> u32 {
653 self.0
654 }
655}
656
657#[derive(Debug, Clone)]
659pub struct AtlasUsageStats {
660 pub allocated_area: u32,
662 pub total_area: u32,
664 pub allocated_count: u32,
666}
667
668impl AtlasUsageStats {
669 pub fn usage_percentage(&self) -> f32 {
671 if self.total_area == 0 {
672 0.0
673 } else {
674 self.allocated_area as f32 / self.total_area as f32
675 }
676 }
677}
678
679#[derive(Debug)]
681pub struct AtlasAllocation {
682 pub atlas_id: AtlasId,
684 pub allocation: Allocation,
686}
687
688#[derive(Debug, Clone, Copy)]
695pub struct AtlasConfig {
696 pub initial_atlas_count: usize,
700 pub max_atlases: usize,
702 pub atlas_size: (u32, u32),
705 pub auto_grow: bool,
707 pub allocation_strategy: AllocationStrategy,
709}
710
711impl Default for AtlasConfig {
712 fn default() -> Self {
713 Self {
714 initial_atlas_count: 0,
715 max_atlases: 8,
716 atlas_size: (4096, 4096),
717 auto_grow: true,
718 allocation_strategy: AllocationStrategy::FirstFit,
719 }
720 }
721}
722
723#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
725pub enum AllocationStrategy {
726 #[default]
728 FirstFit,
729 BestFit,
731 LeastUsed,
733 RoundRobin,
735}
736
737#[cfg(test)]
738mod tests {
739 use super::*;
740
741 #[test]
742 fn test_atlas_creation() {
743 let mut manager = MultiAtlasManager::new(AtlasConfig {
744 initial_atlas_count: 0,
745 ..Default::default()
746 });
747
748 let atlas_id = manager.create_atlas().unwrap();
749 assert_eq!(atlas_id.as_u32(), 0);
750 assert_eq!(manager.atlas_count(), 1);
751 }
752
753 #[test]
754 fn test_default_lazily_creates_first_atlas() {
755 let mut manager = MultiAtlasManager::new(AtlasConfig::default());
756 assert_eq!(manager.atlas_count(), 0);
757
758 let allocation = manager.try_allocate(100, 100).unwrap();
759 assert_eq!(allocation.atlas_id.as_u32(), 0);
760 assert_eq!(manager.atlas_count(), 1);
761 }
762
763 #[test]
764 fn test_allocation_strategies() {
765 let mut manager = MultiAtlasManager::new(AtlasConfig {
766 initial_atlas_count: 1,
767 max_atlases: 3,
768 atlas_size: (256, 256),
769 allocation_strategy: AllocationStrategy::FirstFit,
770 auto_grow: true,
771 });
772
773 let allocation = manager.try_allocate(100, 100).unwrap();
775 assert_eq!(allocation.atlas_id.as_u32(), 0);
776 }
777
778 #[test]
779 fn test_atlas_limit() {
780 let mut manager = MultiAtlasManager::new(AtlasConfig {
781 initial_atlas_count: 1,
782 max_atlases: 1,
783 atlas_size: (256, 256),
784 allocation_strategy: AllocationStrategy::FirstFit,
785 auto_grow: false,
786 });
787
788 assert!(matches!(
789 manager.create_atlas(),
790 Err(AtlasError::AtlasLimitReached {
791 max_atlases: 1,
792 diagnostics: AtlasSpaceDiagnostics::Unavailable,
793 })
794 ));
795 }
796
797 #[test]
798 fn test_no_space_diagnostics() {
799 let mut manager = MultiAtlasManager::new(AtlasConfig {
800 initial_atlas_count: 1,
801 max_atlases: 1,
802 atlas_size: (256, 256),
803 auto_grow: false,
804 ..Default::default()
805 });
806 manager.try_allocate(128, 256).unwrap();
807
808 let Err(AtlasError::NoSpaceAvailable(AtlasSpaceDiagnostics::Allocation {
809 width: 129,
810 height: 256,
811 atlas_width: 256,
812 atlas_height: 256,
813 max_atlases: 1,
814 atlases,
815 })) = manager.try_allocate(129, 256)
816 else {
817 panic!("expected no-space diagnostics");
818 };
819 assert_eq!(atlases.len(), 1);
820 let atlas = &atlases[0];
821 assert_eq!(atlas.atlas_id, AtlasId::new(0));
822 assert_eq!(atlas.total_area, 65_536);
823 assert_eq!(atlas.free_area, 32_768);
824 assert_eq!(atlas.free_rectangle_count, 1);
825 assert_eq!(
826 (atlas.largest_free_width, atlas.largest_free_height),
827 (128, 256)
828 );
829 assert_eq!(atlas.fragmentation_percentage(), 0.0);
830 }
831
832 #[test]
833 fn test_atlas_limit_diagnostics() {
834 let mut manager = MultiAtlasManager::new(AtlasConfig {
835 initial_atlas_count: 1,
836 max_atlases: 1,
837 atlas_size: (256, 256),
838 auto_grow: true,
839 ..Default::default()
840 });
841 manager.try_allocate(128, 256).unwrap();
842
843 let Err(AtlasError::AtlasLimitReached {
844 max_atlases: 1,
845 diagnostics:
846 AtlasSpaceDiagnostics::Allocation {
847 width: 129,
848 height: 256,
849 atlas_width: 256,
850 atlas_height: 256,
851 max_atlases: 1,
852 atlases,
853 },
854 }) = manager.try_allocate(129, 256)
855 else {
856 panic!("expected atlas-limit diagnostics");
857 };
858 assert_eq!(atlases.len(), 1);
859 }
860
861 #[test]
862 fn test_fragmentation_per_atlas_layer() {
863 let atlases = [
864 AtlasLayerDiagnostics {
865 atlas_id: AtlasId::new(0),
866 total_area: 100,
867 free_area: 100,
868 free_rectangle_count: 1,
869 largest_free_width: 10,
870 largest_free_height: 10,
871 },
872 AtlasLayerDiagnostics {
873 atlas_id: AtlasId::new(1),
874 total_area: 100,
875 free_area: 100,
876 free_rectangle_count: 2,
877 largest_free_width: 5,
878 largest_free_height: 10,
879 },
880 ];
881
882 assert_eq!(atlases[0].fragmentation_percentage(), 0.0);
883 assert_eq!(atlases[1].fragmentation_percentage(), 50.0);
884 }
885
886 #[test]
887 fn test_texture_too_large() {
888 let mut manager = MultiAtlasManager::new(AtlasConfig {
889 atlas_size: (256, 256),
890 ..Default::default()
891 });
892
893 let result = manager.try_allocate(300, 300);
894 assert!(matches!(
895 result,
896 Err(AtlasError::TextureTooLarge {
897 width: 300,
898 height: 300,
899 max_width: 256,
900 max_height: 256,
901 })
902 ));
903 }
904
905 #[test]
906 fn test_first_fit_allocation_strategy() {
907 let mut manager = MultiAtlasManager::new(AtlasConfig {
908 initial_atlas_count: 3,
909 max_atlases: 3,
910 atlas_size: (256, 256),
911 allocation_strategy: AllocationStrategy::FirstFit,
912 auto_grow: false,
913 });
914
915 let allocation0 = manager.try_allocate(100, 100).unwrap();
917 assert_eq!(allocation0.atlas_id.as_u32(), 0);
918
919 let allocation1 = manager.try_allocate(50, 50).unwrap();
921 assert_eq!(allocation1.atlas_id.as_u32(), 0);
922
923 let allocation2 = manager.try_allocate(80, 80).unwrap();
925 assert_eq!(allocation2.atlas_id.as_u32(), 0);
926
927 let allocation3 = manager.try_allocate(200, 200).unwrap();
930 assert_eq!(allocation3.atlas_id.as_u32(), 1);
931
932 let allocation4 = manager.try_allocate(20, 20).unwrap();
934 assert_eq!(allocation4.atlas_id.as_u32(), 0);
935 }
936
937 #[test]
938 fn test_best_fit_allocation_strategy() {
939 let mut manager = MultiAtlasManager::new(AtlasConfig {
940 initial_atlas_count: 3,
941 max_atlases: 3,
942 atlas_size: (256, 256),
943 allocation_strategy: AllocationStrategy::BestFit,
944 auto_grow: false,
945 });
946
947 let allocation0 = manager.try_allocate(150, 150).unwrap();
949 assert_eq!(allocation0.atlas_id.as_u32(), 0);
950
951 let allocation1 = manager.try_allocate(100, 100).unwrap();
954 assert_eq!(allocation1.atlas_id.as_u32(), 0);
955
956 let allocation2 = manager.try_allocate(100, 100).unwrap();
959 assert_eq!(allocation2.atlas_id.as_u32(), 0);
960
961 let allocation3 = manager.try_allocate(200, 200).unwrap();
964 assert_eq!(allocation3.atlas_id.as_u32(), 1);
965
966 let allocation4 = manager.try_allocate(80, 80).unwrap();
969 assert_eq!(allocation4.atlas_id.as_u32(), 0);
970
971 let allocation5 = manager.try_allocate(80, 80).unwrap();
974 assert_eq!(allocation5.atlas_id.as_u32(), 2);
975 }
976
977 #[test]
978 fn test_least_used_allocation_strategy() {
979 let mut manager = MultiAtlasManager::new(AtlasConfig {
980 initial_atlas_count: 3,
981 max_atlases: 3,
982 atlas_size: (256, 256),
983 allocation_strategy: AllocationStrategy::LeastUsed,
984 auto_grow: false,
985 });
986
987 let allocation0 = manager.try_allocate(100, 100).unwrap();
989 assert_eq!(allocation0.atlas_id.as_u32(), 0);
990
991 let allocation1 = manager.try_allocate(50, 50).unwrap();
993 assert_eq!(allocation1.atlas_id.as_u32(), 1);
994
995 let allocation2 = manager.try_allocate(30, 30).unwrap();
997 assert_eq!(allocation2.atlas_id.as_u32(), 2);
998
999 let allocation3 = manager.try_allocate(30, 30).unwrap();
1001 assert_eq!(allocation3.atlas_id.as_u32(), 2);
1002 }
1003
1004 #[test]
1005 fn test_round_robin_allocation_strategy() {
1006 let mut manager = MultiAtlasManager::new(AtlasConfig {
1007 initial_atlas_count: 3,
1008 max_atlases: 3,
1009 atlas_size: (256, 256),
1010 allocation_strategy: AllocationStrategy::RoundRobin,
1011 auto_grow: false,
1012 });
1013
1014 let allocation0 = manager.try_allocate(50, 50).unwrap();
1016 assert_eq!(allocation0.atlas_id.as_u32(), 0);
1017
1018 let allocation1 = manager.try_allocate(50, 50).unwrap();
1019 assert_eq!(allocation1.atlas_id.as_u32(), 1);
1020
1021 let allocation2 = manager.try_allocate(50, 50).unwrap();
1022 assert_eq!(allocation2.atlas_id.as_u32(), 2);
1023
1024 let allocation3 = manager.try_allocate(50, 50).unwrap();
1026 assert_eq!(allocation3.atlas_id.as_u32(), 0);
1027
1028 let allocation4 = manager.try_allocate(50, 50).unwrap();
1030 assert_eq!(allocation4.atlas_id.as_u32(), 1);
1031 }
1032
1033 #[test]
1034 fn test_auto_grow() {
1035 let mut manager = MultiAtlasManager::new(AtlasConfig {
1036 initial_atlas_count: 1,
1037 max_atlases: 3,
1038 atlas_size: (256, 256),
1039 allocation_strategy: AllocationStrategy::FirstFit,
1040 auto_grow: true,
1041 });
1042
1043 let allocation0 = manager.try_allocate(256, 256).unwrap();
1044 assert_eq!(allocation0.atlas_id.as_u32(), 0);
1045
1046 let allocation1 = manager.try_allocate(256, 256).unwrap();
1047 assert_eq!(allocation1.atlas_id.as_u32(), 1);
1048
1049 let allocation2 = manager.try_allocate(256, 256).unwrap();
1050 assert_eq!(allocation2.atlas_id.as_u32(), 2);
1051 }
1052
1053 fn test_allocate_excluding_with_strategy(strategy: AllocationStrategy) {
1054 let mut manager = MultiAtlasManager::new(AtlasConfig {
1055 initial_atlas_count: 3,
1056 max_atlases: 3,
1057 atlas_size: (256, 256),
1058 allocation_strategy: strategy,
1059 auto_grow: false,
1060 });
1061
1062 let allocation0 = manager.try_allocate(100, 100).unwrap();
1063 let first_atlas = allocation0.atlas_id;
1064 let allocation1 = manager
1065 .try_allocate_excluding(256, 256, Some(first_atlas))
1066 .unwrap();
1067 assert_ne!(allocation1.atlas_id, first_atlas);
1068
1069 let second_atlas = allocation1.atlas_id;
1070 let allocation2 = manager
1071 .try_allocate_excluding(100, 100, Some(second_atlas))
1072 .unwrap();
1073 assert_ne!(allocation2.atlas_id, second_atlas);
1074
1075 let allocation3 = manager
1076 .try_allocate_excluding(100, 100, Some(first_atlas))
1077 .unwrap();
1078 assert_ne!(allocation3.atlas_id, first_atlas);
1079 }
1080
1081 #[test]
1082 fn test_allocate_excluding_first_fit() {
1083 test_allocate_excluding_with_strategy(AllocationStrategy::FirstFit);
1084 }
1085
1086 #[test]
1087 fn test_allocate_excluding_best_fit() {
1088 test_allocate_excluding_with_strategy(AllocationStrategy::BestFit);
1089 }
1090
1091 #[test]
1092 fn test_allocate_excluding_least_used() {
1093 test_allocate_excluding_with_strategy(AllocationStrategy::LeastUsed);
1094 }
1095
1096 #[test]
1097 fn test_allocate_excluding_round_robin() {
1098 test_allocate_excluding_with_strategy(AllocationStrategy::RoundRobin);
1099 }
1100}