sprite_cache.rs

  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}