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}