164 lines
4.9 KiB
Rust
164 lines
4.9 KiB
Rust
use super::atlas::AtlasAllocator;
|
|
use crate::{
|
|
fonts::{FontId, GlyphId},
|
|
geometry::vector::{vec2f, Vector2F, Vector2I},
|
|
platform::{self, RasterizationOptions},
|
|
};
|
|
use collections::hash_map::Entry;
|
|
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<f32>,
|
|
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<dyn platform::FontSystem>,
|
|
atlases: AtlasAllocator,
|
|
glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
|
|
icons: HashMap<IconDescriptor, IconSprite>,
|
|
scale_factor: f32,
|
|
}
|
|
|
|
impl SpriteCache {
|
|
pub fn new(
|
|
device: metal::Device,
|
|
size: Vector2I,
|
|
scale_factor: f32,
|
|
fonts: Arc<dyn platform::FontSystem>,
|
|
) -> 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(),
|
|
scale_factor,
|
|
}
|
|
}
|
|
|
|
pub fn set_scale_factor(&mut self, scale_factor: f32) {
|
|
if scale_factor != self.scale_factor {
|
|
self.icons.clear();
|
|
self.glyphs.clear();
|
|
self.atlases.clear();
|
|
}
|
|
self.scale_factor = scale_factor;
|
|
}
|
|
|
|
pub fn render_glyph(
|
|
&mut self,
|
|
font_id: FontId,
|
|
font_size: f32,
|
|
glyph_id: GlyphId,
|
|
target_position: Vector2F,
|
|
) -> Option<GlyphSprite> {
|
|
const SUBPIXEL_VARIANTS: u8 = 4;
|
|
|
|
let target_position = target_position * self.scale_factor;
|
|
let subpixel_variant = (
|
|
(target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
|
(target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
|
);
|
|
|
|
self.glyphs
|
|
.entry(GlyphDescriptor {
|
|
font_id,
|
|
font_size: OrderedFloat(font_size),
|
|
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) = self.fonts.rasterize_glyph(
|
|
font_id,
|
|
font_size,
|
|
glyph_id,
|
|
subpixel_shift,
|
|
self.scale_factor,
|
|
RasterizationOptions::Alpha,
|
|
)?;
|
|
|
|
let (alloc_id, atlas_bounds) = self
|
|
.atlases
|
|
.upload(glyph_bounds.size(), &mask)
|
|
.expect("could not upload glyph");
|
|
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,
|
|
) -> Option<IconSprite> {
|
|
let atlases = &mut self.atlases;
|
|
match self.icons.entry(IconDescriptor {
|
|
path,
|
|
width: size.x(),
|
|
height: size.y(),
|
|
}) {
|
|
Entry::Occupied(entry) => Some(entry.get().clone()),
|
|
Entry::Vacant(entry) => {
|
|
let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?;
|
|
resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
|
|
let mask = pixmap
|
|
.pixels()
|
|
.iter()
|
|
.map(|a| a.alpha())
|
|
.collect::<Vec<_>>();
|
|
let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
|
|
let icon_sprite = IconSprite {
|
|
atlas_id: alloc_id.atlas_id,
|
|
atlas_origin: atlas_bounds.origin(),
|
|
size,
|
|
};
|
|
Some(entry.insert(icon_sprite).clone())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
|
|
self.atlases.texture(atlas_id)
|
|
}
|
|
}
|