sprite_cache.rs

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