image_cache.rs

  1use super::atlas::{AllocId, AtlasAllocator};
  2use crate::{
  3    fonts::{FontId, GlyphId},
  4    geometry::{rect::RectI, vector::Vector2I},
  5    platform::{FontSystem, RasterizationOptions},
  6    scene::ImageGlyph,
  7    ImageData,
  8};
  9use anyhow::anyhow;
 10use metal::{MTLPixelFormat, TextureDescriptor, TextureRef};
 11use ordered_float::OrderedFloat;
 12use std::{collections::HashMap, mem, sync::Arc};
 13
 14#[derive(Hash, Eq, PartialEq)]
 15struct GlyphDescriptor {
 16    font_id: FontId,
 17    font_size: OrderedFloat<f32>,
 18    glyph_id: GlyphId,
 19}
 20
 21pub struct ImageCache {
 22    prev_frame: HashMap<usize, (AllocId, RectI)>,
 23    curr_frame: HashMap<usize, (AllocId, RectI)>,
 24    image_glyphs: HashMap<GlyphDescriptor, Option<(AllocId, RectI, Vector2I)>>,
 25    atlases: AtlasAllocator,
 26    scale_factor: f32,
 27    fonts: Arc<dyn FontSystem>,
 28}
 29
 30impl ImageCache {
 31    pub fn new(
 32        device: metal::Device,
 33        size: Vector2I,
 34        scale_factor: f32,
 35        fonts: Arc<dyn FontSystem>,
 36    ) -> Self {
 37        let descriptor = TextureDescriptor::new();
 38        descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
 39        descriptor.set_width(size.x() as u64);
 40        descriptor.set_height(size.y() as u64);
 41        Self {
 42            prev_frame: Default::default(),
 43            curr_frame: Default::default(),
 44            image_glyphs: Default::default(),
 45            atlases: AtlasAllocator::new(device, descriptor),
 46            scale_factor,
 47            fonts,
 48        }
 49    }
 50
 51    pub fn set_scale_factor(&mut self, scale_factor: f32) {
 52        if scale_factor != self.scale_factor {
 53            self.scale_factor = scale_factor;
 54            for (_, glyph) in self.image_glyphs.drain() {
 55                if let Some((alloc_id, _, _)) = glyph {
 56                    self.atlases.deallocate(alloc_id);
 57                }
 58            }
 59        }
 60    }
 61
 62    pub fn render(&mut self, image: &ImageData) -> (AllocId, RectI) {
 63        let (alloc_id, atlas_bounds) = self
 64            .prev_frame
 65            .remove(&image.id)
 66            .or_else(|| self.curr_frame.get(&image.id).copied())
 67            .or_else(|| self.atlases.upload(image.size(), image.as_bytes()))
 68            .ok_or_else(|| anyhow!("could not upload image of size {:?}", image.size()))
 69            .unwrap();
 70        self.curr_frame.insert(image.id, (alloc_id, atlas_bounds));
 71        (alloc_id, atlas_bounds)
 72    }
 73
 74    pub fn render_glyph(&mut self, image_glyph: &ImageGlyph) -> Option<(AllocId, RectI, Vector2I)> {
 75        *self
 76            .image_glyphs
 77            .entry(GlyphDescriptor {
 78                font_id: image_glyph.font_id,
 79                font_size: OrderedFloat(image_glyph.font_size),
 80                glyph_id: image_glyph.id,
 81            })
 82            .or_insert_with(|| {
 83                let (glyph_bounds, bytes) = self.fonts.rasterize_glyph(
 84                    image_glyph.font_id,
 85                    image_glyph.font_size,
 86                    image_glyph.id,
 87                    Default::default(),
 88                    self.scale_factor,
 89                    RasterizationOptions::Bgra,
 90                )?;
 91                let (alloc_id, atlas_bounds) = self
 92                    .atlases
 93                    .upload(glyph_bounds.size(), &bytes)
 94                    .ok_or_else(|| {
 95                        anyhow!(
 96                            "could not upload image glyph of size {:?}",
 97                            glyph_bounds.size()
 98                        )
 99                    })
100                    .unwrap();
101                Some((alloc_id, atlas_bounds, glyph_bounds.origin()))
102            })
103    }
104
105    pub fn finish_frame(&mut self) {
106        mem::swap(&mut self.prev_frame, &mut self.curr_frame);
107        for (_, (id, _)) in self.curr_frame.drain() {
108            self.atlases.deallocate(id);
109        }
110    }
111
112    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&TextureRef> {
113        self.atlases.texture(atlas_id)
114    }
115}