Add caching of swash rendering, significantly improves layout speed
This commit is contained in:
parent
0b5050e601
commit
a4959bbe7b
5 changed files with 243 additions and 92 deletions
136
examples/text/src/font/cache.rs
Normal file
136
examples/text/src/font/cache.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct CacheKey {
|
||||
pub x_bin: SubpixelBin,
|
||||
pub y_bin: SubpixelBin,
|
||||
pub font_size: i32,
|
||||
pub glyph_id: u16,
|
||||
}
|
||||
|
||||
impl CacheKey {
|
||||
pub fn new(pos: (f32, f32), font_size: i32, glyph_id: u16) -> (i32, i32, Self) {
|
||||
let (x, x_bin) = SubpixelBin::new(pos.0);
|
||||
let (y, y_bin) = SubpixelBin::new(pos.1);
|
||||
(
|
||||
x,
|
||||
y,
|
||||
Self {
|
||||
x_bin,
|
||||
y_bin,
|
||||
font_size,
|
||||
glyph_id,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "swash"))]
|
||||
pub type CacheItem = ();
|
||||
|
||||
#[cfg(feature = "swash")]
|
||||
pub type CacheItem = Option<swash::scale::image::Image>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum SubpixelBin {
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
}
|
||||
|
||||
impl SubpixelBin {
|
||||
pub fn new(pos: f32) -> (i32, Self) {
|
||||
let trunc = pos.trunc() as i32;
|
||||
let fract = pos.fract();
|
||||
if pos.is_sign_negative() {
|
||||
if fract > -0.125 {
|
||||
(trunc, Self::Zero)
|
||||
} else if fract > -0.375 {
|
||||
(trunc - 1, Self::Three)
|
||||
} else if fract > -0.625 {
|
||||
(trunc - 1, Self::Two)
|
||||
} else if fract > -0.875 {
|
||||
(trunc - 1, Self::One)
|
||||
} else {
|
||||
(trunc - 1, Self::Zero)
|
||||
}
|
||||
} else {
|
||||
if fract < 0.125 {
|
||||
(trunc, Self::Zero)
|
||||
} else if fract < 0.375 {
|
||||
(trunc, Self::One)
|
||||
} else if fract < 0.625 {
|
||||
(trunc, Self::Two)
|
||||
} else if fract < 0.875 {
|
||||
(trunc, Self::Three)
|
||||
} else {
|
||||
(trunc + 1, Self::Zero)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float(&self) -> f32 {
|
||||
match self {
|
||||
Self::Zero => 0.0,
|
||||
Self::One => 0.25,
|
||||
Self::Two => 0.5,
|
||||
Self::Three => 0.75,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subpixel_bins() {
|
||||
// POSITIVE TESTS
|
||||
|
||||
// Maps to 0.0
|
||||
assert_eq!(SubpixelBin::new(0.0), (0, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(0.124), (0, SubpixelBin::Zero));
|
||||
|
||||
// Maps to 0.25
|
||||
assert_eq!(SubpixelBin::new(0.125), (0, SubpixelBin::One));
|
||||
assert_eq!(SubpixelBin::new(0.25), (0, SubpixelBin::One));
|
||||
assert_eq!(SubpixelBin::new(0.374), (0, SubpixelBin::One));
|
||||
|
||||
// Maps to 0.5
|
||||
assert_eq!(SubpixelBin::new(0.375), (0, SubpixelBin::Two));
|
||||
assert_eq!(SubpixelBin::new(0.5), (0, SubpixelBin::Two));
|
||||
assert_eq!(SubpixelBin::new(0.624), (0, SubpixelBin::Two));
|
||||
|
||||
// Maps to 0.75
|
||||
assert_eq!(SubpixelBin::new(0.625), (0, SubpixelBin::Three));
|
||||
assert_eq!(SubpixelBin::new(0.75), (0, SubpixelBin::Three));
|
||||
assert_eq!(SubpixelBin::new(0.874), (0, SubpixelBin::Three));
|
||||
|
||||
// Maps to 1.0
|
||||
assert_eq!(SubpixelBin::new(0.875), (1, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(0.999), (1, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(1.0), (1, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(1.124), (1, SubpixelBin::Zero));
|
||||
|
||||
// NEGATIVE TESTS
|
||||
|
||||
// Maps to 0.0
|
||||
assert_eq!(SubpixelBin::new(-0.0), (0, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(-0.124), (0, SubpixelBin::Zero));
|
||||
|
||||
// Maps to 0.25
|
||||
assert_eq!(SubpixelBin::new(-0.125), (-1, SubpixelBin::Three));
|
||||
assert_eq!(SubpixelBin::new(-0.25), (-1, SubpixelBin::Three));
|
||||
assert_eq!(SubpixelBin::new(-0.374), (-1, SubpixelBin::Three));
|
||||
|
||||
// Maps to 0.5
|
||||
assert_eq!(SubpixelBin::new(-0.375), (-1, SubpixelBin::Two));
|
||||
assert_eq!(SubpixelBin::new(-0.5), (-1, SubpixelBin::Two));
|
||||
assert_eq!(SubpixelBin::new(-0.624), (-1, SubpixelBin::Two));
|
||||
|
||||
// Maps to 0.75
|
||||
assert_eq!(SubpixelBin::new(-0.625), (-1, SubpixelBin::One));
|
||||
assert_eq!(SubpixelBin::new(-0.75), (-1, SubpixelBin::One));
|
||||
assert_eq!(SubpixelBin::new(-0.874), (-1, SubpixelBin::One));
|
||||
|
||||
// Maps to 1.0
|
||||
assert_eq!(SubpixelBin::new(-0.875), (-1, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(-0.999), (-1, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(-1.0), (-1, SubpixelBin::Zero));
|
||||
assert_eq!(SubpixelBin::new(-1.124), (-1, SubpixelBin::Zero));
|
||||
}
|
||||
|
|
@ -1,24 +1,22 @@
|
|||
use core::marker::PhantomData;
|
||||
use super::{CacheKey, Font, FontLineIndex};
|
||||
|
||||
use super::FontLineIndex;
|
||||
|
||||
pub struct FontLayoutGlyph<'a, T: 'a> {
|
||||
pub struct FontLayoutGlyph<'a> {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub x: f32,
|
||||
pub w: f32,
|
||||
pub font: &'a Font<'a>,
|
||||
#[cfg(feature = "ab_glyph")]
|
||||
pub inner: Option<ab_glyph::OutlinedGlyph>,
|
||||
#[cfg(feature = "rusttype")]
|
||||
pub inner: rusttype::PositionedGlyph<'a>,
|
||||
#[cfg(feature = "swash")]
|
||||
pub inner: (i32, i32, Option<swash::scale::image::Image>),
|
||||
pub phantom: PhantomData<&'a T>,
|
||||
pub inner: (i32, i32, CacheKey),
|
||||
}
|
||||
|
||||
pub struct FontLayoutLine<'a> {
|
||||
pub line_i: FontLineIndex,
|
||||
pub glyphs: Vec<FontLayoutGlyph<'a, ()>>,
|
||||
pub glyphs: Vec<FontLayoutGlyph<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FontLayoutLine<'a> {
|
||||
|
|
@ -52,41 +50,81 @@ impl<'a> FontLayoutLine<'a> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "swash")]
|
||||
if let Some(ref image) = glyph.inner.2 {
|
||||
use swash::scale::image::Content;
|
||||
{
|
||||
use swash::scale::{Render, Source, StrikeWith};
|
||||
use swash::zeno::{Format, Vector};
|
||||
|
||||
let x = glyph.inner.0 + image.placement.left;
|
||||
let y = glyph.inner.1 - image.placement.top;
|
||||
let mut cache = glyph.font.cache.lock().unwrap();
|
||||
|
||||
match image.content {
|
||||
Content::Mask => {
|
||||
let mut i = 0;
|
||||
for off_y in 0..image.placement.height as i32 {
|
||||
for off_x in 0..image.placement.width as i32 {
|
||||
let color =
|
||||
(image.data[i] as u32) << 24 |
|
||||
base & 0xFFFFFF;
|
||||
f(x + off_x, y + off_y, color);
|
||||
i += 1;
|
||||
let (x_int, y_int, cache_key) = glyph.inner;
|
||||
|
||||
let image_opt = cache.entry(cache_key).or_insert_with(|| {
|
||||
let mut scale_context = glyph.font.scale_context.lock().unwrap();
|
||||
|
||||
// Build the scaler
|
||||
let mut scaler = scale_context
|
||||
.builder(glyph.font.swash)
|
||||
.size(cache_key.font_size as f32)
|
||||
.hint(true)
|
||||
.build();
|
||||
|
||||
// Compute the fractional offset-- you'll likely want to quantize this
|
||||
// in a real renderer
|
||||
let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
|
||||
|
||||
// Select our source order
|
||||
Render::new(&[
|
||||
// Color outline with the first palette
|
||||
Source::ColorOutline(0),
|
||||
// Color bitmap with best fit selection mode
|
||||
Source::ColorBitmap(StrikeWith::BestFit),
|
||||
// Standard scalable outline
|
||||
Source::Outline,
|
||||
])
|
||||
// Select a subpixel format
|
||||
.format(Format::Alpha)
|
||||
// Apply the fractional offset
|
||||
.offset(offset)
|
||||
// Render the image
|
||||
.render(&mut scaler, cache_key.glyph_id)
|
||||
});
|
||||
|
||||
if let Some(ref image) = image_opt {
|
||||
use swash::scale::image::Content;
|
||||
|
||||
let x = x_int + image.placement.left;
|
||||
let y = y_int - image.placement.top;
|
||||
|
||||
match image.content {
|
||||
Content::Mask => {
|
||||
let mut i = 0;
|
||||
for off_y in 0..image.placement.height as i32 {
|
||||
for off_x in 0..image.placement.width as i32 {
|
||||
let color =
|
||||
(image.data[i] as u32) << 24 |
|
||||
base & 0xFFFFFF;
|
||||
f(x + off_x, y + off_y, color);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Content::Color => {
|
||||
let mut i = 0;
|
||||
for off_y in 0..image.placement.height as i32 {
|
||||
for off_x in 0..image.placement.width as i32 {
|
||||
let color =
|
||||
(image.data[i + 3] as u32) << 24 |
|
||||
(image.data[i] as u32) << 16 |
|
||||
(image.data[i + 1] as u32) << 8 |
|
||||
(image.data[i + 2] as u32);
|
||||
f(x + off_x, y + off_y, color);
|
||||
i += 4;
|
||||
},
|
||||
Content::Color => {
|
||||
let mut i = 0;
|
||||
for off_y in 0..image.placement.height as i32 {
|
||||
for off_x in 0..image.placement.width as i32 {
|
||||
let color =
|
||||
(image.data[i + 3] as u32) << 24 |
|
||||
(image.data[i] as u32) << 16 |
|
||||
(image.data[i + 1] as u32) << 8 |
|
||||
(image.data[i + 2] as u32);
|
||||
f(x + off_x, y + off_y, color);
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
},
|
||||
Content::SubpixelMask => {
|
||||
println!("TODO: SubpixelMask");
|
||||
}
|
||||
},
|
||||
Content::SubpixelMask => {
|
||||
println!("TODO: SubpixelMask");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,10 +68,7 @@ impl<'a> FontMatches<'a> {
|
|||
y_advance,
|
||||
x_offset,
|
||||
y_offset,
|
||||
#[cfg(feature = "ab_glyph")]
|
||||
font: &self.fonts[font_i].ab_glyph,
|
||||
#[cfg(feature = "swash")]
|
||||
font: &self.fonts[font_i].swash,
|
||||
font: &self.fonts[font_i],
|
||||
inner,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
pub use self::cache::*;
|
||||
mod cache;
|
||||
|
||||
pub use self::layout::*;
|
||||
mod layout;
|
||||
|
||||
|
|
@ -10,6 +18,12 @@ mod shape;
|
|||
pub use self::system::*;
|
||||
mod system;
|
||||
|
||||
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct FontCacheKey {
|
||||
glyph_id: u16,
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct FontLineIndex(usize);
|
||||
|
||||
|
|
@ -32,6 +46,9 @@ pub struct Font<'a> {
|
|||
pub rusttype: rusttype::Font<'a>,
|
||||
#[cfg(feature = "swash")]
|
||||
pub swash: swash::FontRef<'a>,
|
||||
#[cfg(feature = "swash")]
|
||||
pub scale_context: Mutex<swash::scale::ScaleContext>,
|
||||
pub cache: Mutex<HashMap<CacheKey, CacheItem>>,
|
||||
}
|
||||
|
||||
impl<'a> Font<'a> {
|
||||
|
|
@ -45,6 +62,9 @@ impl<'a> Font<'a> {
|
|||
rusttype: rusttype::Font::try_from_bytes_and_index(data, index)?,
|
||||
#[cfg(feature = "swash")]
|
||||
swash: swash::FontRef::from_index(data, index as usize)?,
|
||||
#[cfg(feature = "swash")]
|
||||
scale_context: Mutex::new(swash::scale::ScaleContext::new()),
|
||||
cache: Mutex::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
#[cfg(feature = "ab_glyph")]
|
||||
use ab_glyph::Font;
|
||||
use core::marker::PhantomData;
|
||||
use ab_glyph::Font as _;
|
||||
|
||||
use super::{FontLayoutGlyph, FontLayoutLine, FontLineIndex};
|
||||
use super::{CacheKey, Font, FontLayoutGlyph, FontLayoutLine, FontLineIndex};
|
||||
|
||||
pub struct FontShapeGlyph<'a> {
|
||||
pub start: usize,
|
||||
|
|
@ -11,26 +10,23 @@ pub struct FontShapeGlyph<'a> {
|
|||
pub y_advance: f32,
|
||||
pub x_offset: f32,
|
||||
pub y_offset: f32,
|
||||
#[cfg(feature = "ab_glyph")]
|
||||
pub font: &'a ab_glyph::FontRef<'a>,
|
||||
pub font: &'a Font<'a>,
|
||||
#[cfg(feature = "ab_glyph")]
|
||||
pub inner: ab_glyph::GlyphId,
|
||||
#[cfg(feature = "rusttype")]
|
||||
pub inner: rusttype::Glyph<'a>,
|
||||
#[cfg(feature = "swash")]
|
||||
pub font: &'a swash::FontRef<'a>,
|
||||
#[cfg(feature = "swash")]
|
||||
pub inner: swash::GlyphId,
|
||||
}
|
||||
|
||||
impl<'a> FontShapeGlyph<'a> {
|
||||
fn layout(&self, font_size: i32, x: f32, y: f32) -> FontLayoutGlyph<'a, ()> {
|
||||
fn layout(&self, font_size: i32, x: f32, y: f32) -> FontLayoutGlyph<'a> {
|
||||
let x_offset = font_size as f32 * self.x_offset;
|
||||
let y_offset = font_size as f32 * self.y_offset;
|
||||
let x_advance = font_size as f32 * self.x_advance;
|
||||
|
||||
#[cfg(feature = "ab_glyph")]
|
||||
let inner = self.font.outline_glyph(
|
||||
let inner = self.font.ab_glyph.outline_glyph(
|
||||
self.inner.with_scale_and_position(
|
||||
font_size as f32,
|
||||
ab_glyph::point(
|
||||
|
|
@ -49,55 +45,19 @@ impl<'a> FontShapeGlyph<'a> {
|
|||
));
|
||||
|
||||
#[cfg(feature = "swash")]
|
||||
let inner = {
|
||||
use swash::scale::{Render, ScaleContext, Source, StrikeWith};
|
||||
use swash::zeno::{Format, Vector};
|
||||
|
||||
//TODO: store somewhere else
|
||||
static mut CONTEXT: Option<ScaleContext> = None;
|
||||
|
||||
unsafe {
|
||||
if CONTEXT.is_none() {
|
||||
CONTEXT = Some(ScaleContext::new());
|
||||
}
|
||||
}
|
||||
|
||||
// Build the scaler
|
||||
let mut scaler = unsafe { CONTEXT.as_mut().unwrap() }
|
||||
.builder(*self.font)
|
||||
.size(font_size as f32)
|
||||
.hint(true)
|
||||
.build();
|
||||
|
||||
// Compute the fractional offset-- you'll likely want to quantize this
|
||||
// in a real renderer
|
||||
let offset = Vector::new((x + x_offset).fract(), (y - y_offset).fract());
|
||||
|
||||
// Select our source order
|
||||
let image_opt = Render::new(&[
|
||||
// Color outline with the first palette
|
||||
Source::ColorOutline(0),
|
||||
// Color bitmap with best fit selection mode
|
||||
Source::ColorBitmap(StrikeWith::BestFit),
|
||||
// Standard scalable outline
|
||||
Source::Outline,
|
||||
])
|
||||
// Select a subpixel format
|
||||
.format(Format::Alpha)
|
||||
// Apply the fractional offset
|
||||
.offset(offset)
|
||||
// Render the image
|
||||
.render(&mut scaler, self.inner);
|
||||
((x + x_offset).trunc() as i32, (y - y_offset).trunc() as i32, image_opt)
|
||||
};
|
||||
let inner = CacheKey::new(
|
||||
(x + x_offset, y - y_offset),
|
||||
font_size,
|
||||
self.inner
|
||||
);
|
||||
|
||||
FontLayoutGlyph {
|
||||
start: self.start,
|
||||
end: self.end,
|
||||
x: x,
|
||||
w: x_advance,
|
||||
font: self.font,
|
||||
inner,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue