use super::atlas::{AllocId, AtlasAllocator}; use crate::{ fonts::{FontId, GlyphId}, geometry::{rect::RectI, vector::Vector2I}, platform::{FontSystem, RasterizationOptions}, scene::ImageGlyph, ImageData, }; use anyhow::anyhow; use metal::{MTLPixelFormat, TextureDescriptor, TextureRef}; use ordered_float::OrderedFloat; use std::{collections::HashMap, mem, sync::Arc}; #[derive(Hash, Eq, PartialEq)] struct GlyphDescriptor { font_id: FontId, font_size: OrderedFloat, glyph_id: GlyphId, } pub struct ImageCache { prev_frame: HashMap, curr_frame: HashMap, image_glyphs: HashMap>, atlases: AtlasAllocator, scale_factor: f32, fonts: Arc, } impl ImageCache { pub fn new( device: metal::Device, size: Vector2I, scale_factor: f32, fonts: Arc, ) -> Self { let descriptor = TextureDescriptor::new(); descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm); descriptor.set_width(size.x() as u64); descriptor.set_height(size.y() as u64); Self { prev_frame: Default::default(), curr_frame: Default::default(), image_glyphs: Default::default(), atlases: AtlasAllocator::new(device, descriptor), scale_factor, fonts, } } pub fn set_scale_factor(&mut self, scale_factor: f32) { if scale_factor != self.scale_factor { self.scale_factor = scale_factor; for (_, glyph) in self.image_glyphs.drain() { if let Some((alloc_id, _, _)) = glyph { self.atlases.deallocate(alloc_id); } } } } pub fn render(&mut self, image: &ImageData) -> (AllocId, RectI) { let (alloc_id, atlas_bounds) = self .prev_frame .remove(&image.id) .or_else(|| self.curr_frame.get(&image.id).copied()) .or_else(|| self.atlases.upload(image.size(), image.as_bytes())) .ok_or_else(|| anyhow!("could not upload image of size {:?}", image.size())) .unwrap(); self.curr_frame.insert(image.id, (alloc_id, atlas_bounds)); (alloc_id, atlas_bounds) } pub fn render_glyph(&mut self, image_glyph: &ImageGlyph) -> Option<(AllocId, RectI, Vector2I)> { *self .image_glyphs .entry(GlyphDescriptor { font_id: image_glyph.font_id, font_size: OrderedFloat(image_glyph.font_size), glyph_id: image_glyph.id, }) .or_insert_with(|| { let (glyph_bounds, bytes) = self.fonts.rasterize_glyph( image_glyph.font_id, image_glyph.font_size, image_glyph.id, Default::default(), self.scale_factor, RasterizationOptions::Bgra, )?; let (alloc_id, atlas_bounds) = self .atlases .upload(glyph_bounds.size(), &bytes) .ok_or_else(|| { anyhow!( "could not upload image glyph of size {:?}", glyph_bounds.size() ) }) .unwrap(); Some((alloc_id, atlas_bounds, glyph_bounds.origin())) }) } pub fn finish_frame(&mut self) { mem::swap(&mut self.prev_frame, &mut self.curr_frame); for (_, (id, _)) in self.curr_frame.drain() { self.atlases.deallocate(id); } } pub fn atlas_texture(&self, atlas_id: usize) -> Option<&TextureRef> { self.atlases.texture(atlas_id) } }