1use std::{collections::HashMap, sync::Arc};
2
3use crate::{
4 fonts::{FontId, GlyphId},
5 geometry::{
6 rect::RectI,
7 vector::{vec2i, Vector2I},
8 },
9 FontCache,
10};
11use etagere::BucketedAtlasAllocator;
12use metal::{MTLPixelFormat, TextureDescriptor};
13use ordered_float::OrderedFloat;
14
15#[derive(Hash, Eq, PartialEq)]
16struct GlyphDescriptor {
17 font_id: FontId,
18 font_size: OrderedFloat<f32>,
19 glyph_id: GlyphId,
20}
21
22pub struct SpriteCache {
23 device: metal::Device,
24 atlas_size: Vector2I,
25 font_cache: Arc<FontCache>,
26 atlasses: Vec<Atlas>,
27 glyphs: HashMap<GlyphDescriptor, Option<(usize, RectI)>>,
28}
29
30impl SpriteCache {
31 pub fn new(device: metal::Device, size: Vector2I, font_cache: Arc<FontCache>) -> Self {
32 let atlasses = vec![Atlas::new(&device, size)];
33 Self {
34 device,
35 atlas_size: size,
36 font_cache,
37 atlasses,
38 glyphs: Default::default(),
39 }
40 }
41
42 pub fn atlas_size(&self) -> Vector2I {
43 self.atlas_size
44 }
45
46 pub fn render_glyph(
47 &mut self,
48 font_id: FontId,
49 font_size: f32,
50 glyph_id: GlyphId,
51 scale_factor: f32,
52 ) -> Option<(usize, RectI)> {
53 let font_cache = &self.font_cache;
54 let atlasses = &mut self.atlasses;
55 let atlas_size = self.atlas_size;
56 let device = &self.device;
57 self.glyphs
58 .entry(GlyphDescriptor {
59 font_id,
60 font_size: OrderedFloat(font_size),
61 glyph_id,
62 })
63 .or_insert_with(|| {
64 let (size, mask) =
65 font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?;
66 assert!(size.x() < atlas_size.x());
67 assert!(size.y() < atlas_size.y());
68
69 let atlas = atlasses.last_mut().unwrap();
70 if let Some(bounds) = atlas.try_insert(size, &mask) {
71 Some((atlasses.len() - 1, RectI::new(bounds.origin(), size)))
72 } else {
73 let mut atlas = Atlas::new(device, atlas_size);
74 let bounds = atlas.try_insert(size, &mask).unwrap();
75 atlasses.push(atlas);
76 Some((atlasses.len() - 1, RectI::new(bounds.origin(), size)))
77 }
78 })
79 .clone()
80 }
81
82 pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
83 self.atlasses.get(atlas_id).map(|a| a.texture.as_ref())
84 }
85}
86
87struct Atlas {
88 allocator: BucketedAtlasAllocator,
89 texture: metal::Texture,
90}
91
92impl Atlas {
93 fn new(device: &metal::DeviceRef, size: Vector2I) -> Self {
94 let descriptor = TextureDescriptor::new();
95 descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
96 descriptor.set_width(size.x() as u64);
97 descriptor.set_height(size.y() as u64);
98
99 Self {
100 allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
101 texture: device.new_texture(&descriptor),
102 }
103 }
104
105 fn try_insert(&mut self, size: Vector2I, mask: &[u8]) -> Option<RectI> {
106 let allocation = self
107 .allocator
108 .allocate(etagere::size2(size.x(), size.y()))?;
109
110 let bounds = allocation.rectangle;
111 let region = metal::MTLRegion::new_2d(
112 bounds.min.x as u64,
113 bounds.min.y as u64,
114 size.x() as u64,
115 size.y() as u64,
116 );
117 self.texture
118 .replace_region(region, 0, mask.as_ptr() as *const _, size.x() as u64);
119 Some(RectI::from_points(
120 vec2i(bounds.min.x, bounds.min.y),
121 vec2i(bounds.max.x, bounds.max.y),
122 ))
123 }
124}