sprite_cache.rs

  1use std::{collections::HashMap, sync::Arc};
  2
  3use crate::{
  4    fonts::{FontId, GlyphId},
  5    geometry::{
  6        rect::RectI,
  7        vector::{vec2i, Vector2I},
  8    },
  9    FontCache,
 10};
 11use etagere::BucketedAtlasAllocator;
 12use metal::{MTLPixelFormat, TextureDescriptor};
 13use ordered_float::OrderedFloat;
 14
 15#[derive(Hash, Eq, PartialEq)]
 16struct GlyphDescriptor {
 17    font_id: FontId,
 18    font_size: OrderedFloat<f32>,
 19    glyph_id: GlyphId,
 20}
 21
 22pub struct SpriteCache {
 23    device: metal::Device,
 24    atlas_size: Vector2I,
 25    font_cache: Arc<FontCache>,
 26    atlasses: Vec<Atlas>,
 27    glyphs: HashMap<GlyphDescriptor, Option<(usize, RectI)>>,
 28}
 29
 30impl SpriteCache {
 31    pub fn new(device: metal::Device, size: Vector2I, font_cache: Arc<FontCache>) -> Self {
 32        let atlasses = vec![Atlas::new(&device, size)];
 33        Self {
 34            device,
 35            atlas_size: size,
 36            font_cache,
 37            atlasses,
 38            glyphs: Default::default(),
 39        }
 40    }
 41
 42    pub fn render_glyph(
 43        &mut self,
 44        font_id: FontId,
 45        font_size: f32,
 46        glyph_id: GlyphId,
 47        scale_factor: f32,
 48    ) -> Option<(usize, RectI)> {
 49        let font_cache = &self.font_cache;
 50        let atlasses = &mut self.atlasses;
 51        let atlas_size = self.atlas_size;
 52        let device = &self.device;
 53        self.glyphs
 54            .entry(GlyphDescriptor {
 55                font_id,
 56                font_size: OrderedFloat(font_size),
 57                glyph_id,
 58            })
 59            .or_insert_with(|| {
 60                let (size, mask) =
 61                    font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?;
 62                assert!(size.x() < atlas_size.x());
 63                assert!(size.y() < atlas_size.y());
 64
 65                let atlas = atlasses.last_mut().unwrap();
 66                if let Some(bounds) = atlas.try_insert(size, &mask) {
 67                    Some((atlasses.len() - 1, bounds))
 68                } else {
 69                    let mut atlas = Atlas::new(device, atlas_size);
 70                    let bounds = atlas.try_insert(size, &mask).unwrap();
 71                    atlasses.push(atlas);
 72                    Some((atlasses.len() - 1, bounds))
 73                }
 74            })
 75            .clone()
 76    }
 77
 78    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
 79        self.atlasses.get(atlas_id).map(|a| a.texture.as_ref())
 80    }
 81}
 82
 83struct Atlas {
 84    allocator: BucketedAtlasAllocator,
 85    texture: metal::Texture,
 86}
 87
 88impl Atlas {
 89    fn new(device: &metal::DeviceRef, size: Vector2I) -> Self {
 90        let descriptor = TextureDescriptor::new();
 91        descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
 92        descriptor.set_width(size.x() as u64);
 93        descriptor.set_height(size.y() as u64);
 94
 95        Self {
 96            allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
 97            texture: device.new_texture(&descriptor),
 98        }
 99    }
100
101    fn try_insert(&mut self, size: Vector2I, mask: &[u8]) -> Option<RectI> {
102        let allocation = self
103            .allocator
104            .allocate(etagere::size2(size.x(), size.y()))?;
105
106        let bounds = allocation.rectangle;
107        let region = metal::MTLRegion::new_2d(
108            bounds.min.x as u64,
109            bounds.min.y as u64,
110            bounds.width() as u64,
111            bounds.height() as u64,
112        );
113        self.texture
114            .replace_region(region, 0, mask.as_ptr() as *const _, size.x() as u64);
115        Some(RectI::from_points(
116            vec2i(bounds.min.x, bounds.min.y),
117            vec2i(bounds.max.x, bounds.max.y),
118        ))
119    }
120}