sprite_cache.rs

  1use super::atlas::AtlasAllocator;
  2use crate::{
  3    fonts::{FontId, GlyphId},
  4    geometry::vector::{vec2f, Vector2F, Vector2I},
  5    platform,
  6};
  7use metal::{MTLPixelFormat, TextureDescriptor};
  8use ordered_float::OrderedFloat;
  9use std::{borrow::Cow, collections::HashMap, sync::Arc};
 10
 11#[derive(Hash, Eq, PartialEq)]
 12struct GlyphDescriptor {
 13    font_id: FontId,
 14    font_size: OrderedFloat<f32>,
 15    glyph_id: GlyphId,
 16    subpixel_variant: (u8, u8),
 17}
 18
 19#[derive(Clone)]
 20pub struct GlyphSprite {
 21    pub atlas_id: usize,
 22    pub atlas_origin: Vector2I,
 23    pub offset: Vector2I,
 24    pub size: Vector2I,
 25}
 26
 27#[derive(Hash, Eq, PartialEq)]
 28struct IconDescriptor {
 29    path: Cow<'static, str>,
 30    width: i32,
 31    height: i32,
 32}
 33
 34#[derive(Clone)]
 35pub struct IconSprite {
 36    pub atlas_id: usize,
 37    pub atlas_origin: Vector2I,
 38    pub size: Vector2I,
 39}
 40
 41pub struct SpriteCache {
 42    fonts: Arc<dyn platform::FontSystem>,
 43    atlases: AtlasAllocator,
 44    glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
 45    icons: HashMap<IconDescriptor, IconSprite>,
 46}
 47
 48impl SpriteCache {
 49    pub fn new(
 50        device: metal::Device,
 51        size: Vector2I,
 52        fonts: Arc<dyn platform::FontSystem>,
 53    ) -> Self {
 54        let descriptor = TextureDescriptor::new();
 55        descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
 56        descriptor.set_width(size.x() as u64);
 57        descriptor.set_height(size.y() as u64);
 58        Self {
 59            fonts,
 60            atlases: AtlasAllocator::new(device, descriptor),
 61            glyphs: Default::default(),
 62            icons: Default::default(),
 63        }
 64    }
 65
 66    pub fn render_glyph(
 67        &mut self,
 68        font_id: FontId,
 69        font_size: f32,
 70        glyph_id: GlyphId,
 71        target_position: Vector2F,
 72        scale_factor: f32,
 73    ) -> Option<GlyphSprite> {
 74        const SUBPIXEL_VARIANTS: u8 = 4;
 75
 76        let target_position = target_position * scale_factor;
 77        let fonts = &self.fonts;
 78        let atlases = &mut self.atlases;
 79        let subpixel_variant = (
 80            (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
 81                % SUBPIXEL_VARIANTS,
 82            (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
 83                % SUBPIXEL_VARIANTS,
 84        );
 85        self.glyphs
 86            .entry(GlyphDescriptor {
 87                font_id,
 88                font_size: OrderedFloat(font_size),
 89                glyph_id,
 90                subpixel_variant,
 91            })
 92            .or_insert_with(|| {
 93                let subpixel_shift = vec2f(
 94                    subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
 95                    subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
 96                );
 97                let (glyph_bounds, mask) = fonts.rasterize_glyph(
 98                    font_id,
 99                    font_size,
100                    glyph_id,
101                    subpixel_shift,
102                    scale_factor,
103                )?;
104
105                let (alloc_id, atlas_bounds) = atlases.upload(glyph_bounds.size(), &mask);
106                Some(GlyphSprite {
107                    atlas_id: alloc_id.atlas_id,
108                    atlas_origin: atlas_bounds.origin(),
109                    offset: glyph_bounds.origin(),
110                    size: glyph_bounds.size(),
111                })
112            })
113            .clone()
114    }
115
116    pub fn render_icon(
117        &mut self,
118        size: Vector2I,
119        path: Cow<'static, str>,
120        svg: usvg::Tree,
121    ) -> IconSprite {
122        let atlases = &mut self.atlases;
123        self.icons
124            .entry(IconDescriptor {
125                path,
126                width: size.x(),
127                height: size.y(),
128            })
129            .or_insert_with(|| {
130                let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32).unwrap();
131                resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
132                let mask = pixmap
133                    .pixels()
134                    .iter()
135                    .map(|a| a.alpha())
136                    .collect::<Vec<_>>();
137
138                let (alloc_id, atlas_bounds) = atlases.upload(size, &mask);
139                IconSprite {
140                    atlas_id: alloc_id.atlas_id,
141                    atlas_origin: atlas_bounds.origin(),
142                    size,
143                }
144            })
145            .clone()
146    }
147
148    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
149        self.atlases.texture(atlas_id)
150    }
151}