use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AtlasRect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl AtlasRect {
pub fn uv(&self, atlas: u32) -> [f32; 4] {
let scale = 1.0 / atlas as f32;
[
self.x as f32 * scale,
self.y as f32 * scale,
(self.x + self.width) as f32 * scale,
(self.y + self.height) as f32 * scale,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GlyphKey {
pub font: u64,
pub glyph: u16,
pub size: u16,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Coverage {
pub width: u32,
pub height: u32,
pub texels: Vec<u8>,
}
impl Coverage {
pub fn is_consistent(&self) -> bool {
self.texels.len() as u64 == self.width as u64 * self.height as u64
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtlasError {
Inconsistent,
TooLarge,
Full,
}
impl std::fmt::Display for AtlasError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Inconsistent => write!(f, "coverage dimensions do not match its buffer"),
Self::TooLarge => write!(f, "glyph is larger than the atlas"),
Self::Full => write!(f, "atlas is full"),
}
}
}
impl std::error::Error for AtlasError {}
#[derive(Debug, Clone, Copy)]
struct Shelf {
top: u32,
height: u32,
used: u32,
}
#[derive(Debug)]
pub struct Atlas {
size: u32,
texels: Vec<u8>,
shelves: Vec<Shelf>,
placed: HashMap<GlyphKey, Placed>,
frame: u64,
dirty: bool,
compactions: u64,
limit: u32,
growths: u64,
}
#[derive(Debug, Clone, Copy)]
struct Placed {
rect: AtlasRect,
used: u64,
}
const PADDING: u32 = 1;
impl Atlas {
pub fn new(size: u32) -> Self {
Self::with_limit(size, 4096)
}
pub fn with_limit(size: u32, limit: u32) -> Self {
Self {
size,
texels: vec![0; (size as usize) * (size as usize)],
shelves: Vec::new(),
placed: HashMap::new(),
frame: 0,
dirty: false,
compactions: 0,
limit,
growths: 0,
}
}
pub fn size(&self) -> u32 {
self.size
}
pub fn texels(&self) -> &[u8] {
&self.texels
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn mark_clean(&mut self) {
self.dirty = false;
}
pub fn len(&self) -> usize {
self.placed.len()
}
pub fn is_empty(&self) -> bool {
self.placed.is_empty()
}
pub fn get(&self, key: GlyphKey) -> Option<AtlasRect> {
self.placed.get(&key).map(|placed| placed.rect)
}
pub fn begin_frame(&mut self) {
self.frame += 1;
}
pub fn compactions(&self) -> u64 {
self.compactions
}
pub fn growths(&self) -> u64 {
self.growths
}
pub fn limit(&self) -> u32 {
self.limit
}
pub fn insert(&mut self, key: GlyphKey, coverage: &Coverage) -> Result<AtlasRect, AtlasError> {
if let Some(placed) = self.placed.get_mut(&key) {
placed.used = self.frame;
return Ok(placed.rect);
}
if !coverage.is_consistent() {
return Err(AtlasError::Inconsistent);
}
if coverage.width == 0 || coverage.height == 0 {
let rect = AtlasRect {
x: 0,
y: 0,
width: 0,
height: 0,
};
self.placed.insert(
key,
Placed {
rect,
used: self.frame,
},
);
return Ok(rect);
}
let rect = match self.allocate(coverage.width, coverage.height) {
Ok(rect) => rect,
Err(AtlasError::Full) => {
if !self.compact() && !self.grow() {
return Err(AtlasError::Full);
}
self.allocate(coverage.width, coverage.height)?
}
Err(e) => return Err(e),
};
for row in 0..coverage.height {
let from = (row * coverage.width) as usize;
let to = ((rect.y + row) * self.size + rect.x) as usize;
self.texels[to..to + coverage.width as usize]
.copy_from_slice(&coverage.texels[from..from + coverage.width as usize]);
}
self.placed.insert(
key,
Placed {
rect,
used: self.frame,
},
);
self.dirty = true;
Ok(rect)
}
fn compact(&mut self) -> bool {
let survivors: Vec<(GlyphKey, Placed)> = self
.placed
.iter()
.filter(|(_, placed)| placed.used == self.frame)
.map(|(key, placed)| (*key, *placed))
.collect();
if survivors.len() == self.placed.len() {
return false;
}
let mut coverage: Vec<(GlyphKey, Coverage)> = survivors
.iter()
.map(|(key, placed)| (*key, self.extract(placed.rect)))
.collect();
repacking_order(&mut coverage);
self.texels.fill(0);
self.shelves.clear();
self.placed.clear();
self.compactions += 1;
self.dirty = true;
for (key, coverage) in coverage {
let _ = self.insert(key, &coverage);
}
true
}
fn grow(&mut self) -> bool {
if self.size >= self.limit {
return false;
}
let grown = (self.size.saturating_mul(2)).min(self.limit);
if grown <= self.size {
return false;
}
let mut kept: Vec<(GlyphKey, Coverage)> = self
.placed
.iter()
.map(|(key, placed)| (*key, self.extract(placed.rect)))
.collect();
repacking_order(&mut kept);
self.size = grown;
self.texels = vec![0; (grown as usize) * (grown as usize)];
self.shelves.clear();
self.placed.clear();
self.growths += 1;
self.dirty = true;
for (key, coverage) in kept {
let _ = self.insert(key, &coverage);
}
true
}
fn extract(&self, rect: AtlasRect) -> Coverage {
let mut texels = Vec::with_capacity((rect.width * rect.height) as usize);
for row in 0..rect.height {
let from = ((rect.y + row) * self.size + rect.x) as usize;
texels.extend_from_slice(&self.texels[from..from + rect.width as usize]);
}
Coverage {
width: rect.width,
height: rect.height,
texels,
}
}
fn allocate(&mut self, width: u32, height: u32) -> Result<AtlasRect, AtlasError> {
let padded_width = width + PADDING;
let padded_height = height + PADDING;
if padded_width > self.size || padded_height > self.size {
return Err(AtlasError::TooLarge);
}
for shelf in &mut self.shelves {
if shelf.height >= padded_height && self.size - shelf.used >= padded_width {
let rect = AtlasRect {
x: shelf.used,
y: shelf.top,
width,
height,
};
shelf.used += padded_width;
return Ok(rect);
}
}
let top = self
.shelves
.last()
.map_or(0, |shelf| shelf.top + shelf.height);
if top + padded_height > self.size {
return Err(AtlasError::Full);
}
self.shelves.push(Shelf {
top,
height: padded_height,
used: padded_width,
});
Ok(AtlasRect {
x: 0,
y: top,
width,
height,
})
}
}
fn repacking_order(glyphs: &mut [(GlyphKey, Coverage)]) {
glyphs.sort_by(|(left_key, left), (right_key, right)| {
right
.height
.cmp(&left.height)
.then(right.width.cmp(&left.width))
.then(left_key.cmp(right_key))
});
}
#[cfg(test)]
mod tests {
use super::*;
fn solid(width: u32, height: u32, value: u8) -> Coverage {
Coverage {
width,
height,
texels: vec![value; (width * height) as usize],
}
}
fn key(glyph: u16) -> GlyphKey {
GlyphKey {
font: 1,
glyph,
size: 16,
}
}
#[test]
fn a_compaction_lays_its_survivors_out_tallest_first() {
let mut atlas = Atlas::with_limit(64, 64);
let sizes: Vec<(u32, u32)> = (0..40u32)
.map(|i| (3 + (i * 7) % 11, 3 + (i * 5) % 13))
.collect();
for (i, (w, h)) in sizes.iter().enumerate() {
let _ = atlas.insert(key(i as u16), &solid(*w, *h, 200));
}
atlas.begin_frame();
let mut survivors = Vec::new();
for i in (0..40usize).step_by(2) {
let (w, h) = sizes[i];
if atlas.insert(key(i as u16), &solid(w, h, 200)).is_ok() {
survivors.push((i as u16, h));
}
}
let _ = atlas.insert(key(90), &solid(6, 9, 200));
assert_eq!(
atlas.compactions(),
1,
"nothing compacted, so this proves nothing"
);
survivors.sort_by(|(left_key, left), (right_key, right)| {
right.cmp(left).then(left_key.cmp(right_key))
});
let (tallest, height) = survivors[0];
let rect = atlas
.get(key(tallest))
.expect("the tallest survivor should have been kept");
assert_eq!(
rect.y, 0,
"glyph {tallest}, the tallest survivor at {height}, landed at row \
{} rather than opening the first shelf -- so the rebuild placed \
something else before it",
rect.y
);
}
#[test]
fn a_rebuild_packs_the_tallest_glyphs_first() {
let mut glyphs = vec![
(key(1), solid(4, 4, 1)),
(key(2), solid(4, 20, 2)),
(key(3), solid(9, 12, 3)),
(key(4), solid(2, 20, 4)),
];
repacking_order(&mut glyphs);
let heights: Vec<u32> = glyphs.iter().map(|(_, c)| c.height).collect();
assert_eq!(heights, vec![20, 20, 12, 4], "tallest first");
assert_eq!(
(glyphs[0].1.width, glyphs[1].1.width),
(4, 2),
"a tie in height is broken by width"
);
}
#[test]
fn an_inserted_glyph_can_be_found_again() {
let mut atlas = Atlas::new(64);
let rect = atlas.insert(key(1), &solid(8, 10, 200)).expect("insert");
assert_eq!(atlas.get(key(1)), Some(rect));
assert_eq!(rect.width, 8);
assert_eq!(rect.height, 10);
assert_eq!(atlas.len(), 1);
}
#[test]
fn inserting_the_same_glyph_twice_returns_the_same_place() {
let mut atlas = Atlas::new(64);
let first = atlas.insert(key(1), &solid(8, 10, 200)).expect("first");
let second = atlas.insert(key(1), &solid(8, 10, 200)).expect("second");
assert_eq!(first, second);
assert_eq!(atlas.len(), 1);
}
#[test]
fn glyphs_differing_only_in_font_or_size_are_distinct() {
let mut atlas = Atlas::new(64);
let a = atlas.insert(key(1), &solid(8, 8, 255)).expect("a");
let b = atlas
.insert(GlyphKey { font: 2, ..key(1) }, &solid(8, 8, 255))
.expect("b");
let c = atlas
.insert(GlyphKey { size: 32, ..key(1) }, &solid(8, 8, 255))
.expect("c");
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
assert_eq!(atlas.len(), 3);
}
#[test]
fn coverage_lands_where_the_rectangle_says() {
let mut atlas = Atlas::new(16);
let coverage = Coverage {
width: 3,
height: 2,
texels: vec![10, 20, 30, 40, 50, 60],
};
let rect = atlas.insert(key(1), &coverage).expect("insert");
for row in 0..coverage.height {
for column in 0..coverage.width {
let at = ((rect.y + row) * atlas.size() + rect.x + column) as usize;
assert_eq!(
atlas.texels()[at],
coverage.texels[(row * coverage.width + column) as usize],
"texel ({column}, {row}) landed wrong"
);
}
}
}
#[test]
fn glyphs_do_not_touch_each_other() {
let mut atlas = Atlas::new(64);
let a = atlas.insert(key(1), &solid(8, 8, 255)).expect("a");
let b = atlas.insert(key(2), &solid(8, 8, 255)).expect("b");
assert!(
b.x >= a.x + a.width + PADDING || b.y >= a.y + a.height + PADDING,
"{a:?} and {b:?} are adjacent"
);
}
#[test]
fn a_second_shelf_starts_below_the_first() {
let mut atlas = Atlas::new(32);
let first = atlas.insert(key(1), &solid(11, 6, 255)).expect("first");
let second = atlas.insert(key(2), &solid(11, 6, 255)).expect("second");
let third = atlas.insert(key(3), &solid(11, 6, 255)).expect("third");
assert_eq!(first.y, second.y, "the first two share a shelf");
assert!(third.y > first.y, "the third started a new shelf");
assert_eq!(third.x, 0, "a new shelf starts at the left edge");
}
#[test]
fn a_short_glyph_reuses_a_taller_shelf() {
let mut atlas = Atlas::new(64);
let tall = atlas.insert(key(1), &solid(8, 20, 255)).expect("tall");
let short = atlas.insert(key(2), &solid(8, 4, 255)).expect("short");
assert_eq!(tall.y, short.y, "the short glyph opened a new shelf");
}
#[test]
fn a_glyph_larger_than_the_atlas_is_reported_as_such() {
let mut atlas = Atlas::new(16);
assert_eq!(
atlas.insert(key(1), &solid(16, 16, 255)),
Err(AtlasError::TooLarge),
"a glyph needing padding beyond the edge should not fit"
);
assert_eq!(
atlas.insert(key(2), &solid(64, 4, 255)),
Err(AtlasError::TooLarge)
);
}
#[test]
fn a_full_atlas_says_so_rather_than_overwriting() {
let mut atlas = fixed(16);
let mut inserted = 0;
for glyph in 0..64u16 {
match atlas.insert(key(glyph), &solid(6, 6, 255)) {
Ok(_) => inserted += 1,
Err(e) => {
assert_eq!(e, AtlasError::Full);
break;
}
}
}
assert!(inserted > 0, "nothing fit at all");
assert!(inserted < 64, "everything fit, so fullness went untested");
let mut seen = std::collections::HashSet::new();
for glyph in 0..inserted as u16 {
let rect = atlas.get(key(glyph)).expect("still present");
assert!(seen.insert((rect.x, rect.y)), "two glyphs share a place");
}
}
#[test]
fn a_glyph_with_no_area_is_placed_rather_than_refused() {
let mut atlas = Atlas::new(16);
let rect = atlas.insert(key(1), &solid(0, 0, 0)).expect("insert");
assert_eq!(rect.width, 0);
assert!(!atlas.is_dirty(), "an empty glyph changed no texels");
}
#[test]
fn inconsistent_coverage_is_refused() {
let mut atlas = Atlas::new(16);
let bad = Coverage {
width: 4,
height: 4,
texels: vec![0; 3],
};
assert_eq!(atlas.insert(key(1), &bad), Err(AtlasError::Inconsistent));
}
#[test]
fn the_dirty_flag_tracks_whether_an_upload_is_owed() {
let mut atlas = Atlas::new(32);
assert!(!atlas.is_dirty(), "an empty atlas owes no upload");
atlas.insert(key(1), &solid(4, 4, 255)).expect("insert");
assert!(atlas.is_dirty());
atlas.mark_clean();
assert!(!atlas.is_dirty());
atlas.insert(key(1), &solid(4, 4, 255)).expect("again");
assert!(
!atlas.is_dirty(),
"a glyph already present dirtied the atlas"
);
}
fn fixed(size: u32) -> Atlas {
Atlas::with_limit(size, size)
}
fn fill_until_growth(atlas: &mut Atlas, from: u16) -> u16 {
let mut glyph = from;
while atlas.growths() == 0 {
atlas
.insert(key(glyph), &solid(6, 6, 255))
.unwrap_or_else(|e| panic!("insert {glyph}: {e}"));
glyph += 1;
assert!(glyph < from + 200, "the atlas never grew");
}
glyph - from
}
fn fill(atlas: &mut Atlas, from: u16) -> u16 {
let mut glyph = from;
while atlas.insert(key(glyph), &solid(6, 6, 255)).is_ok() {
glyph += 1;
assert!(glyph < from + 200, "the atlas never filled");
}
glyph - from
}
#[test]
fn a_full_atlas_makes_room_for_what_the_new_frame_needs() {
let mut atlas = fixed(32);
let fitted = fill(&mut atlas, 0);
assert!(
fitted > 2,
"only {fitted} glyphs fitted; too few to evict from"
);
atlas.begin_frame();
let rect = atlas
.insert(key(500), &solid(6, 6, 255))
.expect("a stale atlas should make room");
assert_eq!(rect.width, 6);
assert_eq!(atlas.compactions(), 1);
assert_eq!(atlas.len(), 1, "the stale glyphs were not discarded");
}
#[test]
fn compaction_keeps_the_glyphs_this_frame_asked_for() {
let mut atlas = fixed(32);
let fitted = fill(&mut atlas, 0);
atlas.begin_frame();
let kept: Vec<u16> = vec![0, 1];
for glyph in &kept {
atlas
.insert(key(*glyph), &solid(6, 6, 255))
.expect("refresh");
}
atlas
.insert(key(500), &solid(6, 6, 255))
.expect("should make room");
assert_eq!(atlas.compactions(), 1);
for glyph in &kept {
assert!(
atlas.get(key(*glyph)).is_some(),
"glyph {glyph} was asked for this frame and discarded anyway"
);
}
assert!(
atlas.get(key(fitted - 1)).is_none(),
"a glyph nothing asked for survived"
);
}
#[test]
fn a_glyph_kept_through_compaction_keeps_its_coverage() {
let mut atlas = fixed(32);
let distinct = Coverage {
width: 3,
height: 2,
texels: vec![11, 22, 33, 44, 55, 66],
};
atlas.insert(key(0), &distinct).expect("insert");
fill(&mut atlas, 1);
atlas.begin_frame();
atlas.insert(key(0), &distinct).expect("refresh");
atlas
.insert(key(500), &solid(6, 6, 255))
.expect("should make room");
let rect = atlas.get(key(0)).expect("survived");
let mut got = Vec::new();
for row in 0..rect.height {
let from = ((rect.y + row) * atlas.size() + rect.x) as usize;
got.extend_from_slice(&atlas.texels()[from..from + rect.width as usize]);
}
assert_eq!(got, distinct.texels, "coverage was lost in repacking");
}
#[test]
fn an_atlas_full_of_glyphs_this_frame_needs_reports_full() {
let mut atlas = fixed(32);
let fitted = fill(&mut atlas, 0);
atlas.begin_frame();
for glyph in 0..fitted {
atlas
.insert(key(glyph), &solid(6, 6, 255))
.expect("refresh");
}
assert_eq!(
atlas.insert(key(500), &solid(6, 6, 255)),
Err(AtlasError::Full)
);
assert_eq!(
atlas.compactions(),
0,
"it repacked without freeing anything"
);
}
#[test]
fn a_glyph_too_large_is_not_worth_compacting_for() {
let mut atlas = fixed(32);
fill(&mut atlas, 0);
let before = atlas.len();
atlas.begin_frame();
assert_eq!(
atlas.insert(key(500), &solid(64, 64, 255)),
Err(AtlasError::TooLarge)
);
assert_eq!(atlas.compactions(), 0);
assert_eq!(atlas.len(), before, "the atlas was emptied for nothing");
}
#[test]
fn an_atlas_that_is_never_told_about_frames_keeps_everything() {
let mut atlas = fixed(32);
let fitted = fill(&mut atlas, 0);
assert_eq!(
atlas.insert(key(500), &solid(6, 6, 255)),
Err(AtlasError::Full)
);
assert_eq!(atlas.len(), fitted as usize);
assert_eq!(atlas.compactions(), 0);
}
#[test]
fn an_atlas_with_nothing_stale_grows_rather_than_refusing() {
let mut atlas = Atlas::with_limit(32, 128);
let inserted = fill_until_growth(&mut atlas, 0);
assert!(inserted > 2, "only {inserted} glyphs went in");
assert_eq!(atlas.size(), 64, "it did not double");
assert_eq!(atlas.growths(), 1);
assert_eq!(atlas.compactions(), 0, "it discarded something it needed");
assert_eq!(atlas.len(), inserted as usize, "a glyph was lost");
}
#[test]
fn growth_keeps_every_glyph_and_its_coverage() {
let mut atlas = Atlas::with_limit(32, 128);
let distinct = Coverage {
width: 3,
height: 2,
texels: vec![11, 22, 33, 44, 55, 66],
};
atlas.insert(key(0), &distinct).expect("insert");
fill_until_growth(&mut atlas, 1);
assert_eq!(atlas.growths(), 1);
let rect = atlas.get(key(0)).expect("survived");
let mut got = Vec::new();
for row in 0..rect.height {
let from = ((rect.y + row) * atlas.size() + rect.x) as usize;
got.extend_from_slice(&atlas.texels()[from..from + rect.width as usize]);
}
assert_eq!(got, distinct.texels, "coverage was lost in growing");
}
#[test]
fn growth_stops_at_the_limit_and_says_so() {
let mut atlas = Atlas::with_limit(16, 32);
let mut glyph = 0u16;
loop {
match atlas.insert(key(glyph), &solid(6, 6, 255)) {
Ok(_) => glyph += 1,
Err(e) => {
assert_eq!(e, AtlasError::Full);
break;
}
}
assert!(glyph < 200, "it never filled");
}
assert_eq!(atlas.size(), 32, "it did not grow to its limit");
assert!(atlas.growths() >= 1);
assert!(atlas.get(key(0)).is_some());
}
#[test]
fn a_limit_no_larger_than_the_atlas_means_it_never_grows() {
let mut atlas = Atlas::with_limit(16, 16);
fill(&mut atlas, 0);
assert_eq!(
atlas.insert(key(500), &solid(6, 6, 255)),
Err(AtlasError::Full)
);
assert_eq!(atlas.size(), 16);
assert_eq!(atlas.growths(), 0);
}
#[test]
fn texture_coordinates_span_the_rectangle() {
let rect = AtlasRect {
x: 16,
y: 32,
width: 8,
height: 4,
};
let [left, top, right, bottom] = rect.uv(64);
assert!((left - 0.25).abs() < 1e-6);
assert!((top - 0.5).abs() < 1e-6);
assert!((right - 0.375).abs() < 1e-6);
assert!((bottom - 0.5625).abs() < 1e-6);
assert!(bottom > top);
}
}