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    scale_factor: f32,
 47}
 48
 49impl SpriteCache {
 50    pub fn new(
 51        device: metal::Device,
 52        size: Vector2I,
 53        scale_factor: f32,
 54        fonts: Arc<dyn platform::FontSystem>,
 55    ) -> Self {
 56        let descriptor = TextureDescriptor::new();
 57        descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
 58        descriptor.set_width(size.x() as u64);
 59        descriptor.set_height(size.y() as u64);
 60        Self {
 61            fonts,
 62            atlases: AtlasAllocator::new(device, descriptor),
 63            glyphs: Default::default(),
 64            icons: Default::default(),
 65            scale_factor,
 66        }
 67    }
 68
 69    pub fn set_scale_factor(&mut self, scale_factor: f32) {
 70        if scale_factor != self.scale_factor {
 71            self.icons.clear();
 72            self.glyphs.clear();
 73            self.atlases.clear();
 74        }
 75        self.scale_factor = scale_factor;
 76    }
 77
 78    pub fn render_glyph(
 79        &mut self,
 80        font_id: FontId,
 81        font_size: f32,
 82        glyph_id: GlyphId,
 83        target_position: Vector2F,
 84    ) -> Option<GlyphSprite> {
 85        const SUBPIXEL_VARIANTS: u8 = 4;
 86
 87        let scale_factor = self.scale_factor;
 88        let target_position = target_position * scale_factor;
 89        let fonts = &self.fonts;
 90        let atlases = &mut self.atlases;
 91        let subpixel_variant = (
 92            (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
 93                % SUBPIXEL_VARIANTS,
 94            (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
 95                % SUBPIXEL_VARIANTS,
 96        );
 97        self.glyphs
 98            .entry(GlyphDescriptor {
 99                font_id,
100                font_size: OrderedFloat(font_size),
101                glyph_id,
102                subpixel_variant,
103            })
104            .or_insert_with(|| {
105                let subpixel_shift = vec2f(
106                    subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
107                    subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
108                );
109                let (glyph_bounds, mask) = fonts.rasterize_glyph(
110                    font_id,
111                    font_size,
112                    glyph_id,
113                    subpixel_shift,
114                    scale_factor,
115                )?;
116
117                let (alloc_id, atlas_bounds) = atlases.upload(glyph_bounds.size(), &mask);
118                Some(GlyphSprite {
119                    atlas_id: alloc_id.atlas_id,
120                    atlas_origin: atlas_bounds.origin(),
121                    offset: glyph_bounds.origin(),
122                    size: glyph_bounds.size(),
123                })
124            })
125            .clone()
126    }
127
128    pub fn render_icon(
129        &mut self,
130        size: Vector2I,
131        path: Cow<'static, str>,
132        svg: usvg::Tree,
133    ) -> IconSprite {
134        let atlases = &mut self.atlases;
135        self.icons
136            .entry(IconDescriptor {
137                path,
138                width: size.x(),
139                height: size.y(),
140            })
141            .or_insert_with(|| {
142                let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32).unwrap();
143                resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
144                let mask = pixmap
145                    .pixels()
146                    .iter()
147                    .map(|a| a.alpha())
148                    .collect::<Vec<_>>();
149
150                let (alloc_id, atlas_bounds) = atlases.upload(size, &mask);
151                IconSprite {
152                    atlas_id: alloc_id.atlas_id,
153                    atlas_origin: atlas_bounds.origin(),
154                    size,
155                }
156            })
157            .clone()
158    }
159
160    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
161        self.atlases.texture(atlas_id)
162    }
163}