Implement basic glyph cache in iced_tiny_skia

This commit is contained in:
Héctor Ramón Jiménez 2023-02-27 16:28:19 +01:00
parent 3105ad2e00
commit c1ff803b8f
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
2 changed files with 120 additions and 65 deletions

View file

@ -27,7 +27,7 @@ path = "../graphics"
[dependencies.cosmic-text]
features = ["std", "swash"]
git = "https://github.com/hecrj/cosmic-text"
rev = "dc83efbf00a2efb4118403538e8a47bfd69c3e5e"
rev = "81080c1b9498933b43c1889601a7ea6a3d16e161"
[dependencies.twox-hash]
version = "1.6"

View file

@ -13,6 +13,7 @@ use std::sync::Arc;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
system: Option<System>,
glyph_cache: GlyphCache,
}
#[ouroboros::self_referencing]
@ -45,6 +46,7 @@ impl Pipeline {
}
.build(),
),
glyph_cache: GlyphCache::new(),
}
}
@ -116,76 +118,20 @@ impl Pipeline {
for run in buffer.layout_runs() {
for glyph in run.glyphs {
// TODO: Outline support
if let Some(image) = swash.get_image(glyph.cache_key) {
let glyph_size = image.placement.width as usize
* image.placement.height as usize;
if glyph_size == 0 {
continue;
}
// TODO: Cache glyph rasterization
let mut buffer = vec![0u32; glyph_size];
match image.content {
cosmic_text::SwashContent::Mask => {
let mut i = 0;
// TODO: Blend alpha
let [r, g, b, _a] = color.into_rgba8();
for _y in 0..image.placement.height {
for _x in 0..image.placement.width {
buffer[i] =
tiny_skia::ColorU8::from_rgba(
b,
g,
r,
image.data[i],
)
.premultiply()
.get();
i += 1;
}
}
}
cosmic_text::SwashContent::Color => {
let mut i = 0;
for _y in 0..image.placement.height {
for _x in 0..image.placement.width {
// TODO: Blend alpha
buffer[i >> 2] =
tiny_skia::ColorU8::from_rgba(
image.data[i + 2],
image.data[i + 1],
image.data[i],
image.data[i + 3],
)
.premultiply()
.get();
i += 4;
}
}
}
cosmic_text::SwashContent::SubpixelMask => {
// TODO
}
}
if let Some((buffer, placement)) = self
.glyph_cache
.allocate(glyph.cache_key, color, &mut swash)
{
let pixmap = tiny_skia::PixmapRef::from_bytes(
bytemuck::cast_slice(&buffer),
image.placement.width,
image.placement.height,
placement.width,
placement.height,
)
.expect("Create glyph pixel map");
pixels.draw_pixmap(
x as i32 + glyph.x_int + image.placement.left,
y as i32 - glyph.y_int - image.placement.top
x as i32 + glyph.x_int + placement.left,
y as i32 - glyph.y_int - placement.top
+ run.line_y as i32,
pixmap,
&tiny_skia::PixmapPaint::default(),
@ -203,6 +149,8 @@ impl Pipeline {
.as_mut()
.unwrap()
.with_render_cache_mut(|cache| cache.trim());
self.glyph_cache.trim();
}
pub fn measure(
@ -283,6 +231,113 @@ fn to_family(font: Font) -> cosmic_text::Family<'static> {
}
}
#[derive(Debug, Clone, Default)]
struct GlyphCache {
entries: FxHashMap<
(cosmic_text::CacheKey, [u8; 3]),
(Vec<u32>, cosmic_text::Placement),
>,
recently_used: FxHashSet<(cosmic_text::CacheKey, [u8; 3])>,
trim_count: usize,
}
impl GlyphCache {
fn new() -> Self {
GlyphCache::default()
}
fn allocate(
&mut self,
cache_key: cosmic_text::CacheKey,
color: Color,
swash: &mut cosmic_text::SwashCache<'_>,
) -> Option<(&[u8], cosmic_text::Placement)> {
let [r, g, b, _a] = color.into_rgba8();
let key = (cache_key, [r, g, b]);
if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) {
// TODO: Outline support
let image = swash.get_image(cache_key).as_ref()?;
let glyph_size = image.placement.width as usize
* image.placement.height as usize;
if glyph_size == 0 {
return None;
}
// TODO: Cache glyph rasterization
let mut buffer = vec![0u32; glyph_size];
match image.content {
cosmic_text::SwashContent::Mask => {
let mut i = 0;
// TODO: Blend alpha
for _y in 0..image.placement.height {
for _x in 0..image.placement.width {
buffer[i] = tiny_skia::ColorU8::from_rgba(
b,
g,
r,
image.data[i],
)
.premultiply()
.get();
i += 1;
}
}
}
cosmic_text::SwashContent::Color => {
let mut i = 0;
for _y in 0..image.placement.height {
for _x in 0..image.placement.width {
// TODO: Blend alpha
buffer[i >> 2] = tiny_skia::ColorU8::from_rgba(
image.data[i + 2],
image.data[i + 1],
image.data[i],
image.data[i + 3],
)
.premultiply()
.get();
i += 4;
}
}
}
cosmic_text::SwashContent::SubpixelMask => {
// TODO
}
}
entry.insert((buffer, image.placement));
}
self.recently_used.insert(key);
self.entries.get(&key).map(|(buffer, placement)| {
(bytemuck::cast_slice(buffer.as_slice()), *placement)
})
}
pub fn trim(&mut self) {
if self.trim_count > 300 {
self.entries
.retain(|key, _| self.recently_used.contains(key));
self.recently_used.clear();
self.trim_count = 0;
} else {
self.trim_count += 1;
}
}
}
struct Cache<'a> {
entries: FxHashMap<KeyHash, cosmic_text::Buffer<'a>>,
recently_used: FxHashSet<KeyHash>,