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