etagere/lib.rs
1//! Dynamic texture atlas allocation using the shelf packing algorithm.
2//!
3//! ## Texture atlas allocation
4//!
5//! The goal of a texture atlas allocator is to pack multiple rectangles into a larger one.
6//! When rendering 2D or 3D graphics on the GPU, this packing important for draw call batching.
7//!
8//! This crate provides two implementations of the shelf packing algorithm for *dynamic*
9//! texture atlas allocation (dynamic here means supporting both allocation and deallocation).
10//!
11//! [A thousand ways to pack the bin](https://github.com/juj/RectangleBinPack/blob/master/RectangleBinPack.pdf)
12//! is a good resource to learn about rectangle packing algorithms, although it does not not cover
13//! deallocation which complicates the problem space a fair bit.
14//!
15//! The shelf packing algorithm is explained in the above document. It isn't the most efficient
16//! packing strategy, however its simplicity makes it possible to support reasonably fast deallocation.
17//! Note that the [guillotiere](https://crates.io/crates/guillotiere) crate implements a similar API
18//! using the guillotine packing algorithm and may or may not provide better results depending on the
19//! type of workload.
20//!
21//! ## Two allocators
22//!
23//! - [`AtlasAllocator`] Tracks allocations for each individual item and does a reasonable
24//! job of dealing with fragmentation, at a runtime cost.
25//! - [`BucketedAtlasAllocator`] groups items by buckets and only reclaim the space occupied
26//! by a bucket when all of its items are deallocated. In addition, it has limited support
27//! for merging consecutive empty shelves. These limitations allow faster allocation and
28//! deallocation, making it an appealing option when the atlas is expected to hold a very
29//! large amount of small items.
30//!
31//! Both allocators support:
32//! - Requiring an alignment for the items.
33//! - Packing into vertical shelves instead of horizontal ones (might provide a better output
34//! for some workloads).
35//! - Splitting the allocator into multiple columns in order to make shelves smaller and allow
36//! more of them.
37//! - Dumping the content of the atlas in SVG format for easy debugging.
38//!
39//! See [`AllocatorOptions`](struct.AllocatorOptions.html)
40//!
41//! In addition, this repository contains a command-line application to experiment with and
42//! test the implementations interactively.
43//!
44//! ## Example
45//!
46//! ```rust
47//! use etagere::*;
48//!
49//! let mut atlas = AtlasAllocator::new(size2(1000, 1000));
50//!
51//! let a = atlas.allocate(size2(100, 100)).unwrap();
52//! let b = atlas.allocate(size2(900, 200)).unwrap();
53//! println!("Allocated {:?} and {:?}", a.rectangle, b.rectangle);
54//!
55//! atlas.deallocate(a.id);
56//!
57//! let c = atlas.allocate(size2(300, 200)).unwrap();
58//!
59//! atlas.deallocate(c.id);
60//! atlas.deallocate(b.id);
61//! ```
62//!
63//! ## License
64//!
65//! Licensed under either of
66//!
67//! * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
68//! * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
69//!
70//! at your option.
71//!
72//!
73//! [`AtlasAllocator`]: struct.AtlasAllocator.html
74//! [`BucketedAtlasAllocator`]: struct.BucketedAtlasAllocator.html
75
76#[cfg(feature = "serde")]
77#[macro_use]
78pub extern crate serde;
79pub extern crate euclid;
80
81mod bucketed;
82mod allocator;
83#[cfg(feature = "ffi")]
84pub mod ffi;
85
86pub use allocator::*;
87pub use bucketed::*;
88pub use euclid::{point2, size2};
89
90pub type Point = euclid::default::Point2D<i32>;
91pub type Size = euclid::default::Size2D<i32>;
92pub type Rectangle = euclid::default::Box2D<i32>;
93
94/// Options to tweak the behavior of the atlas allocator.
95#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
96#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
97pub struct AllocatorOptions {
98 /// Align item sizes to a multiple of this alignment.
99 ///
100 /// Default value: [1, 1] (no alignment).
101 pub alignment: Size,
102 /// Use vertical instead of horizontal shelves.
103 ///
104 /// Default value: false.
105 pub vertical_shelves: bool,
106 /// If possible split the allocator's surface into multiple columns.
107 ///
108 /// Having multiple columns allows having more (smaller shelves).
109 ///
110 /// Default value: 1.
111 pub num_columns: i32,
112}
113
114pub const DEFAULT_OPTIONS: AllocatorOptions = AllocatorOptions {
115 vertical_shelves: false,
116 alignment: size2(1, 1),
117 num_columns: 1,
118};
119
120impl Default for AllocatorOptions {
121 fn default() -> Self {
122 DEFAULT_OPTIONS
123 }
124}
125
126/// The `AllocId` and `Rectangle` resulting from an allocation.
127#[repr(C)]
128#[derive(Copy, Clone, Debug, PartialEq)]
129#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
130pub struct Allocation {
131 pub id: AllocId,
132 pub rectangle: Rectangle,
133}
134
135/// ID referring to an allocated rectangle.
136#[repr(C)]
137#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
138#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
139pub struct AllocId(pub(crate) u32);
140
141impl AllocId {
142 #[inline]
143 pub(crate) fn new(index: u16, gen: u16) -> Self {
144 AllocId(index as u32 | ((gen as u32) << 16))
145 }
146
147 #[inline]
148 pub(crate) fn index(&self) -> u16 {
149 self.0 as u16
150 }
151
152 #[inline]
153 pub(crate) fn generation(&self) -> u16 {
154 (self.0 >> 16) as u16
155 }
156
157 #[inline]
158 pub fn serialize(&self) -> u32 {
159 self.0
160 }
161
162 #[inline]
163 pub fn deserialize(bytes: u32) -> Self {
164 AllocId(bytes)
165 }
166}
167