Skip to main content

devpage_design/
atlas.rs

1//! Whether a drawing's images fit the GPU, and where. `vello` copies every
2//! image a frame paints into one atlas texture, at most [`SIDE`] pixels a
3//! side, and an image that finds no room there is silently left out of the
4//! frame. So the engine asks this model first, and a drawing whose images
5//! don't fit is the design's problem rather than a picture with holes.
6//!
7//! The model is `vello_encoding`'s own image cache (0.11, `image_cache.rs` and
8//! `Resolver::resolve_pending_images`) run on a fresh cache, with the same
9//! `guillotiere` allocator: images placed in the order the frame first uses
10//! them, the atlas starting at [`START`] and doubling, everything placed so far
11//! repacked in blob order at each size, until [`SIDE`]. The canvas keeps a
12//! vello renderer — and so its atlas — only while the drawings it renders
13//! paint the very same blobs, in the same order: the atlas then holds those
14//! images and nothing else, placed as a fresh one would place them. Any other
15//! drawing gets a fresh renderer. The engine makes each drawing's new blobs in
16//! the order it first uses them, so blob order is that order on every machine.
17//! A test in the module runs vello's own resolver on the same images and
18//! holds the two to the same fit and the same places.
19
20use guillotiere::{AtlasAllocator, size2};
21
22/// The atlas's largest side, `vello_encoding`'s `MAX_ATLAS_SIZE`.
23pub const SIDE: u32 = 8192;
24/// The atlas's first side, `vello_encoding`'s `DEFAULT_ATLAS_SIZE`.
25const START: i32 = 1024;
26
27/// Places `images` — each a `(width, height)`, distinct, in the order a frame
28/// first paints them — as `vello` does in a fresh atlas, and gives each one's
29/// top-left corner there. `Err` is the index of the first image it leaves out.
30pub fn place(images: &[(u32, u32)]) -> Result<Vec<(u32, u32)>, usize> {
31    let mut side = START;
32    let mut atlas = AtlasAllocator::new(size2(side, side));
33    let mut at = Vec::with_capacity(images.len());
34    let corner = |a: guillotiere::Allocation| (a.rectangle.min.x as u32, a.rectangle.min.y as u32);
35    while at.len() < images.len() {
36        let (w, h) = images[at.len()];
37        if let Some(a) = atlas.allocate(size2(w as i32, h as i32)) {
38            at.push(corner(a));
39            continue;
40        }
41        // No room: a larger atlas, with everything placed so far repacked
42        // into it in blob order — which is first-use order.
43        loop {
44            side *= 2;
45            if side > SIDE as i32 {
46                return Err(at.len());
47            }
48            let mut bigger = AtlasAllocator::new(size2(side, side));
49            let repacked: Option<Vec<_>> = images[..at.len()]
50                .iter()
51                .map(|&(w, h)| bigger.allocate(size2(w as i32, h as i32)).map(corner))
52                .collect();
53            if let Some(repacked) = repacked {
54                atlas = bigger;
55                at = repacked;
56                break;
57            }
58        }
59    }
60    Ok(at)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn four_quarters_fill_the_atlas_and_a_fifth_does_not_fit() {
69        assert_eq!(
70            place(&[(4096, 4096); 4]),
71            Ok(vec![(0, 0), (0, 4096), (4096, 0), (4096, 4096)])
72        );
73        assert_eq!(place(&[(4096, 4096); 5]), Err(4));
74        assert_eq!(place(&[(8192, 8192)]), Ok(vec![(0, 0)]));
75        assert_eq!(place(&[(8193, 1)]), Err(0));
76        // Sixteen 2000-pixel squares pack four by four.
77        assert!(place(&[(2000, 2000); 16]).is_ok());
78        assert_eq!(place(&[]), Ok(vec![]));
79    }
80}