1use crate::{
2 fonts::{FontId, GlyphId},
3 geometry::{
4 rect::RectI,
5 vector::{vec2f, vec2i, Vector2F, Vector2I},
6 },
7 platform,
8};
9use etagere::BucketedAtlasAllocator;
10use metal::{MTLPixelFormat, TextureDescriptor};
11use ordered_float::OrderedFloat;
12use std::{borrow::Cow, collections::HashMap, sync::Arc};
13
14#[derive(Hash, Eq, PartialEq)]
15struct GlyphDescriptor {
16 font_id: FontId,
17 font_size: OrderedFloat<f32>,
18 glyph_id: GlyphId,
19 subpixel_variant: (u8, u8),
20}
21
22#[derive(Clone)]
23pub struct GlyphSprite {
24 pub atlas_id: usize,
25 pub atlas_origin: Vector2I,
26 pub offset: Vector2I,
27 pub size: Vector2I,
28}
29
30#[derive(Hash, Eq, PartialEq)]
31struct IconDescriptor {
32 path: Cow<'static, str>,
33 width: i32,
34 height: i32,
35}
36
37#[derive(Clone)]
38pub struct IconSprite {
39 pub atlas_id: usize,
40 pub atlas_origin: Vector2I,
41 pub size: Vector2I,
42}
43
44pub struct SpriteCache {
45 device: metal::Device,
46 atlas_size: Vector2I,
47 fonts: Arc<dyn platform::FontSystem>,
48 atlases: Vec<Atlas>,
49 glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
50 icons: HashMap<IconDescriptor, IconSprite>,
51}
52
53impl SpriteCache {
54 pub fn new(
55 device: metal::Device,
56 size: Vector2I,
57 fonts: Arc<dyn platform::FontSystem>,
58 ) -> Self {
59 let atlases = vec![Atlas::new(&device, size)];
60 Self {
61 device,
62 atlas_size: size,
63 fonts,
64 atlases,
65 glyphs: Default::default(),
66 icons: Default::default(),
67 }
68 }
69
70 pub fn atlas_size(&self) -> Vector2I {
71 self.atlas_size
72 }
73
74 pub fn render_glyph(
75 &mut self,
76 font_id: FontId,
77 font_size: f32,
78 glyph_id: GlyphId,
79 target_position: Vector2F,
80 scale_factor: f32,
81 ) -> Option<GlyphSprite> {
82 const SUBPIXEL_VARIANTS: u8 = 4;
83
84 let target_position = target_position * scale_factor;
85 let fonts = &self.fonts;
86 let atlases = &mut self.atlases;
87 let atlas_size = self.atlas_size;
88 let device = &self.device;
89 let subpixel_variant = (
90 (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
91 % SUBPIXEL_VARIANTS,
92 (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
93 % SUBPIXEL_VARIANTS,
94 );
95 self.glyphs
96 .entry(GlyphDescriptor {
97 font_id,
98 font_size: OrderedFloat(font_size),
99 glyph_id,
100 subpixel_variant,
101 })
102 .or_insert_with(|| {
103 let subpixel_shift = vec2f(
104 subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
105 subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
106 );
107 let (glyph_bounds, mask) = fonts.rasterize_glyph(
108 font_id,
109 font_size,
110 glyph_id,
111 subpixel_shift,
112 scale_factor,
113 )?;
114 assert!(glyph_bounds.width() < atlas_size.x());
115 assert!(glyph_bounds.height() < atlas_size.y());
116
117 let atlas_bounds = atlases
118 .last_mut()
119 .unwrap()
120 .try_insert(glyph_bounds.size(), &mask)
121 .unwrap_or_else(|| {
122 let mut atlas = Atlas::new(device, atlas_size);
123 let bounds = atlas.try_insert(glyph_bounds.size(), &mask).unwrap();
124 atlases.push(atlas);
125 bounds
126 });
127
128 Some(GlyphSprite {
129 atlas_id: atlases.len() - 1,
130 atlas_origin: atlas_bounds.origin(),
131 offset: glyph_bounds.origin(),
132 size: glyph_bounds.size(),
133 })
134 })
135 .clone()
136 }
137
138 pub fn render_icon(
139 &mut self,
140 size: Vector2F,
141 path: Cow<'static, str>,
142 svg: usvg::Tree,
143 target_position: Vector2F,
144 scale_factor: f32,
145 ) -> IconSprite {
146 const SUBPIXEL_VARIANTS: u8 = 4;
147
148 let target_position = target_position * scale_factor;
149 let subpixel_variant = (
150 (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
151 % SUBPIXEL_VARIANTS,
152 (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
153 % SUBPIXEL_VARIANTS,
154 );
155 let subpixel_shift = vec2f(
156 subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
157 subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
158 );
159
160 let atlases = &mut self.atlases;
161 let atlas_size = self.atlas_size;
162 let device = &self.device;
163 let size = (size * scale_factor).round().to_i32();
164 assert!(size.x() < atlas_size.x());
165 assert!(size.y() < atlas_size.y());
166 self.icons
167 .entry(IconDescriptor {
168 path,
169 width: size.x(),
170 height: size.y(),
171 })
172 .or_insert_with(|| {
173 let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32).unwrap();
174 resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
175 let mask = pixmap
176 .pixels()
177 .iter()
178 .map(|a| a.alpha())
179 .collect::<Vec<_>>();
180
181 let atlas_bounds = atlases
182 .last_mut()
183 .unwrap()
184 .try_insert(size, &mask)
185 .unwrap_or_else(|| {
186 let mut atlas = Atlas::new(device, atlas_size);
187 let bounds = atlas.try_insert(size, &mask).unwrap();
188 atlases.push(atlas);
189 bounds
190 });
191
192 IconSprite {
193 atlas_id: atlases.len() - 1,
194 atlas_origin: atlas_bounds.origin(),
195 size,
196 }
197 })
198 .clone()
199 }
200
201 pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
202 self.atlases.get(atlas_id).map(|a| a.texture.as_ref())
203 }
204}
205
206struct Atlas {
207 allocator: BucketedAtlasAllocator,
208 texture: metal::Texture,
209}
210
211impl Atlas {
212 fn new(device: &metal::DeviceRef, size: Vector2I) -> Self {
213 let descriptor = TextureDescriptor::new();
214 descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
215 descriptor.set_width(size.x() as u64);
216 descriptor.set_height(size.y() as u64);
217
218 Self {
219 allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
220 texture: device.new_texture(&descriptor),
221 }
222 }
223
224 fn try_insert(&mut self, size: Vector2I, mask: &[u8]) -> Option<RectI> {
225 let allocation = self
226 .allocator
227 .allocate(etagere::size2(size.x() + 1, size.y() + 1))?;
228
229 let bounds = allocation.rectangle;
230 let region = metal::MTLRegion::new_2d(
231 bounds.min.x as u64,
232 bounds.min.y as u64,
233 size.x() as u64,
234 size.y() as u64,
235 );
236 self.texture
237 .replace_region(region, 0, mask.as_ptr() as *const _, size.x() as u64);
238 Some(RectI::from_points(
239 vec2i(bounds.min.x, bounds.min.y),
240 vec2i(bounds.max.x, bounds.max.y),
241 ))
242 }
243}