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