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).round() as u8
 94                % SUBPIXEL_VARIANTS,
 95            (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
 96                % SUBPIXEL_VARIANTS,
 97        );
 98        self.glyphs
 99            .entry(GlyphDescriptor {
100                font_id,
101                font_size: OrderedFloat(font_size),
102                glyph_id,
103                subpixel_variant,
104            })
105            .or_insert_with(|| {
106                let subpixel_shift = vec2f(
107                    subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
108                    subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
109                );
110                let (glyph_bounds, mask) = fonts.rasterize_glyph(
111                    font_id,
112                    font_size,
113                    glyph_id,
114                    subpixel_shift,
115                    scale_factor,
116                    RasterizationOptions::Alpha,
117                )?;
118
119                let (alloc_id, atlas_bounds) = atlases
120                    .upload(glyph_bounds.size(), &mask)
121                    .expect("could not upload glyph");
122                Some(GlyphSprite {
123                    atlas_id: alloc_id.atlas_id,
124                    atlas_origin: atlas_bounds.origin(),
125                    offset: glyph_bounds.origin(),
126                    size: glyph_bounds.size(),
127                })
128            })
129            .clone()
130    }
131
132    pub fn render_icon(
133        &mut self,
134        size: Vector2I,
135        path: Cow<'static, str>,
136        svg: usvg::Tree,
137    ) -> Option<IconSprite> {
138        let atlases = &mut self.atlases;
139        match self.icons.entry(IconDescriptor {
140            path,
141            width: size.x(),
142            height: size.y(),
143        }) {
144            Entry::Occupied(entry) => Some(entry.get().clone()),
145            Entry::Vacant(entry) => {
146                let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?;
147                resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
148                let mask = pixmap
149                    .pixels()
150                    .iter()
151                    .map(|a| a.alpha())
152                    .collect::<Vec<_>>();
153                let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
154                let icon_sprite = IconSprite {
155                    atlas_id: alloc_id.atlas_id,
156                    atlas_origin: atlas_bounds.origin(),
157                    size,
158                };
159                Some(entry.insert(icon_sprite).clone())
160            }
161        }
162    }
163
164    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
165        self.atlases.texture(atlas_id)
166    }
167}