1use api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
6
7const NUM_BINS: usize = 3;
11const MIN_RECT_AXIS_SIZES: [i32; NUM_BINS] = [1, 16, 32];
14
15#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
16struct FreeListBin(u8);
17
18#[derive(Debug, Clone, Copy)]
19struct FreeListIndex(usize);
20
21impl FreeListBin {
22 fn for_size(size: &DeviceIntSize) -> Self {
23 MIN_RECT_AXIS_SIZES
24 .iter()
25 .enumerate()
26 .rev()
27 .find(|(_, &min_size)| min_size <= size.width && min_size <= size.height)
28 .map(|(id, _)| FreeListBin(id as u8))
29 .unwrap_or_else(|| panic!("Unable to find a bin for {:?}!", size))
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
34#[cfg_attr(feature = "capture", derive(Serialize))]
35#[cfg_attr(feature = "replay", derive(Deserialize))]
36pub struct FreeRectSlice(pub u32);
37
38#[cfg_attr(feature = "capture", derive(Serialize))]
39#[cfg_attr(feature = "replay", derive(Deserialize))]
40struct FreeRect {
41 slice: FreeRectSlice,
42 rect: DeviceIntRect,
43}
44
45#[cfg_attr(feature = "capture", derive(Serialize))]
46#[cfg_attr(feature = "replay", derive(Deserialize))]
47struct FreeRectSize {
48 width: i16,
49 height: i16,
50}
51
52#[cfg_attr(feature = "capture", derive(Serialize))]
53#[cfg_attr(feature = "replay", derive(Deserialize))]
54struct Bin {
55 sizes: Vec<FreeRectSize>,
58 rects: Vec<FreeRect>,
59}
60
61#[cfg_attr(feature = "capture", derive(Serialize))]
73#[cfg_attr(feature = "replay", derive(Deserialize))]
74pub struct GuillotineAllocator {
75 bins: [Bin; NUM_BINS],
76}
77
78impl GuillotineAllocator {
79 pub fn new(initial_size: Option<DeviceIntSize>) -> Self {
80 let mut allocator = GuillotineAllocator {
81 bins: [
82 Bin { rects: Vec::new(), sizes: Vec::new() },
83 Bin { rects: Vec::new(), sizes: Vec::new() },
84 Bin { rects: Vec::new(), sizes: Vec::new() },
85 ],
86 };
87
88 if let Some(initial_size) = initial_size {
89 allocator.push(
90 FreeRectSlice(0),
91 initial_size.into(),
92 );
93 }
94
95 allocator
96 }
97
98 fn push(&mut self, slice: FreeRectSlice, rect: DeviceIntRect) {
99 let id = FreeListBin::for_size(&rect.size()).0 as usize;
100 self.bins[id].rects.push(FreeRect {
101 slice,
102 rect,
103 });
104 self.bins[id].sizes.push(FreeRectSize {
105 width: rect.width() as i16,
106 height: rect.height() as i16,
107 });
108 }
109
110 fn find_index_of_best_rect(
112 &self,
113 requested_dimensions: &DeviceIntSize,
114 ) -> Option<(FreeListBin, FreeListIndex)> {
115
116 let start_bin = FreeListBin::for_size(&requested_dimensions);
117
118 let w = requested_dimensions.width as i16;
119 let h = requested_dimensions.height as i16;
120 (start_bin.0 .. NUM_BINS as u8)
121 .find_map(|id| {
122 self.bins[id as usize].sizes
123 .iter()
124 .position(|candidate| w <= candidate.width && h <= candidate.height)
125 .map(|index| (FreeListBin(id), FreeListIndex(index)))
126 })
127 }
128
129 fn split_guillotine(&mut self, chosen: &FreeRect, requested_dimensions: &DeviceIntSize) {
131 let candidate_free_rect_to_right = DeviceIntRect::from_origin_and_size(
132 DeviceIntPoint::new(
133 chosen.rect.min.x + requested_dimensions.width,
134 chosen.rect.min.y,
135 ),
136 DeviceIntSize::new(
137 chosen.rect.width() - requested_dimensions.width,
138 requested_dimensions.height,
139 ),
140 );
141 let candidate_free_rect_to_bottom = DeviceIntRect::from_origin_and_size(
142 DeviceIntPoint::new(
143 chosen.rect.min.x,
144 chosen.rect.min.y + requested_dimensions.height,
145 ),
146 DeviceIntSize::new(
147 requested_dimensions.width,
148 chosen.rect.height() - requested_dimensions.height,
149 ),
150 );
151
152 let new_free_rect_to_right;
154 let new_free_rect_to_bottom;
155 if candidate_free_rect_to_right.area() > candidate_free_rect_to_bottom.area() {
156 new_free_rect_to_right = DeviceIntRect::from_origin_and_size(
157 candidate_free_rect_to_right.min,
158 DeviceIntSize::new(
159 candidate_free_rect_to_right.width(),
160 chosen.rect.height(),
161 ),
162 );
163 new_free_rect_to_bottom = candidate_free_rect_to_bottom
164 } else {
165 new_free_rect_to_right = candidate_free_rect_to_right;
166 new_free_rect_to_bottom = DeviceIntRect::from_origin_and_size(
167 candidate_free_rect_to_bottom.min,
168 DeviceIntSize::new(
169 chosen.rect.width(),
170 candidate_free_rect_to_bottom.height(),
171 ),
172 )
173 }
174
175 if !new_free_rect_to_right.is_empty() {
177 self.push(chosen.slice, new_free_rect_to_right);
178 }
179 if !new_free_rect_to_bottom.is_empty() {
180 self.push(chosen.slice, new_free_rect_to_bottom);
181 }
182 }
183
184 pub fn allocate(
185 &mut self, requested_dimensions: &DeviceIntSize
186 ) -> Option<(FreeRectSlice, DeviceIntPoint)> {
187 let mut requested_dimensions = *requested_dimensions;
188 requested_dimensions.width = (requested_dimensions.width + 7) & !7;
191 requested_dimensions.height = (requested_dimensions.height + 7) & !7;
192
193 if requested_dimensions.width == 0 || requested_dimensions.height == 0 {
194 return Some((FreeRectSlice(0), DeviceIntPoint::new(0, 0)));
195 }
196
197 let (bin, index) = self.find_index_of_best_rect(&requested_dimensions)?;
198
199 let chosen = self.bins[bin.0 as usize].rects.swap_remove(index.0);
201 self.bins[bin.0 as usize].sizes.swap_remove(index.0);
202 self.split_guillotine(&chosen, &requested_dimensions);
203
204 Some((chosen.slice, chosen.rect.min))
206 }
207
208 pub fn extend(
210 &mut self,
211 slice: FreeRectSlice,
212 total_size: DeviceIntSize,
213 requested_dimensions: DeviceIntSize,
214 ) {
215 self.split_guillotine(
216 &FreeRect { slice, rect: total_size.into() },
217 &requested_dimensions
218 );
219 }
220}
221
222#[cfg(test)]
223fn random_fill(count: usize, texture_size: i32) -> f32 {
224 use rand::{thread_rng, Rng};
225
226 let total_rect = DeviceIntRect::from_size(
227 DeviceIntSize::new(texture_size, texture_size),
228 );
229 let mut rng = thread_rng();
230 let mut allocator = GuillotineAllocator::new(None);
231
232 assert_eq!(
234 allocator.allocate(&DeviceIntSize::new(0, 12)),
235 Some((FreeRectSlice(0), DeviceIntPoint::zero())),
236 );
237
238 let mut slices: Vec<Vec<DeviceIntRect>> = Vec::new();
239 let mut requested_area = 0f32;
240 for _ in 0 .. count {
242 let size = DeviceIntSize::new(
243 rng.gen_range(1, texture_size),
244 rng.gen_range(1, texture_size),
245 );
246 requested_area += size.area() as f32;
247
248 match allocator.allocate(&size) {
249 Some((slice, origin)) => {
250 let rect = DeviceIntRect::from_origin_and_size(origin, size);
251 assert_eq!(None, slices[slice.0 as usize].iter().find(|r| r.intersects(&rect)));
252 assert!(total_rect.contains_box(&rect));
253 slices[slice.0 as usize].push(rect);
254 }
255 None => {
256 allocator.extend(FreeRectSlice(slices.len() as u32), total_rect.size(), size);
257 let rect = DeviceIntRect::from_size(size);
258 slices.push(vec![rect]);
259 }
260 }
261 }
262 for (i, bin) in allocator.bins.iter().enumerate() {
264 for fr in &bin.rects {
265 assert_eq!(FreeListBin(i as u8), FreeListBin::for_size(&fr.rect.size()));
266 assert_eq!(None, slices[fr.slice.0 as usize].iter().find(|r| r.intersects(&fr.rect)));
267 assert!(total_rect.contains_box(&fr.rect));
268 slices[fr.slice.0 as usize].push(fr.rect);
269 }
270 }
271
272 let allocated_area = slices.len() as f32 * (texture_size * texture_size) as f32;
273 requested_area / allocated_area
274}
275
276#[test]
277fn test_small() {
278 random_fill(100, 100);
279}
280
281#[test]
282fn test_large() {
283 random_fill(1000, 10000);
284}