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