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 atlas_size(&self) -> Vector2I {
 43        self.atlas_size
 44    }
 45
 46    pub fn render_glyph(
 47        &mut self,
 48        font_id: FontId,
 49        font_size: f32,
 50        glyph_id: GlyphId,
 51        scale_factor: f32,
 52    ) -> Option<(usize, RectI)> {
 53        let font_cache = &self.font_cache;
 54        let atlasses = &mut self.atlasses;
 55        let atlas_size = self.atlas_size;
 56        let device = &self.device;
 57        self.glyphs
 58            .entry(GlyphDescriptor {
 59                font_id,
 60                font_size: OrderedFloat(font_size),
 61                glyph_id,
 62            })
 63            .or_insert_with(|| {
 64                let (size, mask) =
 65                    font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?;
 66                assert!(size.x() < atlas_size.x());
 67                assert!(size.y() < atlas_size.y());
 68
 69                let atlas = atlasses.last_mut().unwrap();
 70                if let Some(bounds) = atlas.try_insert(size, &mask) {
 71                    Some((atlasses.len() - 1, RectI::new(bounds.origin(), size)))
 72                } else {
 73                    let mut atlas = Atlas::new(device, atlas_size);
 74                    let bounds = atlas.try_insert(size, &mask).unwrap();
 75                    atlasses.push(atlas);
 76                    Some((atlasses.len() - 1, RectI::new(bounds.origin(), size)))
 77                }
 78            })
 79            .clone()
 80    }
 81
 82    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
 83        self.atlasses.get(atlas_id).map(|a| a.texture.as_ref())
 84    }
 85}
 86
 87struct Atlas {
 88    allocator: BucketedAtlasAllocator,
 89    texture: metal::Texture,
 90}
 91
 92impl Atlas {
 93    fn new(device: &metal::DeviceRef, size: Vector2I) -> Self {
 94        let descriptor = TextureDescriptor::new();
 95        descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
 96        descriptor.set_width(size.x() as u64);
 97        descriptor.set_height(size.y() as u64);
 98
 99        Self {
100            allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
101            texture: device.new_texture(&descriptor),
102        }
103    }
104
105    fn try_insert(&mut self, size: Vector2I, mask: &[u8]) -> Option<RectI> {
106        let allocation = self
107            .allocator
108            .allocate(etagere::size2(size.x(), size.y()))?;
109
110        let bounds = allocation.rectangle;
111        let region = metal::MTLRegion::new_2d(
112            bounds.min.x as u64,
113            bounds.min.y as u64,
114            size.x() as u64,
115            size.y() as u64,
116        );
117        self.texture
118            .replace_region(region, 0, mask.as_ptr() as *const _, size.x() as u64);
119        Some(RectI::from_points(
120            vec2i(bounds.min.x, bounds.min.y),
121            vec2i(bounds.max.x, bounds.max.y),
122        ))
123    }
124}