use super::atlas::AtlasAllocator; use crate::{ fonts::{FontId, GlyphId}, geometry::vector::{vec2f, Vector2F, Vector2I}, platform, }; use metal::{MTLPixelFormat, TextureDescriptor}; use ordered_float::OrderedFloat; use std::{borrow::Cow, collections::HashMap, sync::Arc}; #[derive(Hash, Eq, PartialEq)] struct GlyphDescriptor { font_id: FontId, font_size: OrderedFloat, scale_factor: OrderedFloat, glyph_id: GlyphId, subpixel_variant: (u8, u8), } #[derive(Clone)] pub struct GlyphSprite { pub atlas_id: usize, pub atlas_origin: Vector2I, pub offset: Vector2I, pub size: Vector2I, } #[derive(Hash, Eq, PartialEq)] struct IconDescriptor { path: Cow<'static, str>, width: i32, height: i32, } #[derive(Clone)] pub struct IconSprite { pub atlas_id: usize, pub atlas_origin: Vector2I, pub size: Vector2I, } pub struct SpriteCache { fonts: Arc, atlases: AtlasAllocator, glyphs: HashMap>, icons: HashMap, } impl SpriteCache { pub fn new( device: metal::Device, size: Vector2I, fonts: Arc, ) -> Self { let descriptor = TextureDescriptor::new(); descriptor.set_pixel_format(MTLPixelFormat::A8Unorm); descriptor.set_width(size.x() as u64); descriptor.set_height(size.y() as u64); Self { fonts, atlases: AtlasAllocator::new(device, descriptor), glyphs: Default::default(), icons: Default::default(), } } pub fn render_glyph( &mut self, font_id: FontId, font_size: f32, glyph_id: GlyphId, target_position: Vector2F, scale_factor: f32, ) -> Option { const SUBPIXEL_VARIANTS: u8 = 4; let target_position = target_position * scale_factor; let fonts = &self.fonts; let atlases = &mut self.atlases; let subpixel_variant = ( (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8 % SUBPIXEL_VARIANTS, (target_position.y().fract() * SUBPIXEL_VARIANTS as f32).round() as u8 % SUBPIXEL_VARIANTS, ); self.glyphs .entry(GlyphDescriptor { font_id, font_size: OrderedFloat(font_size), scale_factor: OrderedFloat(scale_factor), glyph_id, subpixel_variant, }) .or_insert_with(|| { let subpixel_shift = vec2f( subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32, subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32, ); let (glyph_bounds, mask) = fonts.rasterize_glyph( font_id, font_size, glyph_id, subpixel_shift, scale_factor, )?; let (alloc_id, atlas_bounds) = atlases.upload(glyph_bounds.size(), &mask); Some(GlyphSprite { atlas_id: alloc_id.atlas_id, atlas_origin: atlas_bounds.origin(), offset: glyph_bounds.origin(), size: glyph_bounds.size(), }) }) .clone() } pub fn render_icon( &mut self, size: Vector2I, path: Cow<'static, str>, svg: usvg::Tree, ) -> IconSprite { let atlases = &mut self.atlases; self.icons .entry(IconDescriptor { path, width: size.x(), height: size.y(), }) .or_insert_with(|| { let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32).unwrap(); resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut()); let mask = pixmap .pixels() .iter() .map(|a| a.alpha()) .collect::>(); let (alloc_id, atlas_bounds) = atlases.upload(size, &mask); IconSprite { atlas_id: alloc_id.atlas_id, atlas_origin: atlas_bounds.origin(), size, } }) .clone() } pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> { self.atlases.texture(atlas_id) } }