1use cosmic_text::CacheKey;
5use rustc_hash::{FxHashMap, FxHashSet};
6
7use crate::resources::ImageId;
8
9pub const ATLAS_SIZE: u32 = 1024;
10pub const MAX_ATLAS_SIZE: u32 = 4096;
12
13#[derive(Clone, Copy, Debug)]
14pub struct GlyphSlot {
15 pub x: u32,
16 pub y: u32,
17 pub w: u32,
18 pub h: u32,
19 pub left: i32,
21 pub top: i32,
22 pub color_glyph: bool,
24 pub subpixel: bool,
26}
27
28pub struct RasterGlyph {
29 pub w: u32,
30 pub h: u32,
31 pub left: i32,
32 pub top: i32,
33 pub color: bool,
34 pub subpixel: bool,
35 pub data: Vec<u8>,
37}
38
39#[derive(Clone, Copy, PartialEq, Eq, Hash)]
42enum Refusal {
43 Glyph(CacheKey),
44 Synth(char, u32, u32),
45 Path(u64),
47}
48
49struct Shelf {
50 y: u32,
51 h: u32,
52 cursor_x: u32,
53}
54
55struct Prev {
59 size: u32,
60 pixels: Vec<u8>,
61 map: FxHashMap<CacheKey, Option<GlyphSlot>>,
62 synth: FxHashMap<(char, u32, u32), Option<GlyphSlot>>,
63 paths: FxHashMap<u64, Option<GlyphSlot>>,
64}
65
66impl Prev {
67 fn texels(&self, x: u32, y: u32, w: u32, h: u32) -> Vec<u8> {
69 let stride = (self.size * 4) as usize;
70 let row = (w * 4) as usize;
71 let mut out = Vec::with_capacity(row * h as usize);
72 for r in 0..h as usize {
73 let at = (y as usize + r) * stride + (x * 4) as usize;
74 out.extend_from_slice(&self.pixels[at..at + row]);
75 }
76 out
77 }
78}
79
80pub struct GlyphAtlas {
117 pub size: u32,
118 pub pixels: Vec<u8>,
120 pub dirty: bool,
122 pub epoch: u64,
126 pub stamp: u64,
131 map: FxHashMap<CacheKey, Option<GlyphSlot>>,
132 images: FxHashMap<ImageId, Option<GlyphSlot>>,
135 synth: FxHashMap<(char, u32, u32), Option<GlyphSlot>>,
140 paths: FxHashMap<u64, Option<GlyphSlot>>,
146 shelves: Vec<Shelf>,
147 next_shelf_y: u32,
148 base: u32,
153 fresh: bool,
156 short: bool,
160 rows_per_frame: u32,
166 rows_at_begin: u32,
167 frames_since_reset: u32,
171 turned: bool,
179 refused: FxHashMap<Refusal, (u64, u64)>,
183 placed: u64,
187 held: u64,
188 demand: u64,
192 seen: FxHashSet<(u32, u32)>,
193 wanted: bool,
194 frame: u64,
196 prev: Option<Prev>,
199}
200
201impl GlyphAtlas {
202 pub fn new() -> Self {
203 Self::with_size(ATLAS_SIZE)
204 }
205
206 pub fn with_size(size: u32) -> Self {
207 Self {
208 size,
209 pixels: vec![0; (size * size * 4) as usize],
210 dirty: false,
211 epoch: 0,
212 stamp: 0,
213 map: FxHashMap::default(),
214 images: FxHashMap::default(),
215 synth: FxHashMap::default(),
216 paths: FxHashMap::default(),
217 shelves: Vec::new(),
218 next_shelf_y: 0,
219 base: size,
220 fresh: true,
221 short: false,
222 rows_per_frame: 0,
223 rows_at_begin: 0,
224 frames_since_reset: u32::MAX,
225 turned: false,
226 refused: FxHashMap::default(),
227 placed: 0,
228 held: 0,
229 demand: 0,
230 seen: FxHashSet::default(),
231 wanted: false,
232 frame: 0,
233 prev: None,
234 }
235 }
236
237 pub fn begin_frame(&mut self) {
261 self.prev = None;
263 if self.fresh && !self.refused.is_empty() {
264 self.held = self.placed;
265 }
266 let refit = self.refit();
267 let rows = self.next_shelf_y;
268 let opened = if self.fresh {
271 0
272 } else {
273 rows.saturating_sub(self.rows_at_begin)
274 };
275 self.rows_per_frame = opened.max(self.rows_per_frame - self.rows_per_frame / 4);
276 self.frames_since_reset = self.frames_since_reset.saturating_add(1);
277 self.turned |= opened > 0;
278 let extended = self.size > self.base;
279 let filling = self.turned && self.size - rows < 2 * self.rows_per_frame;
280 let thrash = self.frames_since_reset <= 2;
281 if self.short || refit {
282 self.reset_to(self.base, true);
283 } else if extended && thrash {
284 self.base = self.size;
285 } else if extended {
286 self.reset_to(self.base, true);
287 } else if filling && thrash && self.size < MAX_ATLAS_SIZE {
288 self.extend_to((self.size * 2).min(MAX_ATLAS_SIZE));
289 self.base = self.size;
290 } else if filling {
291 self.reset_to(self.base, true);
292 }
293 self.short = false;
294 self.fresh = self.next_shelf_y == 0;
295 self.rows_at_begin = self.next_shelf_y;
296 self.frame += 1;
297 self.demand = 0;
298 self.seen.clear();
299 self.wanted = false;
300 if !self.refused.is_empty() {
301 self.stamp += 1;
302 }
303 }
304
305 pub(crate) fn end_frame(&mut self) {
311 self.prev = None;
312 }
313
314 #[cfg(test)]
317 pub(crate) fn reset_next_frame(&mut self) {
318 self.short = true;
319 }
320
321 #[cfg(test)]
323 pub(crate) fn keeps_prev(&self) -> bool {
324 self.prev.is_some()
325 }
326
327 pub(crate) fn short(&self) -> bool {
331 self.short || self.refit()
332 }
333
334 fn refit(&self) -> bool {
338 !self.fresh && self.wanted && self.demand <= self.held
339 }
340
341 fn note_slot(&mut self, slot: GlyphSlot) {
344 if !self.refused.is_empty() && self.seen.insert((slot.x, slot.y)) {
345 self.demand += texels(slot.w, slot.h);
346 }
347 }
348
349 fn note_refusal(&mut self, refusal: Refusal) {
353 if let Some((cost, last)) = self.refused.get_mut(&refusal)
354 && *last != self.frame
355 {
356 *last = self.frame;
357 self.demand += *cost;
358 self.wanted = true;
359 }
360 }
361
362 fn refuse(&mut self, refusal: Refusal, w: u32, h: u32) {
365 if self.fresh && w < MAX_ATLAS_SIZE && h < MAX_ATLAS_SIZE {
366 self.refused.insert(refusal, (texels(w, h), self.frame));
367 self.demand += texels(w, h);
368 self.wanted = true;
369 }
370 }
371
372 pub fn clear(&mut self) {
376 self.prev = None;
377 self.reset_to(self.size, false);
378 }
379
380 fn reset_to(&mut self, size: u32, keep: bool) {
384 if keep {
385 let pixels = std::mem::replace(&mut self.pixels, vec![0; (size * size * 4) as usize]);
386 self.prev = Some(Prev {
387 size: self.size,
388 pixels,
389 map: std::mem::take(&mut self.map),
390 synth: std::mem::take(&mut self.synth),
391 paths: std::mem::take(&mut self.paths),
392 });
393 self.size = size;
394 } else if size == self.size {
395 self.pixels.fill(0);
396 } else {
397 self.size = size;
398 self.pixels = vec![0; (size * size * 4) as usize];
399 }
400 self.map.clear();
401 self.images.clear();
402 self.synth.clear();
403 self.paths.clear();
404 self.shelves.clear();
405 self.next_shelf_y = 0;
406 self.epoch += 1;
407 self.stamp += 1;
408 self.dirty = true;
409 self.frames_since_reset = 0;
410 self.turned = false;
411 self.refused.clear();
412 self.placed = 0;
413 self.held = 0;
414 }
415
416 fn extend_to(&mut self, size: u32) {
421 let old = self.size as usize;
422 let mut pixels = vec![0; (size * size * 4) as usize];
423 for (row, src) in self.pixels.chunks_exact(old * 4).enumerate() {
424 let at = row * size as usize * 4;
425 pixels[at..at + old * 4].copy_from_slice(src);
426 }
427 self.pixels = pixels;
428 self.size = size;
429 self.epoch += 1;
430 self.stamp += 1;
431 self.dirty = true;
432 }
433
434 fn alloc_or_make_room(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
445 let pos = self.make_room(w, h);
446 if pos.is_some() {
447 self.placed += texels(w, h);
448 }
449 pos
450 }
451
452 fn make_room(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
453 if let Some(pos) = self.alloc(w, h) {
454 return Some(pos);
455 }
456 if w + 1 > MAX_ATLAS_SIZE || h + 1 > MAX_ATLAS_SIZE {
457 return None; }
459 loop {
460 let bigger = (self.size * 2).min(MAX_ATLAS_SIZE);
461 if bigger == self.size {
462 if !self.fresh {
467 self.short = true;
468 }
469 return None;
470 }
471 self.extend_to(bigger);
472 if self.fresh || w + 1 > self.base || h + 1 > self.base {
473 self.base = self.size;
474 }
475 if let Some(pos) = self.alloc(w, h) {
476 return Some(pos);
477 }
478 }
479 }
480
481 fn alloc(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
483 let (pw, ph) = (w + 1, h + 1);
484 if pw > self.size || ph > self.size {
485 return None;
486 }
487 for shelf in &mut self.shelves {
489 if ph <= shelf.h && shelf.h <= ph.saturating_mul(2) && shelf.cursor_x + pw <= self.size
490 {
491 let pos = (shelf.cursor_x, shelf.y);
492 shelf.cursor_x += pw;
493 return Some(pos);
494 }
495 }
496 let shelf_h = ph.next_multiple_of(8);
498 if self.next_shelf_y + shelf_h > self.size {
499 return None;
500 }
501 let shelf = Shelf {
502 y: self.next_shelf_y,
503 h: shelf_h,
504 cursor_x: pw,
505 };
506 self.next_shelf_y += shelf_h;
507 let pos = (0, shelf.y);
508 self.shelves.push(shelf);
509 Some(pos)
510 }
511
512 fn blit(&mut self, x: u32, y: u32, w: u32, h: u32, data: &[u8]) {
513 let stride = (self.size * 4) as usize;
514 for row in 0..h as usize {
515 let src = row * (w as usize) * 4;
516 let dst = (y as usize + row) * stride + (x as usize) * 4;
517 self.pixels[dst..dst + (w as usize) * 4]
518 .copy_from_slice(&data[src..src + (w as usize) * 4]);
519 }
520 self.dirty = true;
521 }
522
523 fn carried(&self, key: &CacheKey) -> Option<RasterGlyph> {
527 let prev = self.prev.as_ref()?;
528 let slot = (*prev.map.get(key)?)?;
529 Some(RasterGlyph {
530 w: slot.w,
531 h: slot.h,
532 left: slot.left,
533 top: slot.top,
534 color: slot.color_glyph,
535 subpixel: slot.subpixel,
536 data: prev.texels(slot.x, slot.y, slot.w, slot.h),
537 })
538 }
539
540 pub fn get_or_insert(
544 &mut self,
545 key: CacheKey,
546 raster: impl FnOnce() -> Option<RasterGlyph>,
547 ) -> Option<GlyphSlot> {
548 if let Some(&slot) = self.map.get(&key) {
549 match slot {
550 Some(slot) => self.note_slot(slot),
551 None => self.note_refusal(Refusal::Glyph(key)),
552 }
553 return slot;
554 }
555 let Some(glyph) = self.carried(&key).or_else(raster) else {
556 self.map.insert(key, None);
557 return None;
558 };
559 let Some((x, y)) = self.alloc_or_make_room(glyph.w, glyph.h) else {
560 self.refuse(Refusal::Glyph(key), glyph.w, glyph.h);
561 self.map.insert(key, None);
562 return None;
563 };
564 self.blit(x, y, glyph.w, glyph.h, &glyph.data);
565 let slot = GlyphSlot {
566 x,
567 y,
568 w: glyph.w,
569 h: glyph.h,
570 left: glyph.left,
571 top: glyph.top,
572 color_glyph: glyph.color,
573 subpixel: glyph.subpixel,
574 };
575 self.note_slot(slot);
576 self.map.insert(key, Some(slot));
577 Some(slot)
578 }
579
580 pub fn get_or_insert_synth(
585 &mut self,
586 ch: char,
587 w: u32,
588 h: u32,
589 coverage: impl FnOnce() -> Vec<u8>,
590 ) -> Option<GlyphSlot> {
591 if let Some(&slot) = self.synth.get(&(ch, w, h)) {
592 match slot {
593 Some(slot) => self.note_slot(slot),
594 None => self.note_refusal(Refusal::Synth(ch, w, h)),
595 }
596 return slot;
597 }
598 let Some((x, y)) = self.alloc_or_make_room(w, h) else {
599 self.refuse(Refusal::Synth(ch, w, h), w, h);
600 self.synth.insert((ch, w, h), None);
601 return None;
602 };
603 let carried = self.prev.as_ref().and_then(|prev| {
604 let slot = (*prev.synth.get(&(ch, w, h))?)?;
605 Some(prev.texels(slot.x, slot.y, w, h))
606 });
607 let rgba = carried.unwrap_or_else(|| {
608 let mask = coverage();
609 debug_assert_eq!(mask.len(), (w * h) as usize);
610 let mut rgba = Vec::with_capacity(mask.len() * 4);
611 for &a in &mask {
612 rgba.extend_from_slice(&[255, 255, 255, a]);
613 }
614 rgba
615 });
616 self.blit(x, y, w, h, &rgba);
617 let slot = GlyphSlot {
618 x,
619 y,
620 w,
621 h,
622 left: 0,
623 top: 0,
624 color_glyph: false,
625 subpixel: false,
626 };
627 self.note_slot(slot);
628 self.synth.insert((ch, w, h), Some(slot));
629 Some(slot)
630 }
631
632 pub fn get_or_insert_path(
640 &mut self,
641 key: u64,
642 w: u32,
643 h: u32,
644 coverage: impl FnOnce() -> Vec<u8>,
645 ) -> Option<GlyphSlot> {
646 self.get_or_insert_keyed(key, w, h, false, || {
647 let mask = coverage();
648 debug_assert_eq!(mask.len(), (w * h) as usize);
649 let mut rgba = Vec::with_capacity(mask.len() * 4);
650 for &a in &mask {
651 rgba.extend_from_slice(&[255, 255, 255, a]);
652 }
653 rgba
654 })
655 }
656
657 pub fn get_or_insert_gradient(
664 &mut self,
665 key: u64,
666 w: u32,
667 h: u32,
668 rgba: impl FnOnce() -> Vec<u8>,
669 ) -> Option<GlyphSlot> {
670 self.get_or_insert_keyed(key, w, h, true, rgba)
671 }
672
673 fn get_or_insert_keyed(
676 &mut self,
677 key: u64,
678 w: u32,
679 h: u32,
680 color: bool,
681 rgba: impl FnOnce() -> Vec<u8>,
682 ) -> Option<GlyphSlot> {
683 if let Some(&slot) = self.paths.get(&key) {
684 match slot {
685 Some(slot) => self.note_slot(slot),
686 None => self.note_refusal(Refusal::Path(key)),
687 }
688 return slot;
689 }
690 let Some((x, y)) = self.alloc_or_make_room(w, h) else {
691 self.refuse(Refusal::Path(key), w, h);
692 self.paths.insert(key, None);
693 return None;
694 };
695 let carried = self.prev.as_ref().and_then(|prev| {
696 let slot = (*prev.paths.get(&key)?)?;
697 (slot.w == w && slot.h == h).then(|| prev.texels(slot.x, slot.y, w, h))
698 });
699 let rgba = carried.unwrap_or_else(rgba);
700 debug_assert_eq!(rgba.len(), (w * h * 4) as usize);
701 self.blit(x, y, w, h, &rgba);
702 let slot = GlyphSlot {
703 x,
704 y,
705 w,
706 h,
707 left: 0,
708 top: 0,
709 color_glyph: color,
710 subpixel: false,
711 };
712 self.note_slot(slot);
713 self.paths.insert(key, Some(slot));
714 Some(slot)
715 }
716
717 pub fn has_path(&self, key: u64) -> bool {
720 matches!(self.paths.get(&key), Some(Some(_)))
721 }
722
723 pub fn get_or_insert_image(
726 &mut self,
727 id: ImageId,
728 w: u32,
729 h: u32,
730 rgba: &[u8],
731 ) -> Option<GlyphSlot> {
732 if let Some(&slot) = self.images.get(&id) {
733 if let Some(slot) = slot {
736 self.note_slot(slot);
737 }
738 return slot;
739 }
740 debug_assert_eq!(rgba.len(), (w * h * 4) as usize);
741 let Some((x, y)) = self.alloc_or_make_room(w, h) else {
742 self.images.insert(id, None);
743 return None;
744 };
745 self.blit(x, y, w, h, rgba);
746 let slot = GlyphSlot {
747 x,
748 y,
749 w,
750 h,
751 left: 0,
752 top: 0,
753 color_glyph: true,
754 subpixel: false,
755 };
756 self.note_slot(slot);
757 self.images.insert(id, Some(slot));
758 Some(slot)
759 }
760
761 pub fn evict_image(&mut self, id: ImageId) {
764 self.images.remove(&id);
765 }
766
767 pub fn retain_images(&mut self, live: impl Fn(ImageId) -> bool) {
771 self.images.retain(|id, _| live(*id));
772 }
773
774 pub fn has_image(&self, id: ImageId) -> bool {
776 self.images.contains_key(&id)
777 }
778}
779
780fn texels(w: u32, h: u32) -> u64 {
782 u64::from(w + 1) * u64::from(h + 1)
783}
784
785impl Default for GlyphAtlas {
786 fn default() -> Self {
787 Self::new()
788 }
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 fn fake_key(i: u32) -> CacheKey {
796 CacheKey {
798 font_id: cosmic_text::fontdb::ID::dummy(),
799 glyph_id: i as u16,
800 font_size_bits: (12.0f32 + (i / 65536) as f32).to_bits(),
801 x_bin: cosmic_text::SubpixelBin::Zero,
802 y_bin: cosmic_text::SubpixelBin::Zero,
803 font_weight: cosmic_text::fontdb::Weight::NORMAL,
804 flags: cosmic_text::CacheKeyFlags::empty(),
805 }
806 }
807
808 fn raster(w: u32, h: u32) -> RasterGlyph {
809 RasterGlyph {
810 w,
811 h,
812 left: 0,
813 top: 0,
814 color: false,
815 subpixel: false,
816 data: vec![0xff; (w * h * 4) as usize],
817 }
818 }
819
820 #[test]
821 fn slots_stay_in_bounds_and_do_not_overlap() {
822 let mut atlas = GlyphAtlas::with_size(256);
823 let mut slots = Vec::new();
824 for i in 0..200 {
825 let (w, h) = (5 + (i % 13), 7 + (i % 9));
826 if let Some(s) = atlas.get_or_insert(fake_key(i), || Some(raster(w, h))) {
827 assert!(
828 s.x + s.w <= 256 && s.y + s.h <= 256,
829 "slot out of bounds: {s:?}"
830 );
831 slots.push(s);
832 }
833 }
834 assert!(!slots.is_empty());
835 for (i, a) in slots.iter().enumerate() {
836 for b in slots.iter().skip(i + 1) {
837 let disjoint =
838 a.x + a.w <= b.x || b.x + b.w <= a.x || a.y + a.h <= b.y || b.y + b.h <= a.y;
839 assert!(disjoint, "overlap: {a:?} vs {b:?}");
840 }
841 }
842 }
843
844 #[test]
845 fn lookup_is_cached() {
846 let mut atlas = GlyphAtlas::with_size(128);
847 let mut calls = 0;
848 let key = fake_key(1);
849 for _ in 0..3 {
850 atlas.get_or_insert(key, || {
851 calls += 1;
852 Some(raster(10, 10))
853 });
854 }
855 assert_eq!(calls, 1);
856 }
857
858 #[test]
859 fn unrasterizable_is_cached_as_none() {
860 let mut atlas = GlyphAtlas::with_size(128);
861 let mut calls = 0;
862 for _ in 0..3 {
863 let slot = atlas.get_or_insert(fake_key(2), || {
864 calls += 1;
865 None
866 });
867 assert!(slot.is_none());
868 }
869 assert_eq!(calls, 1);
870 }
871
872 fn filled(w: u32, h: u32, v: u8) -> RasterGlyph {
873 RasterGlyph {
874 data: vec![v; (w * h * 4) as usize],
875 ..raster(w, h)
876 }
877 }
878
879 fn holds(atlas: &GlyphAtlas, slot: GlyphSlot, v: u8) -> bool {
882 (slot.y..slot.y + slot.h).all(|y| {
883 let at = ((y * atlas.size + slot.x) * 4) as usize;
884 atlas.pixels[at..at + (slot.w * 4) as usize]
885 .iter()
886 .all(|&p| p == v)
887 })
888 }
889
890 #[test]
896 fn a_page_that_fills_mid_frame_keeps_the_frames_slots_until_it_ends() {
897 let mut atlas = GlyphAtlas::with_size(64);
898 atlas.begin_frame();
900 for i in 0..4u32 {
901 atlas.get_or_insert(fake_key(i), || Some(filled(30, 30, i as u8 + 1)));
902 }
903 assert_eq!(atlas.size, 64);
904 atlas.begin_frame();
907 let mut used = Vec::new();
908 for i in [0u32, 4, 5, 6, 7] {
909 let v = i as u8 + 1;
910 let slot = atlas
911 .get_or_insert(fake_key(i), || Some(filled(30, 30, v)))
912 .expect("room is made");
913 used.push((slot, v));
914 for &(slot, v) in &used {
915 assert!(
916 holds(&atlas, slot, v),
917 "a slot this frame used was overwritten"
918 );
919 }
920 }
921 assert_eq!(atlas.size, 128, "extended for the rest of the frame");
922 assert!(!atlas.short());
923 let epoch = atlas.epoch;
924 atlas.begin_frame();
928 assert_eq!(atlas.size, 64);
929 assert!(atlas.epoch > epoch, "reset before anything is emitted");
930 let mut rasterized = 0;
931 for i in [4u32, 5, 6, 7] {
932 let slot = atlas
933 .get_or_insert(fake_key(i), || {
934 rasterized += 1;
935 Some(filled(30, 30, i as u8 + 1))
936 })
937 .expect("room");
938 assert!(holds(&atlas, slot, i as u8 + 1), "copied whole");
939 }
940 assert_eq!(rasterized, 0);
941 assert_eq!(atlas.size, 64, "the set fits the page: no growth kept");
942 }
943
944 #[test]
948 fn a_working_set_larger_than_the_page_grows_it_and_then_holds() {
949 let mut atlas = GlyphAtlas::with_size(64);
950 atlas.begin_frame();
951 for i in 0..100 {
952 atlas.get_or_insert(fake_key(i), || Some(raster(30, 30)));
953 }
954 assert!(atlas.size >= 512, "grown to hold the set: {}", atlas.size);
955 let (size, epoch) = (atlas.size, atlas.epoch);
956 for _ in 0..3 {
957 atlas.begin_frame();
958 for i in 0..100 {
959 atlas.get_or_insert(fake_key(i), || panic!("cached"));
960 }
961 }
962 assert_eq!(
963 (atlas.size, atlas.epoch),
964 (size, epoch),
965 "still from then on"
966 );
967 let mut atlas = GlyphAtlas::with_size(64);
971 let mut sizes = Vec::new();
972 for frame in 0..40u32 {
973 atlas.begin_frame();
974 let (size, epoch) = (atlas.size, atlas.epoch);
975 for i in 0..4 {
976 atlas.get_or_insert(fake_key(frame * 4 + i), || Some(raster(30, 30)));
977 }
978 if frame >= 20 {
979 assert_eq!(
980 (atlas.size, atlas.epoch),
981 (size, epoch),
982 "no fill mid-frame"
983 );
984 }
985 sizes.push(atlas.size);
986 }
987 assert!(sizes[20..].iter().all(|&s| s == sizes[39]), "{sizes:?}");
988 assert!(sizes[39] <= 256, "bounded: {sizes:?}");
989 }
990
991 #[test]
995 fn a_turnover_the_rows_foresee_is_emptied_between_frames() {
996 let mut atlas = GlyphAtlas::with_size(256);
999 let mut resets = 0;
1000 for frame in 0..40u32 {
1001 let epoch = atlas.epoch;
1002 atlas.begin_frame();
1003 if atlas.epoch != epoch {
1004 resets += 1;
1005 }
1006 let (size, epoch) = (atlas.size, atlas.epoch);
1007 for i in 0..8 {
1008 atlas.get_or_insert(fake_key(frame * 8 + i), || Some(raster(30, 30)));
1009 }
1010 assert_eq!(
1011 (atlas.size, atlas.epoch),
1012 (size, epoch),
1013 "frame {frame}: the page filled mid-frame"
1014 );
1015 }
1016 assert_eq!(atlas.size, 256);
1017 assert!(resets >= 4, "emptied between frames: {resets}");
1018 }
1019
1020 #[test]
1025 fn a_page_emptied_again_within_two_frames_grows_with_its_slots() {
1026 let mut atlas = GlyphAtlas::with_size(256);
1027 let mut next = 1000;
1029 let mut frame = |atlas: &mut GlyphAtlas| {
1030 atlas.begin_frame();
1031 for i in 0..48 {
1032 atlas.get_or_insert(fake_key(i), || Some(raster(30, 30)));
1033 }
1034 for _ in 0..8 {
1035 next += 1;
1036 atlas.get_or_insert(fake_key(next), || Some(raster(30, 30)));
1037 }
1038 };
1039 frame(&mut atlas); frame(&mut atlas); let epoch = atlas.epoch;
1042 frame(&mut atlas); assert_eq!((atlas.size, atlas.epoch), (256, epoch + 1));
1044 frame(&mut atlas); assert_eq!(atlas.size, 256);
1046 atlas.begin_frame(); assert_eq!(atlas.size, 512, "grown instead");
1048 for i in 0..48 {
1049 atlas.get_or_insert(fake_key(i), || panic!("kept in place"));
1050 }
1051 let epoch = atlas.epoch;
1052 for _ in 0..40 {
1053 frame(&mut atlas);
1054 assert_eq!(atlas.size, 512, "and it holds");
1055 }
1056 assert!(
1057 atlas.epoch > epoch,
1058 "emptied between frames as it turns over"
1059 );
1060 }
1061
1062 #[test]
1068 fn a_set_between_one_page_and_two_grows_on_its_second_frame() {
1069 let mut atlas = GlyphAtlas::with_size(64);
1070 let frame = |atlas: &mut GlyphAtlas, keys: std::ops::Range<u32>| {
1071 atlas.begin_frame();
1072 for i in keys {
1073 atlas.get_or_insert(fake_key(i), || Some(raster(30, 30)));
1074 }
1075 };
1076 frame(&mut atlas, 100..102);
1077 frame(&mut atlas, 0..6);
1079 assert_eq!(atlas.size, 128, "extended for the frame");
1080 frame(&mut atlas, 0..6);
1081 assert_eq!(atlas.size, 128, "the set alone overflowed: kept");
1082 let epoch = atlas.epoch;
1083 frame(&mut atlas, 0..6);
1084 frame(&mut atlas, 0..6);
1085 assert_eq!(atlas.epoch, epoch, "and still from then on");
1086 }
1087
1088 #[test]
1092 fn a_fill_long_after_a_reset_resets_even_with_a_dropped_glyph_back() {
1093 let mut atlas = GlyphAtlas::with_size(64);
1094 let frame = |atlas: &mut GlyphAtlas, keys: &[u32]| {
1096 atlas.begin_frame();
1097 for &i in keys {
1098 atlas.get_or_insert(fake_key(i), || Some(raster(30, 30)));
1099 }
1100 };
1101 frame(&mut atlas, &[0, 1, 2, 3]);
1102 frame(&mut atlas, &[4, 5, 6, 7]);
1103 for _ in 0..3 {
1104 frame(&mut atlas, &[4, 5, 6, 7]);
1105 }
1106 assert_eq!(atlas.size, 64, "one turnover, one reset");
1107 frame(&mut atlas, &[0, 8, 9, 10]);
1108 frame(&mut atlas, &[0, 8, 9, 10]);
1109 assert_eq!(atlas.size, 64, "a later turnover resets, it does not grow");
1110 }
1111
1112 #[test]
1119 fn a_full_page_at_the_largest_size_refuses_for_one_frame() {
1120 let mut atlas = GlyphAtlas::with_size(MAX_ATLAS_SIZE);
1121 atlas.begin_frame();
1123 let mut used = Vec::new();
1124 for i in 0..16u32 {
1125 let v = i as u8 + 1;
1126 let slot = atlas
1127 .get_or_insert(fake_key(i), || Some(filled(1000, 1000, v)))
1128 .expect("fits");
1129 used.push((slot, v));
1130 }
1131 assert!(!atlas.short());
1132 atlas.begin_frame();
1133 assert!(
1134 atlas
1135 .get_or_insert(fake_key(16), || Some(raster(1000, 1000)))
1136 .is_none()
1137 );
1138 assert!(atlas.short(), "refused on a page holding the last frame");
1139 for &(slot, v) in &used {
1140 assert!(holds(&atlas, slot, v), "nothing dropped under the frame");
1141 }
1142 let epoch = atlas.epoch;
1143 atlas.begin_frame();
1144 assert!(atlas.epoch > epoch && !atlas.short());
1145 assert!(
1146 atlas
1147 .get_or_insert(fake_key(16), || Some(raster(1000, 1000)))
1148 .is_some()
1149 );
1150 for i in 0..16u32 {
1151 atlas.get_or_insert(fake_key(i), || Some(raster(1000, 1000)));
1152 }
1153 assert!(
1154 !atlas.short(),
1155 "a set bigger than the page on an empty one is not short"
1156 );
1157 }
1158
1159 #[test]
1166 fn a_glyph_refused_on_a_fresh_full_page_gets_room_once_its_set_fits() {
1167 let mut atlas = GlyphAtlas::with_size(MAX_ATLAS_SIZE);
1168 let frame = |atlas: &mut GlyphAtlas, keys: std::ops::Range<u32>| {
1170 atlas.begin_frame();
1171 keys.map(|i| atlas.get_or_insert(fake_key(i), || Some(raster(1000, 1000))))
1172 .collect::<Vec<_>>()
1173 };
1174 let slots = frame(&mut atlas, 0..17);
1175 assert!(slots[16].is_none() && !atlas.short(), "refused, not short");
1176 let slots = frame(&mut atlas, 13..17);
1178 assert!(slots[3].is_none(), "the cached refusal, this frame");
1179 assert!(atlas.short(), "and the next frame is owed");
1180 let epoch = atlas.epoch;
1181 let slots = frame(&mut atlas, 13..17);
1182 assert!(atlas.epoch > epoch, "emptied before anything is emitted");
1183 assert!(slots.iter().all(Option::is_some), "and drawn: {slots:?}");
1184 assert!(!atlas.short());
1185 let (epoch, stamp) = (atlas.epoch, atlas.stamp);
1186 for _ in 0..3 {
1187 frame(&mut atlas, 13..17);
1188 }
1189 assert_eq!((atlas.epoch, atlas.stamp), (epoch, stamp), "then still");
1190 }
1191
1192 #[test]
1198 fn a_set_bigger_than_the_largest_page_keeps_it_still() {
1199 let mut atlas = GlyphAtlas::with_size(MAX_ATLAS_SIZE);
1200 let frame = |atlas: &mut GlyphAtlas| {
1201 atlas.begin_frame();
1202 for i in 0..17u32 {
1203 atlas.get_or_insert(fake_key(i), || Some(raster(1000, 1000)));
1204 }
1205 };
1206 frame(&mut atlas);
1207 let epoch = atlas.epoch;
1208 for _ in 0..5 {
1209 let stamp = atlas.stamp;
1210 frame(&mut atlas);
1211 assert!(!atlas.short(), "the set does not fit: nothing owed");
1212 assert_eq!(atlas.epoch, epoch, "the page is kept");
1213 assert!(atlas.stamp > stamp, "measured again");
1214 }
1215 let mut atlas = GlyphAtlas::with_size(256);
1217 atlas.begin_frame();
1218 atlas.get_or_insert(fake_key(0), || Some(raster(30, 30)));
1219 let stamp = atlas.stamp;
1220 atlas.begin_frame();
1221 atlas.get_or_insert(fake_key(0), || panic!("cached"));
1222 assert_eq!(atlas.stamp, stamp);
1223 }
1224
1225 #[test]
1231 fn a_burst_that_fits_does_not_grow_the_page() {
1232 let mut atlas = GlyphAtlas::with_size(1024);
1234 let frame = |atlas: &mut GlyphAtlas, burst: bool| {
1235 atlas.begin_frame();
1236 for i in 0..297 {
1237 atlas.get_or_insert(fake_key(i), || Some(raster(30, 30)));
1238 }
1239 if burst {
1240 for i in 1000..1627 {
1241 atlas.get_or_insert(fake_key(i), || Some(raster(30, 30)));
1242 }
1243 }
1244 };
1245 for _ in 0..4 {
1246 frame(&mut atlas, false);
1247 }
1248 for n in 0..12 {
1249 frame(&mut atlas, true);
1250 assert_eq!(atlas.size, 1024, "frame {n} after the burst");
1251 }
1252 atlas.begin_frame();
1253 assert_eq!(atlas.size, 1024);
1254 }
1255
1256 #[test]
1264 fn the_frame_after_a_reset_copies_what_the_old_page_held() {
1265 let mut atlas = GlyphAtlas::with_size(64);
1266 atlas.begin_frame();
1267 let glyph = |v: u8| RasterGlyph {
1268 left: -2,
1269 top: 9,
1270 subpixel: true,
1271 ..filled(30, 30, v)
1272 };
1273 for i in 0..4u32 {
1274 atlas.get_or_insert(fake_key(i), || Some(glyph(i as u8 + 1)));
1275 }
1276 atlas.get_or_insert_synth('─', 8, 16, || vec![200; 8 * 16]);
1277 atlas.short = true; let epoch = atlas.epoch;
1279 atlas.begin_frame();
1280 assert!(atlas.epoch > epoch, "the page was emptied");
1281
1282 let mut rasterized = 0;
1283 for i in [2u32, 0] {
1284 let slot = atlas
1285 .get_or_insert(fake_key(i), || {
1286 rasterized += 1;
1287 Some(glyph(0))
1288 })
1289 .unwrap();
1290 assert!(holds(&atlas, slot, i as u8 + 1), "glyph {i}'s texels");
1291 assert_eq!((slot.left, slot.top, slot.subpixel), (-2, 9, true));
1292 }
1293 let mut drawn = 0;
1294 let shape = atlas
1295 .get_or_insert_synth('─', 8, 16, || {
1296 drawn += 1;
1297 vec![0; 8 * 16]
1298 })
1299 .unwrap();
1300 assert_eq!(
1301 atlas.pixels[((shape.y * atlas.size + shape.x) * 4 + 3) as usize],
1302 200
1303 );
1304 assert_eq!((rasterized, drawn), (0, 0), "copied, not drawn again");
1305 assert_eq!(
1306 atlas.placed,
1307 2 * texels(30, 30) + texels(8, 16),
1308 "only what was looked up is placed"
1309 );
1310
1311 atlas.end_frame();
1314 assert!(atlas.prev.is_none(), "not held past its frame");
1315 atlas.begin_frame();
1316 atlas.get_or_insert(fake_key(1), || {
1317 rasterized += 1;
1318 Some(glyph(2))
1319 });
1320 assert_eq!(rasterized, 1);
1321
1322 atlas.clear();
1324 atlas.begin_frame();
1325 atlas.get_or_insert(fake_key(1), || {
1326 rasterized += 1;
1327 Some(glyph(2))
1328 });
1329 assert_eq!(rasterized, 2);
1330 }
1331
1332 #[test]
1333 fn a_synthesized_shape_is_keyed_on_its_cell_size() {
1334 use std::cell::Cell;
1335 let mut atlas = GlyphAtlas::with_size(128);
1336 let calls = Cell::new(0);
1337 let draw = |atlas: &mut GlyphAtlas, w, h| {
1338 atlas
1339 .get_or_insert_synth('│', w, h, || {
1340 calls.set(calls.get() + 1);
1341 vec![255; (w * h) as usize]
1342 })
1343 .expect("fits")
1344 };
1345 let a = draw(&mut atlas, 7, 16);
1346 let b = draw(&mut atlas, 7, 16);
1347 let c = draw(&mut atlas, 7, 20);
1348 assert_eq!((a.x, a.y), (b.x, b.y), "the same size shares a slot");
1349 assert_ne!((a.x, a.y), (c.x, c.y), "another size does not");
1350 assert_eq!(calls.get(), 2);
1351 assert!(!a.color_glyph && !a.subpixel && a.left == 0 && a.top == 0);
1352 let i = ((a.y * atlas.size + a.x) * 4) as usize;
1354 assert_eq!(&atlas.pixels[i..i + 4], &[255, 255, 255, 255]);
1355 atlas.clear();
1356 draw(&mut atlas, 7, 16);
1357 assert_eq!(calls.get(), 3, "a reset drops the shape with the glyphs");
1358 }
1359
1360 #[test]
1361 fn oversized_content_grows_the_page() {
1362 let mut atlas = GlyphAtlas::with_size(64);
1363 let s = atlas.get_or_insert(fake_key(5), || Some(raster(200, 200)));
1364 assert!(s.is_some(), "the page should double until it fits");
1365 assert!(atlas.size >= 256, "size is {}", atlas.size);
1366 assert!(
1367 atlas
1368 .get_or_insert(fake_key(6), || Some(raster(10, 10)))
1369 .is_some()
1370 );
1371 }
1372
1373 #[test]
1374 fn impossible_content_is_rejected() {
1375 let mut atlas = GlyphAtlas::with_size(64);
1376 let big = MAX_ATLAS_SIZE + 1;
1377 let s = atlas.get_or_insert(fake_key(7), || Some(raster(big, 1)));
1378 assert!(s.is_none());
1379 assert!(
1381 atlas
1382 .get_or_insert(fake_key(8), || Some(raster(10, 10)))
1383 .is_some()
1384 );
1385 }
1386}