⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/graphics/
texture_atlas.rs

1//! Texture Atlas with Shelf-based Bin Packing
2//!
3//! Implements a shelf (skyline) allocator for packing rectangular texture
4//! regions into a single atlas texture. Used by the GL compositor to batch
5//! surface textures into a single GPU-side allocation.
6//!
7//! All arithmetic is integer-only (no FPU required).
8
9#![allow(dead_code)]
10
11use alloc::vec::Vec;
12
13// ---------------------------------------------------------------------------
14// Atlas region
15// ---------------------------------------------------------------------------
16
17/// A rectangular region within the texture atlas.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct AtlasRegion {
20    /// X offset in the atlas (pixels).
21    pub x: u32,
22    /// Y offset in the atlas (pixels).
23    pub y: u32,
24    /// Width of the region (pixels).
25    pub width: u32,
26    /// Height of the region (pixels).
27    pub height: u32,
28}
29
30impl AtlasRegion {
31    /// Total pixel area of this region.
32    pub fn area(&self) -> u32 {
33        self.width * self.height
34    }
35
36    /// Whether a point lies inside this region.
37    pub fn contains(&self, px: u32, py: u32) -> bool {
38        px >= self.x && px < self.x + self.width && py >= self.y && py < self.y + self.height
39    }
40}
41
42// ---------------------------------------------------------------------------
43// Free span within a shelf
44// ---------------------------------------------------------------------------
45
46/// A contiguous horizontal free span on a shelf.
47#[derive(Debug, Clone, Copy)]
48struct FreeSpan {
49    x: u32,
50    width: u32,
51}
52
53// ---------------------------------------------------------------------------
54// Shelf
55// ---------------------------------------------------------------------------
56
57/// A horizontal shelf in the atlas.
58#[derive(Debug, Clone)]
59struct Shelf {
60    /// Y position of this shelf's top edge.
61    y: u32,
62    /// Height of this shelf (set by the first allocation).
63    height: u32,
64    /// Free horizontal spans remaining on this shelf.
65    free_spans: Vec<FreeSpan>,
66}
67
68impl Shelf {
69    /// Create a new shelf at `y` with `height` and full atlas width free.
70    fn new(y: u32, height: u32, atlas_width: u32) -> Self {
71        Self {
72            y,
73            height,
74            free_spans: alloc::vec![FreeSpan {
75                x: 0,
76                width: atlas_width,
77            }],
78        }
79    }
80
81    /// Try to allocate a rectangle of `w x h` on this shelf.
82    /// Returns the region if successful.
83    fn allocate(&mut self, w: u32, h: u32) -> Option<AtlasRegion> {
84        if h > self.height {
85            return None;
86        }
87
88        for i in 0..self.free_spans.len() {
89            let span = self.free_spans[i];
90            if span.width >= w {
91                let region = AtlasRegion {
92                    x: span.x,
93                    y: self.y,
94                    width: w,
95                    height: h,
96                };
97
98                // Shrink or remove the span
99                if span.width == w {
100                    self.free_spans.remove(i);
101                } else {
102                    self.free_spans[i] = FreeSpan {
103                        x: span.x + w,
104                        width: span.width - w,
105                    };
106                }
107
108                return Some(region);
109            }
110        }
111
112        None
113    }
114
115    /// Return a previously allocated region to this shelf.
116    fn deallocate(&mut self, region: &AtlasRegion) {
117        if region.y != self.y {
118            return;
119        }
120
121        // Insert the freed span and merge with neighbours
122        let new_span = FreeSpan {
123            x: region.x,
124            width: region.width,
125        };
126
127        // Find insertion position (keep sorted by x)
128        let pos = self
129            .free_spans
130            .iter()
131            .position(|s| s.x > new_span.x)
132            .unwrap_or(self.free_spans.len());
133
134        self.free_spans.insert(pos, new_span);
135
136        // Merge adjacent spans
137        self.merge_spans();
138    }
139
140    /// Merge adjacent free spans.
141    fn merge_spans(&mut self) {
142        let mut i = 0;
143        while i + 1 < self.free_spans.len() {
144            let a_end = self.free_spans[i].x + self.free_spans[i].width;
145            if a_end == self.free_spans[i + 1].x {
146                self.free_spans[i].width += self.free_spans[i + 1].width;
147                self.free_spans.remove(i + 1);
148            } else {
149                i += 1;
150            }
151        }
152    }
153}
154
155// ---------------------------------------------------------------------------
156// Shelf allocator
157// ---------------------------------------------------------------------------
158
159/// Bin-packing shelf allocator for a texture atlas.
160///
161/// Allocates rectangular regions from a fixed-size atlas using the shelf
162/// (skyline) algorithm. Shelves are created lazily as needed.
163#[derive(Debug)]
164pub struct ShelfAllocator {
165    /// Total atlas width in pixels.
166    atlas_width: u32,
167    /// Total atlas height in pixels.
168    atlas_height: u32,
169    /// Shelves allocated so far.
170    shelves: Vec<Shelf>,
171    /// Y coordinate of the next shelf to create.
172    next_shelf_y: u32,
173    /// Number of live allocations.
174    allocation_count: u32,
175}
176
177impl ShelfAllocator {
178    /// Create a new shelf allocator for an atlas of the given dimensions.
179    pub fn new(width: u32, height: u32) -> Self {
180        Self {
181            atlas_width: width,
182            atlas_height: height,
183            shelves: Vec::new(),
184            next_shelf_y: 0,
185            allocation_count: 0,
186        }
187    }
188
189    /// Atlas width.
190    pub fn width(&self) -> u32 {
191        self.atlas_width
192    }
193
194    /// Atlas height.
195    pub fn height(&self) -> u32 {
196        self.atlas_height
197    }
198
199    /// Number of live allocations.
200    pub fn allocation_count(&self) -> u32 {
201        self.allocation_count
202    }
203
204    /// Allocate a region of `width x height` pixels.
205    ///
206    /// Returns `None` if the atlas is full.
207    pub fn allocate(&mut self, width: u32, height: u32) -> Option<AtlasRegion> {
208        if width == 0 || height == 0 || width > self.atlas_width {
209            return None;
210        }
211
212        // Try existing shelves (best-fit by shelf height)
213        let mut best_idx: Option<usize> = None;
214        let mut best_waste = u32::MAX;
215
216        for (i, shelf) in self.shelves.iter().enumerate() {
217            if shelf.height >= height {
218                let waste = shelf.height - height;
219                // Check if the shelf has space
220                let has_space = shelf.free_spans.iter().any(|s| s.width >= width);
221                if has_space && waste < best_waste {
222                    best_waste = waste;
223                    best_idx = Some(i);
224                }
225            }
226        }
227
228        if let Some(idx) = best_idx {
229            let region = self.shelves[idx].allocate(width, height);
230            if region.is_some() {
231                self.allocation_count += 1;
232            }
233            return region;
234        }
235
236        // Create a new shelf
237        if self.next_shelf_y + height > self.atlas_height {
238            return None; // Atlas full
239        }
240
241        let mut shelf = Shelf::new(self.next_shelf_y, height, self.atlas_width);
242        let region = shelf.allocate(width, height);
243        self.next_shelf_y += height;
244        self.shelves.push(shelf);
245
246        if region.is_some() {
247            self.allocation_count += 1;
248        }
249        region
250    }
251
252    /// Deallocate a previously allocated region.
253    ///
254    /// The region is returned to its shelf for reuse.
255    pub fn deallocate(&mut self, region: &AtlasRegion) {
256        for shelf in &mut self.shelves {
257            if shelf.y == region.y {
258                shelf.deallocate(region);
259                if self.allocation_count > 0 {
260                    self.allocation_count -= 1;
261                }
262                return;
263            }
264        }
265    }
266
267    /// Total number of shelves created.
268    pub fn shelf_count(&self) -> usize {
269        self.shelves.len()
270    }
271
272    /// Fraction of atlas height used, as a percentage (0-100).
273    pub fn utilisation_percent(&self) -> u32 {
274        if self.atlas_height == 0 {
275            return 0;
276        }
277        (self.next_shelf_y * 100) / self.atlas_height
278    }
279
280    /// Reset the allocator, freeing all regions.
281    pub fn reset(&mut self) {
282        self.shelves.clear();
283        self.next_shelf_y = 0;
284        self.allocation_count = 0;
285    }
286}
287
288// ---------------------------------------------------------------------------
289// Tests
290// ---------------------------------------------------------------------------
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn test_atlas_region_basics() {
298        let r = AtlasRegion {
299            x: 10,
300            y: 20,
301            width: 30,
302            height: 40,
303        };
304        assert_eq!(r.area(), 1200);
305        assert!(r.contains(10, 20));
306        assert!(r.contains(39, 59));
307        assert!(!r.contains(40, 20));
308    }
309
310    #[test]
311    fn test_allocate_single() {
312        let mut alloc = ShelfAllocator::new(256, 256);
313        let r = alloc.allocate(64, 32);
314        assert!(r.is_some());
315        let r = r.unwrap();
316        assert_eq!(r.x, 0);
317        assert_eq!(r.y, 0);
318        assert_eq!(r.width, 64);
319        assert_eq!(r.height, 32);
320        assert_eq!(alloc.allocation_count(), 1);
321    }
322
323    #[test]
324    fn test_allocate_multiple_same_height() {
325        let mut alloc = ShelfAllocator::new(256, 256);
326        let r1 = alloc.allocate(64, 32).unwrap();
327        let r2 = alloc.allocate(64, 32).unwrap();
328        // Both should be on the same shelf
329        assert_eq!(r1.y, r2.y);
330        assert_eq!(r2.x, 64);
331        assert_eq!(alloc.shelf_count(), 1);
332    }
333
334    #[test]
335    fn test_allocate_different_heights() {
336        let mut alloc = ShelfAllocator::new(256, 256);
337        let r1 = alloc.allocate(64, 32).unwrap();
338        let r2 = alloc.allocate(64, 64).unwrap();
339        // Should be on different shelves
340        assert_eq!(r1.y, 0);
341        assert_eq!(r2.y, 32);
342        assert_eq!(alloc.shelf_count(), 2);
343    }
344
345    #[test]
346    fn test_allocate_full() {
347        let mut alloc = ShelfAllocator::new(64, 64);
348        let _ = alloc.allocate(64, 64).unwrap();
349        let r = alloc.allocate(1, 1);
350        assert!(r.is_none());
351    }
352
353    #[test]
354    fn test_allocate_too_wide() {
355        let mut alloc = ShelfAllocator::new(64, 64);
356        let r = alloc.allocate(128, 32);
357        assert!(r.is_none());
358    }
359
360    #[test]
361    fn test_deallocate_reuse() {
362        let mut alloc = ShelfAllocator::new(128, 128);
363        let r1 = alloc.allocate(64, 32).unwrap();
364        let _ = alloc.allocate(64, 32).unwrap();
365        alloc.deallocate(&r1);
366        // Should be able to allocate in the freed space
367        let r3 = alloc.allocate(64, 32).unwrap();
368        assert_eq!(r3.x, 0);
369        assert_eq!(r3.y, 0);
370    }
371
372    #[test]
373    fn test_zero_size() {
374        let mut alloc = ShelfAllocator::new(256, 256);
375        assert!(alloc.allocate(0, 32).is_none());
376        assert!(alloc.allocate(32, 0).is_none());
377    }
378
379    #[test]
380    fn test_utilisation() {
381        let mut alloc = ShelfAllocator::new(256, 100);
382        assert_eq!(alloc.utilisation_percent(), 0);
383        let _ = alloc.allocate(128, 50).unwrap();
384        assert_eq!(alloc.utilisation_percent(), 50);
385    }
386}