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