etagere/
bucketed.rs

1use std::num::Wrapping;
2use std::u16;
3
4use crate::{AllocatorOptions, DEFAULT_OPTIONS, Allocation, AllocId, Size, Rectangle, point2, size2};
5
6const BIN_BITS: u32 = 12;
7const ITEM_BITS: u32 = 12;
8const GEN_BITS: u32 = 8;
9
10const BIN_MASK: u32   = (1 << BIN_BITS) - 1;
11const ITEM_MASK: u32  = ((1 << ITEM_BITS) - 1) << BIN_BITS;
12const GEN_MASK: u32   = ((1 << GEN_BITS) - 1) << (BIN_BITS + ITEM_BITS);
13
14const MAX_ITEMS_PER_BIN: u16 = (ITEM_MASK >> 12) as u16;
15const MAX_BIN_COUNT: usize = BIN_MASK as usize;
16const MAX_SHELF_COUNT: usize = u16::MAX as usize;
17
18#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
20struct BucketIndex(u16);
21
22impl BucketIndex {
23    fn to_usize(self) -> usize {
24        self.0 as usize
25    }
26
27    const INVALID: Self = BucketIndex(u16::MAX);
28}
29
30#[derive(Clone)]
31#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
32struct Shelf {
33    x: u16,
34    y: u16,
35    height: u16,
36    bucket_width: u16,
37
38    first_bucket: BucketIndex,
39}
40
41#[derive(Clone)]
42#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
43struct Bucket {
44    x: u16,
45    free_space: u16,
46
47    next: BucketIndex,
48
49    /// Buckets are cleared when their reference count goes back to zero.
50    refcount: u16,
51    /// Similar to refcount except that the counter is not decremented
52    /// when an item is deallocated. We only use this so that allocation
53    /// ids are unique within a bucket.
54    item_count: u16,
55    shelf: u16,
56    generation: Wrapping<u8>,
57}
58
59/// A faster but less precise Shelf-packing dynamic texture atlas allocator, inspired by https://github.com/mapbox/shelf-pack/
60///
61/// Items are accumulated into buckets which are laid out in rows (shelves) of variable height.
62/// When allocating we first look for a suitable bucket. If none is found, a new shelf of the desired height
63/// is pushed.
64///
65/// Lifetime isn't tracked at item granularity. Instead, items are grouped into buckets and deallocation happens
66/// per bucket when all items of the buckets are removed.
67/// When the top-most shelf is empty, it is removed, potentially cascading into garbage-collecting the next
68/// shelf, etc.
69///
70/// This allocator works well when there are a lot of small items with similar sizes (typically, glyph atlases).
71#[derive(Clone)]
72#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
73pub struct BucketedAtlasAllocator {
74    shelves: Vec<Shelf>,
75    buckets: Vec<Bucket>,
76    available_height: u16,
77    width: u16,
78    height: u16,
79    first_unallocated_bucket: BucketIndex,
80    flip_xy: bool,
81    alignment: Size,
82    current_column: u16,
83    column_width: u16,
84    num_columns: u16,
85    allocated_space: i32,
86}
87
88impl BucketedAtlasAllocator {
89    /// Create an atlas allocator with provided options.
90    pub fn with_options(size: Size, options: &AllocatorOptions) -> Self {
91        assert!(size.width < u16::MAX as i32);
92        assert!(size.height < u16::MAX as i32);
93
94        let (width, height, shelf_alignment) = if options.vertical_shelves {
95            (size.height as u16, size.width as u16, options.alignment.height as u16)
96        } else {
97            (size.width as u16, size.height as u16, options.alignment.width as u16)
98        };
99
100        let mut column_width = width / (options.num_columns as u16);
101        column_width = column_width - column_width % shelf_alignment;
102
103        BucketedAtlasAllocator {
104            shelves: Vec::new(),
105            buckets: Vec::new(),
106            available_height: height,
107            width,
108            height,
109            first_unallocated_bucket: BucketIndex::INVALID,
110            flip_xy: options.vertical_shelves,
111            alignment: options.alignment,
112            current_column: 0,
113            num_columns: options.num_columns as u16,
114            column_width,
115            allocated_space: 0,
116        }
117    }
118
119    /// Create an atlas allocator with default options.
120    pub fn new(size: Size) -> Self {
121        Self::with_options(size, &DEFAULT_OPTIONS)
122    }
123
124    pub fn clear(&mut self) {
125        self.shelves.clear();
126        self.buckets.clear();
127        self.first_unallocated_bucket = BucketIndex::INVALID;
128        self.available_height = self.height;
129        self.current_column = 0;
130        self.allocated_space = 0;
131    }
132
133    pub fn size(&self) -> Size {
134        let (w, h) = convert_coordinates(self.flip_xy, self.width, self.height);
135        size2(w as i32, h as i32)
136    }
137
138    pub fn grow(&mut self, new_size: Size) {
139        assert!(new_size.width < u16::MAX as i32);
140        assert!(new_size.height < u16::MAX as i32);
141
142        let (new_width, new_height) = if self.flip_xy {
143            (new_size.height as u16, new_size.width as u16)
144        } else {
145            (new_size.width as u16, new_size.height as u16)
146        };
147
148        assert!(new_width >= self.width);
149        assert!(new_height >= self.height);
150
151        self.available_height += new_height - self.height;
152        self.width = new_width;
153        self.height = new_height;
154
155        if self.num_columns == 1 {
156            // Add as many new buckets as possible to the existing shelves.
157            let additional_width = self.width - self.column_width;
158
159            let len = self.shelves.len();
160
161            for shelf_index in 0..len {
162                let shelf = &self.shelves[shelf_index];
163                let mut x = self.column_width;
164                let bucket_width = shelf.bucket_width;
165
166                let max_new_buckets = (MAX_BIN_COUNT - self.buckets.len()) as u16;
167                let mut num_buckets_to_add = additional_width / bucket_width;
168                num_buckets_to_add = num_buckets_to_add.min(max_new_buckets);
169
170                let mut bucket_next = shelf.first_bucket;
171
172                for _ in 0..num_buckets_to_add {
173                    let bucket = Bucket {
174                        next: bucket_next,
175                        x,
176                        free_space: bucket_width,
177                        refcount: 0,
178                        shelf: shelf_index as u16,
179                        generation: Wrapping(0),
180                        item_count: 0,
181                    };
182
183                    x += bucket_width;
184
185                    let bucket_index = self.add_bucket(bucket);
186
187                    bucket_next = bucket_index;
188                }
189
190                self.shelves[shelf_index].first_bucket = bucket_next;
191            }
192
193            // Resize the existing column.
194            self.column_width = self.width;
195        } else {
196            // Add as many new columns as possible.
197            self.num_columns = self.width / self.column_width;
198        }
199    }
200
201    pub fn is_empty(&self) -> bool {
202        self.shelves.is_empty()
203    }
204
205    /// Allocate a rectangle in the atlas.
206    pub fn allocate(&mut self, mut requested_size: Size) -> Option<Allocation> {
207        if requested_size.is_empty()
208            || requested_size.width > std::u16::MAX as i32
209            || requested_size.height > std::u16::MAX as i32 {
210            return None;
211        }
212
213        adjust_size(self.alignment.width, &mut requested_size.width);
214        adjust_size(self.alignment.height, &mut requested_size.height);
215
216        if requested_size.width > self.column_width as i32 || requested_size.height > self.height as i32 {
217            return None;
218        }
219
220        let (w, h) = convert_coordinates(self.flip_xy, requested_size.width as u16, requested_size.height as u16);
221
222        let mut selected_shelf = std::usize::MAX;
223        let mut selected_bucket = BucketIndex::INVALID;
224        let mut best_waste = u16::MAX;
225
226        let can_add_shelf = (self.available_height >= h || self.current_column + 1 < self.num_columns)
227            && self.shelves.len() < MAX_SHELF_COUNT
228            && self.buckets.len() < MAX_BIN_COUNT;
229
230        'shelves: for (shelf_index, shelf) in self.shelves.iter().enumerate() {
231            if shelf.height < h || shelf.bucket_width < w {
232                continue;
233            }
234
235            let y_waste = shelf.height - h;
236            if y_waste > best_waste || (can_add_shelf && y_waste > h) {
237                continue;
238            }
239
240            let mut bucket_index = shelf.first_bucket;
241            while bucket_index != BucketIndex::INVALID {
242                let bucket = &self.buckets[bucket_index.to_usize()];
243
244                if bucket.free_space >= w && bucket.item_count < MAX_ITEMS_PER_BIN {
245                    if y_waste == 0 && bucket.free_space == w {
246                        selected_shelf = shelf_index;
247                        selected_bucket = bucket_index;
248
249                        break 'shelves;
250                    }
251
252                    if y_waste < best_waste {
253                        best_waste = y_waste;
254                        selected_shelf = shelf_index;
255                        selected_bucket = bucket_index;
256                        break;
257                    }
258                }
259
260                bucket_index = bucket.next;
261            }
262        }
263
264        if selected_bucket == BucketIndex::INVALID {
265            if can_add_shelf {
266                selected_shelf = self.add_shelf(w, h);
267                selected_bucket = self.shelves[selected_shelf].first_bucket;
268            } else {
269                // Attempt to merge some empty shelves to make a big enough spot.
270                let selected = self.coalesce_shelves(w, h);
271                selected_shelf = selected.0;
272                selected_bucket = selected.1;
273            }
274        }
275
276        if selected_bucket != BucketIndex::INVALID {
277            return self.alloc_from_bucket(selected_shelf, selected_bucket, w);
278        }
279
280        return  None;
281    }
282
283    /// Deallocate a rectangle in the atlas.
284    ///
285    /// Space is only reclaimed when all items of the same bucket are deallocated.
286    pub fn deallocate(&mut self, id: AllocId) {
287        if self.deallocate_from_bucket(id) {
288            self.cleanup_shelves();
289        }
290
291        self.check()
292    }
293
294    /// Amount of occupied space in the atlas.
295    pub fn allocated_space(&self) -> i32 {
296        self.allocated_space
297    }
298
299    /// How much space is available for future allocations.
300    pub fn free_space(&self) -> i32 {
301        (self.width as i32 * self.height as i32) - self.allocated_space
302    }
303
304    fn alloc_from_bucket(&mut self, shelf_index: usize, bucket_index: BucketIndex, width: u16) -> Option<Allocation> {
305        let shelf = &mut self.shelves[shelf_index];
306        let bucket = &mut self.buckets[bucket_index.to_usize()];
307
308        debug_assert!(bucket.free_space >= width);
309
310        let min_x = bucket.x + shelf.bucket_width - bucket.free_space;
311        let min_y = shelf.y;
312        let max_x = min_x + width;
313        let max_y = min_y + shelf.height;
314
315        let (min_x, min_y) = convert_coordinates(self.flip_xy, min_x, min_y);
316        let (max_x, max_y) = convert_coordinates(self.flip_xy, max_x, max_y);
317
318        bucket.free_space -= width;
319        bucket.refcount += 1;
320        bucket.item_count += 1;
321
322        let id = AllocId(
323            (bucket_index.0 as u32) & BIN_MASK
324            | ((bucket.item_count as u32) << 12) & ITEM_MASK
325            | (bucket.generation.0 as u32) << 24
326        );
327
328        let rectangle = Rectangle {
329            min: point2(min_x as i32, min_y as i32),
330            max: point2(max_x as i32, max_y as i32),
331        };
332
333        self.allocated_space += rectangle.size().area();
334
335        self.check();
336
337        Some(Allocation { id, rectangle })
338    }
339
340    fn add_bucket(&mut self, mut bucket: Bucket) -> BucketIndex {
341        let mut bucket_index = self.first_unallocated_bucket;
342
343        if bucket_index == BucketIndex::INVALID {
344            bucket_index = BucketIndex(self.buckets.len() as u16);
345            self.buckets.push(bucket);
346        } else {
347            let idx = bucket_index.to_usize();
348            bucket.generation = self.buckets[idx].generation + Wrapping(1);
349            self.first_unallocated_bucket = self.buckets[idx].next;
350            self.buckets[idx] = bucket;
351        }
352
353        bucket_index
354    }
355
356    fn add_shelf(&mut self, width: u16, height: u16) -> usize {
357
358        let can_add_column = self.current_column + 1 < self.num_columns;
359
360        if self.available_height != 0 && self.available_height < height && can_add_column {
361            // We have room to add a shelf in a new column but current one doesn't have
362            // enough available space. First add a shelf to fill the current column's
363            // remaining height.
364            self.add_shelf(0, self.available_height);
365            debug_assert_eq!(self.available_height, 0);
366        }
367
368        if self.available_height == 0 && can_add_column {
369            self.current_column += 1;
370            self.available_height = self.height;
371        }
372
373        let height = shelf_height(height).min(self.available_height);
374        let num_buckets = self.num_buckets(width, height);
375        let mut bucket_width = self.column_width / num_buckets;
376        bucket_width = bucket_width - (bucket_width % self.alignment.width as u16); // TODO
377        let y = self.height - self.available_height;
378        self.available_height -= height;
379
380        let shelf_index = self.shelves.len();
381
382        // Initialize the buckets for our new shelf.
383        let mut x = self.current_column * self.column_width;
384        let mut bucket_next = BucketIndex::INVALID;
385        for _ in 0..num_buckets {
386            let bucket = Bucket {
387                next: bucket_next,
388                x,
389                free_space: bucket_width,
390                refcount: 0,
391                shelf: shelf_index as u16,
392                generation: Wrapping(0),
393                item_count: 0,
394            };
395
396            x += bucket_width;
397
398            let bucket_index = self.add_bucket(bucket);
399
400            bucket_next = bucket_index;
401        }
402
403        self.shelves.push(Shelf {
404            x: self.current_column * self.column_width,
405            y,
406            height,
407            bucket_width,
408            first_bucket: bucket_next,
409        });
410
411        shelf_index
412    }
413
414    /// Find a sequence of consecutive shelves that can be coalesced into a single one
415    /// tall enough to fit the provided size.
416    ///
417    /// If such a sequence is found, grow the height of first shelf and squash the other
418    /// ones to zero.
419    /// The squashed shelves are not removed, their height is just set to zero so no item
420    /// can go in, and they will be garbage-collected whenever there's no shelf above them.
421    /// For simplicity, the bucket width is not modified.
422
423    fn coalesce_shelves(&mut self, w: u16, h: u16) -> (usize, BucketIndex) {
424        let len = self.shelves.len();
425        let mut coalesce_range = None;
426        let mut coalesced_height = 0;
427
428        'outer: for shelf_index in 0..len {
429            if self.shelves[shelf_index].bucket_width < w {
430                continue;
431            }
432            if !self.shelf_is_empty(shelf_index) {
433                continue;
434            }
435            let shelf_x = self.shelves[shelf_index].x;
436            coalesced_height = self.shelves[shelf_index].height;
437            for i in 1..3 {
438                if self.shelves[shelf_index + i].x != shelf_x {
439                    // Can't coalesce shelves from different columns.
440                    continue 'outer;
441                }
442
443                if shelf_index + i >= len {
444                    break 'outer;
445                }
446
447                if !self.shelf_is_empty(shelf_index + i) {
448                    continue 'outer;
449                }
450
451                coalesced_height += self.shelves[shelf_index + i].height;
452
453                if coalesced_height >= h {
454                    coalesce_range = Some(shelf_index .. (shelf_index + i + 1));
455                    break 'outer;
456                }
457            }
458        }
459
460        if let Some(range) = coalesce_range {
461            let y_top = self.shelves[range.start].y + coalesced_height;
462            for i in range.start + 1 .. range.end {
463                self.shelves[i].y = y_top;
464                self.shelves[i].height = 0;
465            }
466
467            let shelf_index = range.start;
468            let shelf = &mut self.shelves[shelf_index];
469            shelf.height = coalesced_height;
470
471            return (shelf_index, shelf.first_bucket);
472        }
473
474        (0, BucketIndex::INVALID)
475    }
476
477    fn num_buckets(&self, width: u16, height: u16) -> u16 {
478        match self.column_width / u16::max(width, height) {
479            0 ..= 4 => 1,
480            5 ..= 16 => 2,
481            17 ..= 32 => 4,
482            n => (n /16 - 1).next_power_of_two(),
483        }.min((MAX_BIN_COUNT - self.buckets.len()) as u16)
484    }
485
486    /// Returns true if we should garbage-collect the shelves as a result of
487    /// removing this element (we deallocated the last item from the bucket on
488    /// the top-most shelf).
489    fn deallocate_from_bucket(&mut self, id: AllocId) -> bool {
490        let bucket_index = (id.0 & BIN_MASK) as usize;
491        let generation = ((id.0 & GEN_MASK) >> 24 ) as u8;
492
493        let bucket = &mut self.buckets[bucket_index];
494
495        let expected_generation = bucket.generation.0;
496        assert_eq!(generation, expected_generation);
497
498        assert!(bucket.refcount > 0);
499        bucket.refcount -= 1;
500
501        let shelf = &self.shelves[bucket.shelf as usize];
502
503        let bucket_is_empty = bucket.refcount == 0;
504        if bucket_is_empty {
505            self.allocated_space -= (shelf.bucket_width - bucket.free_space) as i32 * shelf.height as i32;
506            bucket.free_space = shelf.bucket_width;
507        }
508
509        bucket_is_empty && bucket.shelf as usize == self.shelves.len() - 1
510    }
511
512    fn cleanup_shelves(&mut self) {
513        while self.shelves.len() > 0 {
514            {
515                let shelf = self.shelves.last().unwrap();
516                let mut bucket_index = shelf.first_bucket;
517                let mut last_bucket = shelf.first_bucket;
518
519                while bucket_index != BucketIndex::INVALID {
520                    let bucket = &self.buckets[bucket_index.to_usize()];
521
522                    if bucket.refcount != 0 {
523                        return;
524                    }
525
526                    last_bucket = bucket_index;
527                    bucket_index = bucket.next;
528                }
529
530                // We didn't run into any bucket on this shelf with live elements,
531                // this means we can remove it.
532
533                // Can't have a shelf with no buckets.
534                debug_assert!(last_bucket != BucketIndex::INVALID);
535                // Add the buckets to the free list.
536                self.buckets[last_bucket.to_usize()].next = self.first_unallocated_bucket;
537                self.first_unallocated_bucket = shelf.first_bucket;
538
539                if shelf.y == 0 && self.current_column > 0 {
540                    self.current_column -= 1;
541                    let prev_shelf = &self.shelves[self.shelves.len() - 2];
542                    self.available_height = self.height - (prev_shelf.y + prev_shelf.height);
543                } else {
544                    // Reclaim the height of the shelf.
545                    self.available_height += shelf.height;
546                }
547            }
548
549            self.shelves.pop();
550        }
551    }
552
553    fn shelf_is_empty(&self, idx: usize) -> bool {
554        let shelf = &self.shelves[idx];
555        let mut bucket_index = shelf.first_bucket;
556
557        while bucket_index != BucketIndex::INVALID {
558            let bucket = &self.buckets[bucket_index.to_usize()];
559
560            if bucket.refcount != 0 {
561                return false;
562            }
563
564            bucket_index = bucket.next;
565        }
566
567        true
568    }
569
570
571    /// Dump a visual representation of the atlas in SVG format.
572    pub fn dump_svg(&self, output: &mut dyn std::io::Write) -> std::io::Result<()> {
573        use svg_fmt::*;
574
575        writeln!(
576            output,
577            "{}",
578            BeginSvg {
579                w: self.width as f32,
580                h: self.height as f32
581            }
582        )?;
583
584        self.dump_into_svg(None, output)?;
585
586        writeln!(output, "{}", EndSvg)
587    }
588
589
590    #[cfg(not(feature = "checks"))]
591    fn check(&self) {}
592
593    #[cfg(feature = "checks")]
594    fn check(&self) {
595        let mut h = 0;
596        for shelf in &self.shelves {
597            h += shelf.height;
598        }
599        h += self.available_height;
600
601        // Total height must be a multiple of the actual height, up to height * num_columns.
602        assert_eq!(h % self.height, 0);
603        assert!(h <= self.height * self.num_columns);
604        assert!(h >= self.height);
605
606        assert_eq!(self.is_empty(), self.allocated_space() == 0)
607    }
608
609    /// Dump a visual representation of the atlas in SVG, omitting the beginning and end of the
610    /// SVG document, so that it can be included in a larger document.
611    ///
612    /// If a rectangle is provided, translate and scale the output to fit it.
613    pub fn dump_into_svg(&self, rect: Option<&Rectangle>, output: &mut dyn std::io::Write) -> std::io::Result<()> {
614        use svg_fmt::*;
615
616        let (sx, sy, tx, ty) = if let Some(rect) = rect {
617            (
618                rect.size().width as f32 / self.width as f32,
619                rect.size().height as f32 / self.height as f32,
620                rect.min.x as f32,
621                rect.min.y as f32,
622            )
623        } else {
624            (1.0, 1.0, 0.0, 0.0)
625        };
626
627        writeln!(
628            output,
629            r#"    {}"#,
630            rectangle(tx, ty, self.width as f32 * sx, self.height as f32 * sy)
631                .fill(rgb(40, 40, 40))
632                .stroke(Stroke::Color(black(), 1.0))
633        )?;
634
635
636        for shelf in &self.shelves {
637            let mut bucket_index = shelf.first_bucket;
638
639            let y = shelf.y as f32 * sy;
640            let h = shelf.height as f32 * sy;
641            while bucket_index != BucketIndex::INVALID {
642                let bucket = &self.buckets[bucket_index.to_usize()];
643
644                let x = bucket.x as f32 * sx;
645                let w = (shelf.bucket_width - bucket.free_space) as f32 * sx;
646
647                {
648                    let (x, y) = if self.flip_xy { (y, x) } else { (x, y) };
649                    let (w, h) = if self.flip_xy { (h, w) } else { (w, h) };
650
651                    writeln!(
652                        output,
653                        r#"    {}"#,
654                        rectangle(x + tx, y + ty, w, h)
655                            .fill(rgb(70, 70, 180))
656                            .stroke(Stroke::Color(black(), 1.0))
657                    )?;
658                }
659
660                if bucket.free_space > 0 {
661                    let x_free = x + w;
662                    let w_free = bucket.free_space as f32 * sx;
663
664                    let (x_free, y) = if self.flip_xy { (y, x_free) } else { (x_free, y) };
665                    let (w_free, h) = if self.flip_xy { (h, w_free) } else { (w_free, h) };
666
667                    writeln!(
668                        output,
669                        r#"    {}"#,
670                        rectangle(x_free + tx, y + ty, w_free, h)
671                            .fill(rgb(50, 50, 50))
672                            .stroke(Stroke::Color(black(), 1.0))
673                    )?;
674                }
675
676                bucket_index = bucket.next;
677            }
678        }
679
680        Ok(())
681    }
682}
683
684fn convert_coordinates(flip_xy: bool, x: u16, y: u16) -> (u16, u16) {
685    if flip_xy {
686        (y, x)
687    } else {
688        (x, y)
689    }
690}
691
692
693fn shelf_height(mut size: u16) -> u16 {
694    let alignment = match size {
695        0 ..= 31 => 8,
696        32 ..= 127 => 16,
697        128 ..= 511 => 32,
698        _ => 64,
699    };
700
701    let rem = size % alignment;
702    if rem > 0 {
703        size += alignment - rem;
704    }
705
706    size
707}
708
709fn adjust_size(alignment: i32, size: &mut i32) {
710    let rem = *size % alignment;
711    if rem > 0 {
712        *size += alignment - rem;
713    }
714}
715
716#[test]
717fn atlas_basic() {
718    let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
719
720    let full = atlas.allocate(size2(1000, 1000)).unwrap().id;
721    assert!(atlas.allocate(size2(1, 1)).is_none());
722
723    atlas.deallocate(full);
724    let a = atlas.allocate(size2(10, 10)).unwrap().id;
725    let b = atlas.allocate(size2(50, 30)).unwrap().id;
726    let c = atlas.allocate(size2(12, 45)).unwrap().id;
727    let d = atlas.allocate(size2(60, 45)).unwrap().id;
728    let e = atlas.allocate(size2(1, 1)).unwrap().id;
729    let f = atlas.allocate(size2(128, 128)).unwrap().id;
730    let g = atlas.allocate(size2(256, 256)).unwrap().id;
731
732    atlas.deallocate(b);
733    atlas.deallocate(f);
734    atlas.deallocate(c);
735    atlas.deallocate(e);
736    let h = atlas.allocate(size2(500, 200)).unwrap().id;
737    atlas.deallocate(a);
738    let i = atlas.allocate(size2(500, 200)).unwrap().id;
739    atlas.deallocate(g);
740    atlas.deallocate(h);
741    atlas.deallocate(d);
742    atlas.deallocate(i);
743
744    let full = atlas.allocate(size2(1000, 1000)).unwrap().id;
745    assert!(atlas.allocate(size2(1, 1)).is_none());
746    atlas.deallocate(full);
747}
748
749#[test]
750fn test_coalesce_shelves() {
751    let mut atlas = BucketedAtlasAllocator::new(size2(256, 256));
752
753    // Allocate 7 shelves (leaving 32px of remaining space on top).
754    let mut ids = Vec::new();
755    for _ in 0..7 {
756        for _ in 0..8 {
757            ids.push(atlas.allocate(size2(32, 32)).unwrap().id)
758        }
759    }
760
761    // Free the first shelf.
762    for i in 0..8 {
763        atlas.deallocate(ids[i]);
764    }
765
766    // Free the 3rd and 4th shelf.
767    for i in 16..32 {
768        atlas.deallocate(ids[i]);
769    }
770
771    // Not enough space left in existing shelves and above.
772    // even coalescing is not sufficient.
773    assert!(atlas.allocate(size2(70, 70)).is_none());
774
775    // Not enough space left in existing shelves and above.
776    // The 3rd and 4th row can be coalesced to fit this allocation, though.
777    let id = atlas.allocate(size2(64, 64)).unwrap().id;
778
779    // Deallocate everything
780    for i in 8..16 {
781        atlas.deallocate(ids[i]);
782    }
783
784    atlas.deallocate(id);
785
786    for i in 32..56 {
787        atlas.deallocate(ids[i]);
788    }
789
790    //dump_svg(&atlas, &mut std::fs::File::create("tmp.svg").expect("!!"));
791
792    assert!(atlas.is_empty());
793    assert_eq!(atlas.allocated_space(), 0);
794}
795
796#[test]
797fn grow_vertically() {
798    let mut atlas = BucketedAtlasAllocator::new(size2(256, 256));
799
800    // Allocate 7 shelves (leaving 32px of remaining space on top).
801    let mut ids = Vec::new();
802    for _ in 0..7 {
803        for _ in 0..8 {
804            ids.push(atlas.allocate(size2(32, 32)).unwrap().id)
805        }
806    }
807
808    // Free the first shelf.
809    for i in 0..8 {
810        atlas.deallocate(ids[i]);
811    }
812
813    // Free the 3rd and 4th shelf.
814    for i in 16..32 {
815        atlas.deallocate(ids[i]);
816    }
817
818    // Not enough space left in existing shelves and above.
819    // even coalescing is not sufficient.
820    assert!(atlas.allocate(size2(70, 70)).is_none());
821
822    // Grow just enough vertically to fit the previous region
823    atlas.grow(size2(256, 256 + 70 - 32));
824
825    // Allocation should succeed now
826    assert!(atlas.allocate(size2(70, 70)).is_some());
827}
828
829#[test]
830fn grow_horizontally() {
831    let mut atlas = BucketedAtlasAllocator::new(size2(256, 256));
832
833    // Allocate 7 shelves (leaving 32px of remaining space on top).
834    let mut ids = Vec::new();
835    for _ in 0..7 {
836        for _ in 0..8 {
837            ids.push(atlas.allocate(size2(32, 32)).unwrap().id)
838        }
839    }
840
841    // Free the first shelf.
842    for i in 0..8 {
843        atlas.deallocate(ids[i]);
844    }
845
846    // Free the 3rd and 4th shelf.
847    for i in 16..32 {
848        atlas.deallocate(ids[i]);
849    }
850
851    // Not enough space left in existing shelves and above.
852    // even coalescing is not sufficient.
853    assert!(atlas.allocate(size2(512, 32)).is_none());
854
855    // Grow just enough horizontally to add more buckets
856    atlas.grow(size2(256 * 2, 256));
857
858    // Allocation should succeed now
859    assert!(atlas.allocate(size2(512, 32)).is_some());
860}
861
862#[test]
863fn grow_to_fit_allocation() {
864    let mut atlas = BucketedAtlasAllocator::new(size2(32, 32));
865
866    // Allocate a shelve to make sure we have a non-empty atlas to test the update.
867    atlas.allocate(size2(32, 32)).unwrap();
868
869    // Try to make a big allocation that doesn't fit.
870    let big_allocation = size2(256, 256);
871
872    assert!(atlas.allocate(big_allocation).is_none());
873
874    // Grow to make enough space for the wanted allocation plus the original shelf.
875    atlas.grow(size2(256, 32 + 256));
876
877    // Adding to the original shelf should succeed.
878    assert!(atlas.allocate(size2(32, 32)).is_some());
879
880    // Big allocation should also succeed now.
881    assert!(atlas.allocate(big_allocation).is_some());
882}
883
884#[test]
885fn columns() {
886    let mut atlas = BucketedAtlasAllocator::with_options(size2(64, 64), &AllocatorOptions {
887        num_columns: 2,
888        ..DEFAULT_OPTIONS
889    });
890
891    let a = atlas.allocate(size2(24, 46)).unwrap();
892    let b = atlas.allocate(size2(24, 32)).unwrap();
893    let c = atlas.allocate(size2(24, 32)).unwrap();
894
895    fn in_range(val: i32, range: std::ops::Range<i32>) -> bool {
896        let ok = val >= range.start && val < range.end;
897
898        if !ok {
899            println!("{:?} not in {:?}", val, range);
900        }
901
902        ok
903    }
904
905    assert!(in_range(a.rectangle.min.x, 0..32));
906    assert!(in_range(a.rectangle.max.x, 0..32));
907    assert!(in_range(b.rectangle.min.x, 32..64));
908    assert!(in_range(b.rectangle.max.x, 32..64));
909    assert!(in_range(c.rectangle.min.x, 32..64));
910    assert!(in_range(c.rectangle.max.x, 32..64));
911
912    atlas.deallocate(b.id);
913    atlas.deallocate(c.id);
914    atlas.deallocate(a.id);
915
916    assert!(atlas.is_empty());
917    assert_eq!(atlas.allocated_space(), 0);
918
919    let a = atlas.allocate(size2(24, 46)).unwrap();
920    let b = atlas.allocate(size2(24, 32)).unwrap();
921    let c = atlas.allocate(size2(24, 32)).unwrap();
922    let d = atlas.allocate(size2(24, 8)).unwrap();
923
924    assert_eq!(a.rectangle.min.x, 0);
925    assert_eq!(b.rectangle.min.x, 32);
926    assert_eq!(c.rectangle.min.x, 32);
927    assert_eq!(d.rectangle.min.x, 0);
928}
929
930#[test]
931fn vertical() {
932    let mut atlas = BucketedAtlasAllocator::with_options(size2(128, 256), &AllocatorOptions {
933        num_columns: 2,
934        vertical_shelves: true,
935        ..DEFAULT_OPTIONS
936    });
937
938    assert_eq!(atlas.size(), size2(128, 256));
939
940    let a = atlas.allocate(size2(32, 16)).unwrap();
941    let b = atlas.allocate(size2(16, 32)).unwrap();
942
943    assert!(a.rectangle.size().width >= 32);
944    assert!(a.rectangle.size().height >= 16);
945
946    assert!(b.rectangle.size().width >= 16);
947    assert!(b.rectangle.size().height >= 32);
948
949    let c = atlas.allocate(size2(128, 128)).unwrap();
950
951    atlas.deallocate(a.id);
952    atlas.deallocate(b.id);
953    atlas.deallocate(c.id);
954
955    assert!(atlas.is_empty());
956    assert_eq!(atlas.allocated_space(), 0);
957}
958
959#[test]
960fn clear() {
961    let mut atlas = BucketedAtlasAllocator::new(size2(2048, 2048));
962
963    // Run a workload a few hundred times to make sure clearing properly resets everything.
964    for _ in 0..500 {
965        atlas.clear();
966        assert_eq!(atlas.allocated_space(), 0);
967
968        atlas.allocate(size2(8, 2)).unwrap();
969        atlas.allocate(size2(2, 8)).unwrap();
970        atlas.allocate(size2(16, 512)).unwrap();
971        atlas.allocate(size2(34, 34)).unwrap();
972        atlas.allocate(size2(34, 34)).unwrap();
973        atlas.allocate(size2(34, 34)).unwrap();
974        atlas.allocate(size2(34, 34)).unwrap();
975        atlas.allocate(size2(2, 8)).unwrap();
976        atlas.allocate(size2(2, 8)).unwrap();
977        atlas.allocate(size2(8, 2)).unwrap();
978        atlas.allocate(size2(2, 8)).unwrap();
979        atlas.allocate(size2(8, 2)).unwrap();
980        atlas.allocate(size2(8, 8)).unwrap();
981        atlas.allocate(size2(8, 8)).unwrap();
982        atlas.allocate(size2(8, 8)).unwrap();
983        atlas.allocate(size2(8, 8)).unwrap();
984        atlas.allocate(size2(82, 80)).unwrap();
985        atlas.allocate(size2(56, 56)).unwrap();
986        atlas.allocate(size2(64, 66)).unwrap();
987        atlas.allocate(size2(32, 32)).unwrap();
988        atlas.allocate(size2(32, 32)).unwrap();
989        atlas.allocate(size2(32, 32)).unwrap();
990        atlas.allocate(size2(32, 32)).unwrap();
991        atlas.allocate(size2(32, 32)).unwrap();
992        atlas.allocate(size2(32, 32)).unwrap();
993        atlas.allocate(size2(32, 32)).unwrap();
994        atlas.allocate(size2(32, 32)).unwrap();
995        atlas.allocate(size2(32, 32)).unwrap();
996        atlas.allocate(size2(40, 40)).unwrap();
997        atlas.allocate(size2(32, 32)).unwrap();
998        atlas.allocate(size2(256, 52)).unwrap();
999        atlas.allocate(size2(32, 32)).unwrap();
1000        atlas.allocate(size2(256, 52)).unwrap();
1001        atlas.allocate(size2(256, 52)).unwrap();
1002        atlas.allocate(size2(256, 52)).unwrap();
1003        atlas.allocate(size2(256, 52)).unwrap();
1004        atlas.allocate(size2(256, 52)).unwrap();
1005        atlas.allocate(size2(256, 52)).unwrap();
1006        atlas.allocate(size2(155, 52)).unwrap();
1007        atlas.allocate(size2(256, 52)).unwrap();
1008        atlas.allocate(size2(32, 32)).unwrap();
1009        atlas.allocate(size2(32, 32)).unwrap();
1010        atlas.allocate(size2(32, 32)).unwrap();
1011        atlas.allocate(size2(24, 24)).unwrap();
1012        atlas.allocate(size2(64, 64)).unwrap();
1013        atlas.allocate(size2(32, 32)).unwrap();
1014        atlas.allocate(size2(84, 84)).unwrap();
1015        atlas.allocate(size2(32, 32)).unwrap();
1016        atlas.allocate(size2(8, 2)).unwrap();
1017        atlas.allocate(size2(34, 34)).unwrap();
1018        atlas.allocate(size2(34, 34)).unwrap();
1019        atlas.allocate(size2(192, 192)).unwrap();
1020        atlas.allocate(size2(192, 192)).unwrap();
1021        atlas.allocate(size2(52, 52)).unwrap();
1022        atlas.allocate(size2(144, 144)).unwrap();
1023        atlas.allocate(size2(192, 192)).unwrap();
1024        atlas.allocate(size2(32, 32)).unwrap();
1025        atlas.allocate(size2(144, 144)).unwrap();
1026        atlas.allocate(size2(24, 24)).unwrap();
1027        atlas.allocate(size2(192, 192)).unwrap();
1028        atlas.allocate(size2(192, 192)).unwrap();
1029        atlas.allocate(size2(432, 243)).unwrap();
1030        atlas.allocate(size2(32, 32)).unwrap();
1031        atlas.allocate(size2(8, 2)).unwrap();
1032        atlas.allocate(size2(2, 8)).unwrap();
1033        atlas.allocate(size2(9, 9)).unwrap();
1034        atlas.allocate(size2(14, 14)).unwrap();
1035        atlas.allocate(size2(14, 14)).unwrap();
1036        atlas.allocate(size2(14, 14)).unwrap();
1037        atlas.allocate(size2(14, 14)).unwrap();
1038        atlas.allocate(size2(8, 8)).unwrap();
1039        atlas.allocate(size2(27, 27)).unwrap();
1040        atlas.allocate(size2(27, 27)).unwrap();
1041        atlas.allocate(size2(27, 27)).unwrap();
1042        atlas.allocate(size2(27, 27)).unwrap();
1043        atlas.allocate(size2(11, 12)).unwrap();
1044        atlas.allocate(size2(29, 28)).unwrap();
1045        atlas.allocate(size2(32, 32)).unwrap();
1046    }
1047}
1048
1049
1050#[test]
1051fn fuzz_01() {
1052    let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
1053
1054    assert!(atlas.allocate(size2(65280, 1)).is_none());
1055    assert!(atlas.allocate(size2(1, 65280)).is_none());
1056}
1057
1058#[test]
1059fn fuzz_02() {
1060    let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
1061
1062    assert!(atlas.allocate(size2(255, 65599)).is_none());
1063}
1064
1065#[test]
1066fn fuzz_03() {
1067    let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
1068
1069    let sizes = &[
1070        size2(999, 128),
1071        size2(168492810, 10),
1072        size2(45, 96),
1073        size2(-16711926, 0),
1074    ];
1075
1076    let mut allocations = Vec::new();
1077    let mut allocated_space = 0;
1078
1079    for size in sizes {
1080        if let Some(alloc) = atlas.allocate(*size) {
1081            allocations.push(alloc);
1082            allocated_space += alloc.rectangle.area();
1083            assert_eq!(allocated_space, atlas.allocated_space());
1084        }
1085    }
1086
1087    for alloc in &allocations {
1088        atlas.deallocate(alloc.id);
1089
1090        allocated_space -= alloc.rectangle.area();
1091        assert_eq!(allocated_space, atlas.allocated_space());
1092    }
1093
1094    assert_eq!(atlas.allocated_space(), 0);
1095}
1096
1097#[test]
1098fn fuzz_04() {
1099    let mut atlas = BucketedAtlasAllocator::new(size2(1000, 1000));
1100
1101    assert!(atlas.allocate(size2(2560, 2147483647)).is_none());
1102}
1103
1104#[test]
1105fn fuzz_05() {
1106    let mut atlas = BucketedAtlasAllocator::new(size2(2048, 2048));
1107
1108    assert!(atlas.allocate(size2(0, -1978597547)).is_none());
1109}