]> git.nega.tv - josh/narcissus/commitdiff
Improve font cache
authorJoshua Simmons <josh@nega.tv>
Tue, 28 Feb 2023 20:24:05 +0000 (21:24 +0100)
committerJoshua Simmons <josh@nega.tv>
Tue, 28 Feb 2023 20:24:05 +0000 (21:24 +0100)
Always write the information for touched glyphs every frame, to try and
avoid accidently depending on those being stable across atlas updates.

When the font cache is out of space, perform an emergency repack,
recreating the atlas with only the glyphs touched in the current frame.

This should address both changing requirements on the font cache over
time from the application, as well as inefficient packing from
incrementally building the atlas.

bins/narcissus/src/main.rs
bins/narcissus/src/pipelines/text.rs
bins/narcissus/src/shaders/text.vert.glsl
bins/narcissus/src/shaders/text.vert.spv
libs/narcissus-font/src/cache.rs
libs/narcissus-font/src/font.rs
libs/narcissus-font/src/lib.rs

index fb282d739d18f22ce54d89f6a7391ad7c760e689..0f046e8ecab4eb59dba0d02dc203cbdadb14014c 100644 (file)
@@ -8,7 +8,7 @@ use helpers::{create_buffer_with_data, create_image_with_data, load_image, load_
 use mapped_buffer::MappedBuffer;
 use narcissus_app::{create_app, Event, Key, PressedState, WindowDesc};
 use narcissus_core::{default, rand::Pcg64};
-use narcissus_font::{CachedGlyph, FontCollection, GlyphCache};
+use narcissus_font::{FontCollection, GlyphCache, TouchedGlyph, TouchedGlyphInfo};
 use narcissus_gpu::{
     create_device, Access, BufferImageCopy, BufferUsageFlags, ClearValue, Extent2d, Extent3d,
     ImageAspectFlags, ImageBarrier, ImageDesc, ImageDimension, ImageFormat, ImageLayout,
@@ -41,7 +41,7 @@ pub unsafe trait Blittable: Sized {}
 unsafe impl Blittable for u8 {}
 unsafe impl Blittable for u16 {}
 unsafe impl Blittable for Affine3 {}
-unsafe impl Blittable for CachedGlyph {}
+unsafe impl Blittable for TouchedGlyph {}
 
 pub fn main() {
     let app = create_app();
@@ -110,7 +110,7 @@ pub fn main() {
     let mut glyph_buffer = MappedBuffer::new(
         device.as_ref(),
         BufferUsageFlags::STORAGE,
-        std::mem::size_of::<CachedGlyph>() * MAX_GLYPHS,
+        std::mem::size_of::<TouchedGlyph>() * MAX_GLYPHS,
     );
 
     let glyph_atlas = device.create_image(&ImageDesc {
@@ -163,7 +163,7 @@ pub fn main() {
         }
     }
 
-    let mut num_chars = 0;
+    let mut space_count = 0;
 
     let start_time = Instant::now();
     'main: loop {
@@ -181,9 +181,8 @@ pub fn main() {
                     if key == Key::Escape {
                         break 'main;
                     }
-                    if key == Key::Space && pressed == PressedState::Released {
-                        num_chars += 1;
-                        println!("{num_chars}");
+                    if key == Key::Space && pressed == PressedState::Pressed {
+                        space_count += 1;
                     }
                 }
                 Quit => {
@@ -271,7 +270,6 @@ pub fn main() {
         let line1 = "加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して 加盟国は、国際連合と協力して";
 
         let mut glyph_instances = Vec::new();
-        let mut glyph_indices = Vec::new();
 
         let mut x;
         let mut y = 0.0;
@@ -294,33 +292,29 @@ pub fn main() {
             y += (font.ascent() - font.descent() + font.line_gap()) * scale;
             y = y.trunc();
 
-            glyph_indices.clear();
-            glyph_indices.extend(text.chars().take(num_chars).map(|c| {
-                font.glyph_index(c)
-                    .unwrap_or_else(|| font.glyph_index('□').unwrap())
-            }));
-
             let mut prev_glyph_index = None;
-            for glyph_index in glyph_indices.iter().copied() {
+            for c in text.chars().skip(space_count).take(1) {
+                let TouchedGlyphInfo {
+                    touched_glyph_index,
+                    glyph_index,
+                    advance_width,
+                } = glyph_cache.touch_glyph(font_family, c, font_size_px);
+
                 if let Some(prev_glyph_index) = prev_glyph_index.replace(glyph_index) {
                     x += font.kerning_advance(prev_glyph_index, glyph_index) * scale;
                 }
 
-                let cached_glyph_index =
-                    glyph_cache.cache_glyph(font_family, glyph_index, font_size_px);
-
                 const COLOR_SERIES: [u32; 4] = [0xfffac228, 0xfff57d15, 0xffd44842, 0xff9f2a63];
                 let color = COLOR_SERIES[rng.next_bound_u64(4) as usize];
 
                 glyph_instances.push(GlyphInstance {
-                    cached_glyph_index,
                     x,
                     y,
+                    touched_glyph_index,
                     color,
                 });
 
-                let h_metrics = font.horizontal_metrics(glyph_index);
-                x += h_metrics.advance_width * scale;
+                x += advance_width * scale;
             }
         }
 
@@ -336,9 +330,10 @@ pub fn main() {
         glyph_instance_buffer.write_slice(&glyph_instances);
 
         // If the atlas has been updated, we need to upload it to the GPU
-        if let Some((cached_glyphs, texture)) = glyph_cache.update_atlas() {
-            glyph_buffer.write_slice(cached_glyphs);
+        let (touched_glyphs, texture) = glyph_cache.update_atlas();
+        glyph_buffer.write_slice(touched_glyphs);
 
+        if let Some(texture) = texture {
             // upload atlas
             {
                 let width = atlas_width;
@@ -391,9 +386,7 @@ pub fn main() {
                 );
 
                 device.destroy_buffer(&frame, buffer);
-
-                image
-            };
+            }
         }
 
         device.cmd_begin_rendering(
index 48e5f71b559d70971cc8c2bdbfc1564fb5ef8abd..df23b0983b2a6ee2860dfc2347cd093c615a8f7c 100644 (file)
@@ -1,5 +1,5 @@
 use narcissus_core::{cstr, default, include_bytes_align};
-use narcissus_font::CachedGlyphIndex;
+use narcissus_font::TouchedGlyphIndex;
 use narcissus_gpu::{
     Bind, BindGroupLayout, BindGroupLayoutDesc, BindGroupLayoutEntryDesc, BindingType, BlendMode,
     Buffer, CmdBuffer, CompareOp, CullingMode, Device, Frame, FrontFace, GraphicsPipelineDesc,
@@ -25,9 +25,9 @@ pub struct TextUniforms {
 #[allow(unused)]
 #[repr(C)]
 pub struct GlyphInstance {
-    pub cached_glyph_index: CachedGlyphIndex,
     pub x: f32,
     pub y: f32,
+    pub touched_glyph_index: TouchedGlyphIndex,
     pub color: u32,
 }
 
index 146096e629520e754dfe62c3291be0a566e2c2fd..26b1e08b9e6c1f22c3fcaa892f6dc02fc80be158 100644 (file)
@@ -13,9 +13,9 @@ struct CachedGlyph {
 };
 
 struct GlyphInstance {
-    uint index;
     float x;
     float y;
+    uint index;
     uint color;
 };
 
index ed9eb1ce047c2bebecc5218d1c9b6250a55cebbd..c4b15a6324e1b860c7e9c8dd73debeaa2b6b0bba 100644 (file)
Binary files a/bins/narcissus/src/shaders/text.vert.spv and b/bins/narcissus/src/shaders/text.vert.spv differ
index ce9334346e65c4202b28fd6a3417826cb36ca197..e42230fa848a72c1d293e55a256bbdc759716eea 100644 (file)
@@ -1,4 +1,10 @@
-use crate::{font::GlyphBitmapBox, FontCollection, GlyphIndex, Oversample, Packer};
+use std::collections::hash_map::Entry;
+
+use crate::{
+    font::{GlyphBitmapBox, HorizontalMetrics},
+    FontCollection, GlyphIndex, Oversample, Packer,
+};
+use narcissus_core::default;
 use rustc_hash::FxHashMap;
 use stb_truetype_sys::rectpack::Rect;
 
@@ -7,12 +13,12 @@ pub use narcissus_core::FiniteF32;
 /// An index into the CachedGlyph slice.
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
 #[repr(transparent)]
-pub struct CachedGlyphIndex(u32);
+pub struct TouchedGlyphIndex(u32);
 
 /// Holds data required to draw a glyph from the glyph atlas.
 #[derive(Clone, Copy, Default)]
 #[repr(C)]
-pub struct CachedGlyph {
+pub struct TouchedGlyph {
     // Bitmap coordinates in texture atlas.
     pub x0: i32,
     pub x1: i32,
@@ -26,19 +32,30 @@ pub struct CachedGlyph {
     pub offset_y1: f32,
 }
 
-#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
 struct GlyphKey<Family> {
     family: Family,
-    glyph_index: GlyphIndex,
+    c: char,
     size_px: FiniteF32,
 }
 
-#[derive(PartialEq, Eq, PartialOrd, Ord)]
-struct Glyph<F> {
-    family: F,
+#[derive(Clone, Copy)]
+pub struct TouchedGlyphInfo {
+    pub touched_glyph_index: TouchedGlyphIndex,
+    pub glyph_index: GlyphIndex,
+    pub advance_width: f32,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+struct CachedGlyphIndex(u32);
+
+struct CachedGlyph<F> {
+    glyph_key: GlyphKey<F>,
     glyph_index: GlyphIndex,
-    size_px: FiniteF32,
-    cached_glyph_index: CachedGlyphIndex,
+    offset_x0: f32,
+    offset_y0: f32,
+    offset_x1: f32,
+    offset_y1: f32,
 }
 
 pub struct GlyphCache<'a, F>
@@ -48,20 +65,19 @@ where
     fonts: &'a F,
 
     padding: usize,
+    width: usize,
+    height: usize,
+    texture: Box<[u8]>,
 
-    next_cached_glyph_index: u32,
-    cached_glyph_lookup: FxHashMap<GlyphKey<F::Family>, CachedGlyphIndex>,
-
-    glyphs: Vec<Glyph<F::Family>>,
+    next_touched_glyph_index: u32,
+    touched_glyph_lookup: FxHashMap<GlyphKey<F::Family>, TouchedGlyphInfo>,
+    touched_glyphs: Vec<TouchedGlyph>,
 
-    packer: Packer,
+    cached_glyph_indices_sorted: Vec<usize>,
+    cached_glyphs: Vec<CachedGlyph<F::Family>>,
     rects: Vec<Rect>,
 
-    cached_glyphs: Vec<CachedGlyph>,
-
-    width: usize,
-    height: usize,
-    texture: Box<[u8]>,
+    packer: Packer,
 }
 
 impl<'a, F> GlyphCache<'a, F>
@@ -73,20 +89,19 @@ where
             fonts,
 
             padding,
+            width,
+            height,
+            texture: vec![0; width * height].into_boxed_slice(),
 
-            next_cached_glyph_index: 0,
-            cached_glyph_lookup: Default::default(),
+            next_touched_glyph_index: 0,
+            touched_glyph_lookup: default(),
+            touched_glyphs: default(),
 
-            glyphs: Vec::new(),
+            cached_glyph_indices_sorted: default(),
+            cached_glyphs: default(),
+            rects: default(),
 
             packer: Packer::new(width - padding, height - padding),
-            rects: Vec::new(),
-
-            cached_glyphs: Vec::new(),
-
-            width,
-            height,
-            texture: vec![0; width * height].into_boxed_slice(),
         }
     }
 
@@ -106,167 +121,231 @@ where
         }
     }
 
-    pub fn cache_glyph(
-        &mut self,
-        family: F::Family,
-        glyph_index: GlyphIndex,
-        size_px: f32,
-    ) -> CachedGlyphIndex {
+    pub fn touch_glyph(&mut self, family: F::Family, c: char, size_px: f32) -> TouchedGlyphInfo {
         let key = GlyphKey {
             family,
-            glyph_index,
+            c,
             size_px: FiniteF32::new(size_px).unwrap(),
         };
 
-        *self.cached_glyph_lookup.entry(key).or_insert_with(|| {
-            let cached_glyph_index = CachedGlyphIndex(self.next_cached_glyph_index);
-            self.next_cached_glyph_index += 1;
-            cached_glyph_index
-        })
-    }
-
-    pub fn update_atlas(&mut self) -> Option<(&[CachedGlyph], &[u8])> {
-        let glyphs_len = self.cached_glyphs.len();
-
-        // If we have the same number of glyphs as we have cached, then there's nothing to do.
-        if glyphs_len == self.cached_glyph_lookup.len() {
-            return None;
+        match self.touched_glyph_lookup.entry(key) {
+            Entry::Occupied(entry) => *entry.get(),
+            Entry::Vacant(entry) => {
+                let touched_glyph_index = TouchedGlyphIndex(self.next_touched_glyph_index);
+                self.next_touched_glyph_index += 1;
+
+                let font = self.fonts.font(family);
+                let glyph_index = font
+                    .glyph_index(c)
+                    .unwrap_or_else(|| font.glyph_index('□').unwrap());
+
+                let HorizontalMetrics {
+                    advance_width,
+                    left_side_bearing: _,
+                } = font.horizontal_metrics(glyph_index);
+
+                *entry.insert(TouchedGlyphInfo {
+                    touched_glyph_index,
+                    glyph_index,
+                    advance_width,
+                })
+            }
         }
+    }
 
-        // Extend the glyphs list with the new glyphs.
-        self.glyphs
-            .extend(self.cached_glyph_lookup.iter().filter_map(
-                |(glyph_key, &cached_glyph_index)| {
-                    if cached_glyph_index.0 < glyphs_len as u32 {
-                        None
-                    } else {
-                        Some(Glyph {
-                            family: glyph_key.family,
-                            glyph_index: glyph_key.glyph_index,
-                            size_px: glyph_key.size_px,
-                            cached_glyph_index,
-                        })
-                    }
-                },
-            ));
-
-        // The new glyphs might not be in the right order, because HashMap doesn't gaurantee
-        // iteration order. So we need to sort them here by their cached_glyph_index.
-        self.glyphs[glyphs_len..].sort_by_key(
-            |Glyph {
-                 family: _,
-                 glyph_index: _,
-                 size_px: _,
-                 cached_glyph_index,
-             }| *cached_glyph_index,
+    pub fn update_atlas(&mut self) -> (&[TouchedGlyph], Option<&[u8]>) {
+        debug_assert_eq!(
+            self.touched_glyph_lookup.len(),
+            self.next_touched_glyph_index as usize
         );
+        self.touched_glyphs
+            .resize(self.touched_glyph_lookup.len(), TouchedGlyph::default());
+        self.next_touched_glyph_index = 0;
+
+        let mut is_emergency_repack = false;
+
+        'emergency_repack: loop {
+            let cached_glyphs_len = self.cached_glyphs.len();
+
+            // For each touched glyph, try and find it in our cached glyph list.
+            for (&glyph_key, touched_glyph_info) in self.touched_glyph_lookup.iter() {
+                match self
+                    .cached_glyph_indices_sorted
+                    .binary_search_by_key(&glyph_key, |&cached_glyph_index| {
+                        self.cached_glyphs[cached_glyph_index].glyph_key
+                    }) {
+                    // We've already cached this glyph. So we just need to write the `touched_glyphs`
+                    // information.
+                    Ok(index) => {
+                        let cached_glyph_index = self.cached_glyph_indices_sorted[index];
+
+                        let touched_glyph = &mut self.touched_glyphs
+                            [touched_glyph_info.touched_glyph_index.0 as usize];
+
+                        let rect = &self.rects[cached_glyph_index];
+                        touched_glyph.x0 = rect.x;
+                        touched_glyph.x1 = rect.x + rect.w;
+                        touched_glyph.y0 = rect.y;
+                        touched_glyph.y1 = rect.y + rect.h;
+
+                        let cached_glyph = &self.cached_glyphs[cached_glyph_index];
+                        touched_glyph.offset_x0 = cached_glyph.offset_x0;
+                        touched_glyph.offset_y0 = cached_glyph.offset_y0;
+                        touched_glyph.offset_x1 = cached_glyph.offset_x1;
+                        touched_glyph.offset_y1 = cached_glyph.offset_y1;
+                    }
+                    // This glyph isn't cached, so we must prepare to pack and render it.
+                    Err(_) => {
+                        let font = self.fonts.font(glyph_key.family);
+                        let size_px = glyph_key.size_px.get();
+                        let scale = font.scale_for_size_px(size_px);
+                        let oversample = Self::oversample_for_size(size_px);
+
+                        let bitmap_box = font.glyph_bitmap_box(
+                            touched_glyph_info.glyph_index,
+                            scale * oversample.as_f32(),
+                            scale * oversample.as_f32(),
+                            0.0,
+                            0.0,
+                        );
+
+                        let w = bitmap_box.x1 - bitmap_box.x0
+                            + self.padding as i32
+                            + oversample.as_i32()
+                            - 1;
+                        let h = bitmap_box.y1 - bitmap_box.y0
+                            + self.padding as i32
+                            + oversample.as_i32()
+                            - 1;
+
+                        self.cached_glyphs.push(CachedGlyph {
+                            glyph_key,
+                            glyph_index: touched_glyph_info.glyph_index,
+                            offset_x0: 0.0,
+                            offset_y0: 0.0,
+                            offset_x1: 0.0,
+                            offset_y1: 0.0,
+                        });
+
+                        self.rects.push(Rect {
+                            id: touched_glyph_info.touched_glyph_index.0 as i32,
+                            w,
+                            h,
+                            x: 0,
+                            y: 0,
+                            was_packed: 0,
+                        });
+                    }
+                }
+            }
+
+            // If we haven't added anything new, we're done here.
+            if self.cached_glyphs.len() == cached_glyphs_len {
+                self.touched_glyph_lookup.clear();
+                return (&self.touched_glyphs, None);
+            }
+
+            // Pack any new glyphs we might have.
+            //
+            // If packing fails, wipe the cache and try again with a full repack and render of the
+            // touched_glyphs this iteration.
+            if !self.packer.pack(&mut self.rects[cached_glyphs_len..]) {
+                assert!(
+                    !is_emergency_repack,
+                    "emergency repack failed, texture atlas exhausted"
+                );
 
-        debug_assert!(self
-            .glyphs
-            .iter()
-            .enumerate()
-            .all(|(i, cached_glyph)| cached_glyph.cached_glyph_index.0 as usize == i));
-
-        let padding = self.padding as i32;
-
-        // Add new rects for the new cached glyphs.
-        self.rects
-            .extend(self.glyphs[glyphs_len..].iter().map(|glyph| {
-                let font = self.fonts.font(glyph.family);
-                let size_px = glyph.size_px.get();
+                self.cached_glyph_indices_sorted.clear();
+                self.cached_glyphs.clear();
+                self.rects.clear();
+                self.packer.clear();
+                self.texture.fill(0);
+
+                is_emergency_repack = true;
+                continue 'emergency_repack;
+            }
+
+            // Render any new glyphs we might have.
+            for (cached_glyph, rect) in self.cached_glyphs[cached_glyphs_len..]
+                .iter_mut()
+                .zip(self.rects[cached_glyphs_len..].iter_mut())
+            {
+                let font = self.fonts.font(cached_glyph.glyph_key.family);
+
+                // Pad on left and top.
+                let padding = self.padding as i32;
+                rect.x += padding;
+                rect.y += padding;
+                rect.w -= padding;
+                rect.h -= padding;
+
+                let size_px = cached_glyph.glyph_key.size_px.get();
                 let scale = font.scale_for_size_px(size_px);
                 let oversample = Self::oversample_for_size(size_px);
 
-                let bitmap_box = font.glyph_bitmap_box(
-                    glyph.glyph_index,
+                let scale_x = scale * oversample.as_f32();
+                let scale_y = scale * oversample.as_f32();
+
+                let (sub_x, sub_y) = font.render_glyph_bitmap(
+                    &mut self.texture,
+                    rect.x,
+                    rect.y,
+                    rect.w,
+                    rect.h,
+                    self.width as i32,
+                    scale_x,
+                    scale_y,
+                    0.0,
+                    0.0,
+                    oversample,
+                    oversample,
+                    cached_glyph.glyph_index,
+                );
+
+                let GlyphBitmapBox {
+                    x0,
+                    x1: _,
+                    y0,
+                    y1: _,
+                } = font.glyph_bitmap_box(
+                    cached_glyph.glyph_index,
                     scale * oversample.as_f32(),
                     scale * oversample.as_f32(),
                     0.0,
                     0.0,
                 );
 
-                let w = bitmap_box.x1 - bitmap_box.x0 + padding + oversample.as_i32() - 1;
-                let h = bitmap_box.y1 - bitmap_box.y0 + padding + oversample.as_i32() - 1;
-
-                Rect {
-                    id: glyph.cached_glyph_index.0 as i32,
-                    w,
-                    h,
-                    x: 0,
-                    y: 0,
-                    was_packed: 0,
-                }
-            }));
-
-        // TODO: Emergency re-pack when this fails, dropping glyphs unused this frame.
-        assert!(self.packer.pack(&mut self.rects[glyphs_len..]));
-
-        self.cached_glyphs
-            .resize(self.glyphs.len(), CachedGlyph::default());
-
-        // Render the new glyphs.
-        for (glyph, rect) in self.glyphs[glyphs_len..]
-            .iter()
-            .zip(self.rects[glyphs_len..].iter_mut())
-        {
-            let font = self.fonts.font(glyph.family);
-
-            // Pad on left and top.
-            let padding = self.padding as i32;
-            rect.x += padding;
-            rect.y += padding;
-            rect.w -= padding;
-            rect.h -= padding;
-
-            let size_px = glyph.size_px.get();
-            let scale = font.scale_for_size_px(size_px);
-            let oversample = Self::oversample_for_size(size_px);
-
-            let scale_x = scale * oversample.as_f32();
-            let scale_y = scale * oversample.as_f32();
-
-            let (sub_x, sub_y) = font.render_glyph_bitmap(
-                &mut self.texture,
-                rect.x,
-                rect.y,
-                rect.w,
-                rect.h,
-                self.width as i32,
-                scale_x,
-                scale_y,
-                0.0,
-                0.0,
-                oversample,
-                oversample,
-                glyph.glyph_index,
-            );
-
-            let cached_glyph = &mut self.cached_glyphs[rect.id as usize];
-
-            cached_glyph.x0 = rect.x;
-            cached_glyph.x1 = rect.x + rect.w;
-            cached_glyph.y0 = rect.y;
-            cached_glyph.y1 = rect.y + rect.h;
-
-            let GlyphBitmapBox {
-                x0,
-                x1: _,
-                y0,
-                y1: _,
-            } = font.glyph_bitmap_box(
-                glyph.glyph_index,
-                scale * oversample.as_f32(),
-                scale * oversample.as_f32(),
-                0.0,
-                0.0,
-            );
-
-            cached_glyph.offset_x0 = x0 as f32 / oversample.as_f32() + sub_x;
-            cached_glyph.offset_y0 = y0 as f32 / oversample.as_f32() + sub_y;
-            cached_glyph.offset_x1 = (x0 + rect.w) as f32 / oversample.as_f32() + sub_x;
-            cached_glyph.offset_y1 = (y0 + rect.h) as f32 / oversample.as_f32() + sub_y;
+                let offset_x0 = x0 as f32 / oversample.as_f32() + sub_x;
+                let offset_y0 = y0 as f32 / oversample.as_f32() + sub_y;
+                let offset_x1 = (x0 + rect.w) as f32 / oversample.as_f32() + sub_x;
+                let offset_y1 = (y0 + rect.h) as f32 / oversample.as_f32() + sub_y;
+
+                cached_glyph.offset_x0 = offset_x0;
+                cached_glyph.offset_y0 = offset_y0;
+                cached_glyph.offset_x1 = offset_x1;
+                cached_glyph.offset_y1 = offset_y1;
+
+                let touched_glyph = &mut self.touched_glyphs[rect.id as usize];
+
+                touched_glyph.x0 = rect.x;
+                touched_glyph.x1 = rect.x + rect.w;
+                touched_glyph.y0 = rect.y;
+                touched_glyph.y1 = rect.y + rect.h;
+
+                touched_glyph.offset_x0 = offset_x0;
+                touched_glyph.offset_y0 = offset_y0;
+                touched_glyph.offset_x1 = offset_x1;
+                touched_glyph.offset_y1 = offset_y1;
+            }
+
+            self.cached_glyph_indices_sorted.clear();
+            self.cached_glyph_indices_sorted
+                .extend(0..self.cached_glyphs.len());
+            self.cached_glyph_indices_sorted
+                .sort_unstable_by_key(|&index| self.cached_glyphs[index].glyph_key);
+
+            self.touched_glyph_lookup.clear();
+            return (&self.touched_glyphs, Some(&self.texture));
         }
-
-        Some((&self.cached_glyphs, &self.texture))
     }
 }
index 26aa0633cff0f479a9597c14991dca01f16844d5..9a326e011dba12eb2994c626f4bdadf9dc2f700d 100644 (file)
@@ -237,5 +237,5 @@ impl<'a> Font<'a> {
 
 pub trait FontCollection<'a> {
     type Family: Copy + Eq + Ord + std::hash::Hash;
-    fn font(&self, font_family: Self::Family) -> &Font<'a>;
+    fn font(&self, family: Self::Family) -> &Font<'a>;
 }
index e91386b3d5eb28ad09b2b64d9812d81fbd0704da..75eb07e61504c2feee8c27d4baa189ee73e15ab2 100644 (file)
@@ -2,6 +2,6 @@ mod cache;
 mod font;
 mod packer;
 
-pub use cache::{CachedGlyph, CachedGlyphIndex, GlyphCache};
+pub use cache::{GlyphCache, TouchedGlyph, TouchedGlyphIndex, TouchedGlyphInfo};
 pub use font::{Font, FontCollection, GlyphIndex, Oversample};
 pub use packer::{Packer, Rect};