From 897dea6d991d4639aa08503d2512626ead33e850 Mon Sep 17 00:00:00 2001
From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
Date: Thu, 9 Jan 2025 14:16:05 +0000
Subject: [PATCH 1/4] Use rayon when resolving glyphs

---
 Cargo.lock                        |  47 ++++++
 vello_encoding/Cargo.toml         |   4 +-
 vello_encoding/src/encoding.rs    |   2 +-
 vello_encoding/src/glyph_cache.rs | 243 ++++++++++++++++++++----------
 vello_encoding/src/path.rs        |   3 +-
 vello_encoding/src/resolve.rs     |  60 +++++---
 6 files changed, 255 insertions(+), 104 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 8c78eda1..9b4ce1af 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -535,6 +535,25 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "crossbeam-utils"
 version = "0.8.20"
@@ -595,6 +614,12 @@ version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
 
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
 [[package]]
 name = "env_filter"
 version = "0.1.2"
@@ -1866,6 +1891,26 @@ version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
 
+[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "read-fonts"
 version = "0.25.0"
@@ -2518,8 +2563,10 @@ dependencies = [
  "bytemuck",
  "guillotiere",
  "peniko",
+ "rayon",
  "skrifa",
  "smallvec",
+ "tracing",
 ]
 
 [[package]]
diff --git a/vello_encoding/Cargo.toml b/vello_encoding/Cargo.toml
index cc684ffe..5bb352a5 100644
--- a/vello_encoding/Cargo.toml
+++ b/vello_encoding/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "vello_encoding"
-version.workspace = true # We mimic Vello's version
+version.workspace = true                                                       # We mimic Vello's version
 description = "Vello types that represent the data that needs to be rendered."
 categories = ["rendering", "graphics"]
 keywords = ["2d", "vector-graphics"]
@@ -30,3 +30,5 @@ skrifa = { workspace = true }
 peniko = { workspace = true }
 guillotiere = { version = "0.6.2" }
 smallvec = { workspace = true }
+rayon = "1.10.0"
+tracing = { default-features = false, version = "0.1.41" }
diff --git a/vello_encoding/src/encoding.rs b/vello_encoding/src/encoding.rs
index d581acd4..d6b223d7 100644
--- a/vello_encoding/src/encoding.rs
+++ b/vello_encoding/src/encoding.rs
@@ -186,7 +186,7 @@ impl Encoding {
         self.encode_style(Style::from_stroke(stroke));
     }
 
-    fn encode_style(&mut self, style: Style) {
+    pub(crate) fn encode_style(&mut self, style: Style) {
         if self.flags & Self::FORCE_NEXT_STYLE != 0 || self.styles.last() != Some(&style) {
             self.path_tags.push(PathTag::STYLE);
             self.styles.push(style);
diff --git a/vello_encoding/src/glyph_cache.rs b/vello_encoding/src/glyph_cache.rs
index c0933506..974ec11f 100644
--- a/vello_encoding/src/glyph_cache.rs
+++ b/vello_encoding/src/glyph_cache.rs
@@ -7,19 +7,19 @@ use std::sync::Arc;
 use super::{Encoding, StreamOffsets};
 
 use peniko::{Font, Style};
+use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
 use skrifa::instance::{NormalizedCoord, Size};
 use skrifa::outline::{HintingInstance, HintingOptions, OutlineGlyphFormat};
 use skrifa::{GlyphId, MetadataProvider, OutlineGlyphCollection};
 
 #[derive(Default)]
 pub(crate) struct GlyphCache {
-    free_list: Vec<Arc<Encoding>>,
     map: GlyphMap,
     var_map: HashMap<VarKey, GlyphMap>,
     cached_count: usize,
-    hinting: HintCache,
     serial: u64,
     last_prune_serial: u64,
+    unresolved: Vec<(VarKey, GlyphKey, Font)>,
 }
 
 impl GlyphCache {
@@ -33,7 +33,6 @@ impl GlyphCache {
     ) -> Option<GlyphCacheSession<'a>> {
         let font_id = font.data.id();
         let font_index = font.index;
-        let font = skrifa::FontRef::from_index(font.data.as_ref(), font.index).ok()?;
         let map = if !coords.is_empty() {
             // This is still ugly in rust. Choices are:
             // 1. multiple lookups in the hashmap (implemented here)
@@ -48,20 +47,7 @@ impl GlyphCache {
         } else {
             &mut self.map
         };
-        let outlines = font.outline_glyphs();
         let size = Size::new(size);
-        let hinter = if hint {
-            let key = HintKey {
-                font_id,
-                font_index,
-                outlines: &outlines,
-                size,
-                coords,
-            };
-            self.hinting.get(&key)
-        } else {
-            None
-        };
         // TODO: we're ignoring dashing for now
         let style_bits = match style {
             Style::Fill(fill) => super::path::Style::from_fill(*fill),
@@ -69,32 +55,27 @@ impl GlyphCache {
         };
         let style_bits: [u32; 2] = bytemuck::cast(style_bits);
         Some(GlyphCacheSession {
-            free_list: &mut self.free_list,
             map,
             font_id,
             font_index,
             coords,
-            size,
             size_bits: size.ppem().unwrap().to_bits(),
-            style,
             style_bits,
-            outlines,
-            hinter,
+            hint,
+            font,
             serial: self.serial,
-            cached_count: &mut self.cached_count,
+            unresolved: &mut self.unresolved,
         })
     }
 
     pub(crate) fn maintain(&mut self) {
+        self.unresolved.clear();
         // Maximum number of resolve phases where we'll retain an unused glyph
         const MAX_ENTRY_AGE: u64 = 64;
         // Maximum number of resolve phases before we force a prune
         const PRUNE_FREQUENCY: u64 = 64;
         // Always prune if the cached count is greater than this value
         const CACHED_COUNT_THRESHOLD: usize = 256;
-        // Number of encoding buffers we'll keep on the free list
-        const MAX_FREE_LIST_SIZE: usize = 32;
-        let free_list = &mut self.free_list;
         let serial = self.serial;
         self.serial += 1;
         // Don't iterate over the whole cache every frame
@@ -106,9 +87,6 @@ impl GlyphCache {
         self.last_prune_serial = serial;
         self.map.retain(|_, entry| {
             if serial - entry.serial > MAX_ENTRY_AGE {
-                if free_list.len() < MAX_FREE_LIST_SIZE {
-                    free_list.push(entry.encoding.clone());
-                }
                 self.cached_count -= 1;
                 false
             } else {
@@ -118,9 +96,6 @@ impl GlyphCache {
         self.var_map.retain(|_, map| {
             map.retain(|_, entry| {
                 if serial - entry.serial > MAX_ENTRY_AGE {
-                    if free_list.len() < MAX_FREE_LIST_SIZE {
-                        free_list.push(entry.encoding.clone());
-                    }
                     self.cached_count -= 1;
                     false
                 } else {
@@ -130,81 +105,175 @@ impl GlyphCache {
             !map.is_empty()
         });
     }
+    pub(crate) fn resolve_in_parallel(&mut self) {
+        tracing::trace_span!("Resolving glyph outlines", count = self.unresolved.len());
+        let result = self
+            .unresolved
+            .par_iter()
+            .map_init(HintCache::default, |hint_cache, (coords, glyph, font)| {
+                tracing::trace_span!("Resolving single glyph");
+                (
+                    resolve_single_glyph(hint_cache, coords, glyph, font),
+                    coords,
+                    glyph,
+                )
+            })
+            .collect_vec_list();
+        for (result, coords, glyph) in result.into_iter().flatten() {
+            let map = if !coords.is_empty() {
+                self.var_map.get_mut(coords).unwrap()
+            } else {
+                &mut self.map
+            };
+            let (encoding, offsets) = result.unwrap_or_default();
+            map.get_mut(glyph).unwrap().status = GlyphEntryStatus::Resolved {
+                encoding,
+                stream_sizes: offsets,
+            };
+        }
+    }
+    pub(crate) fn get_resolved(&mut self, idx: usize) -> (Arc<Encoding>, StreamOffsets) {
+        let (coords, glyph, _) = &mut self.unresolved[idx];
+        let map = if !coords.is_empty() {
+            self.var_map.get_mut(coords).unwrap()
+        } else {
+            &mut self.map
+        };
+        match &mut map.get_mut(glyph).unwrap().status {
+            GlyphEntryStatus::Resolved {
+                encoding,
+                stream_sizes,
+            } => (encoding.clone(), *stream_sizes),
+            GlyphEntryStatus::Unresolved { .. } => unreachable!(),
+        }
+    }
+}
+
+fn resolve_single_glyph(
+    hint_cache: &mut HintCache,
+    coords: &smallvec::SmallVec<[skrifa::raw::types::F2Dot14; 8]>,
+    glyph: &GlyphKey,
+    font: &Font,
+) -> Option<(Arc<Encoding>, StreamOffsets)> {
+    let font_id = font.data.id();
+    let font_index = font.index;
+    let font = skrifa::FontRef::from_index(font.data.as_ref(), font.index).ok()?;
+    let outlines = font.outline_glyphs();
+    let size = Size::new(f32::from_bits(glyph.font_size_bits));
+    let outline = outlines.get(GlyphId::new(glyph.glyph_id))?;
+    let mut encoding = Encoding::default();
+    encoding.reset();
+    let style: crate::Style = bytemuck::cast(glyph.style_bits);
+    encoding.encode_style(style);
+    let is_fill = style.is_fill();
+    use skrifa::outline::DrawSettings;
+    let mut path = encoding.encode_path(is_fill);
+    let hinter = if glyph.hint {
+        let key = HintKey {
+            font_id,
+            font_index,
+            outlines: &outlines,
+            size,
+            coords,
+        };
+        hint_cache.get(&key)
+    } else {
+        None
+    };
+    let draw_settings = if let Some(hinter) = hinter {
+        DrawSettings::hinted(hinter, false)
+    } else {
+        DrawSettings::unhinted(size, &**coords)
+    };
+    outline.draw(draw_settings, &mut path).ok()?;
+    if path.finish(false) == 0 {
+        encoding.reset();
+    }
+    let stream_sizes = encoding.stream_offsets();
+    let arc_encoding = Arc::new(encoding);
+    Some((arc_encoding, stream_sizes))
 }
 
 pub(crate) struct GlyphCacheSession<'a> {
-    free_list: &'a mut Vec<Arc<Encoding>>,
     map: &'a mut GlyphMap,
     font_id: u64,
     font_index: u32,
     coords: &'a [NormalizedCoord],
-    size: Size,
     size_bits: u32,
-    style: &'a Style,
     style_bits: [u32; 2],
-    outlines: OutlineGlyphCollection<'a>,
-    hinter: Option<&'a HintingInstance>,
+    font: &'a Font,
     serial: u64,
-    cached_count: &'a mut usize,
+    hint: bool,
+    unresolved: &'a mut Vec<(VarKey, GlyphKey, Font)>,
 }
 
 impl GlyphCacheSession<'_> {
-    pub(crate) fn get_or_insert(
-        &mut self,
-        glyph_id: u32,
-    ) -> Option<(Arc<Encoding>, StreamOffsets)> {
+    pub(crate) fn get_or_insert(&mut self, glyph_id: u32) -> GlyphEntryStatus {
         let key = GlyphKey {
             font_id: self.font_id,
             font_index: self.font_index,
             glyph_id,
             font_size_bits: self.size_bits,
             style_bits: self.style_bits,
-            hint: self.hinter.is_some(),
+            hint: self.hint,
         };
         if let Some(entry) = self.map.get_mut(&key) {
             entry.serial = self.serial;
-            return Some((entry.encoding.clone(), entry.stream_sizes));
+            return entry.status.clone();
         }
-        let outline = self.outlines.get(GlyphId::new(key.glyph_id))?;
-        let mut encoding = self.free_list.pop().unwrap_or_default();
-        let encoding_ptr = Arc::make_mut(&mut encoding);
-        encoding_ptr.reset();
-        let is_fill = match &self.style {
-            Style::Fill(fill) => {
-                encoding_ptr.encode_fill_style(*fill);
-                true
-            }
-            Style::Stroke(stroke) => {
-                encoding_ptr.encode_stroke_style(stroke);
-                false
-            }
-        };
-        use skrifa::outline::DrawSettings;
-        let mut path = encoding_ptr.encode_path(is_fill);
-        let draw_settings = if key.hint {
-            if let Some(hinter) = self.hinter {
-                DrawSettings::hinted(hinter, false)
-            } else {
-                DrawSettings::unhinted(self.size, self.coords)
-            }
-        } else {
-            DrawSettings::unhinted(self.size, self.coords)
-        };
-        outline.draw(draw_settings, &mut path).ok()?;
-        if path.finish(false) == 0 {
-            encoding_ptr.reset();
-        }
-        let stream_sizes = encoding_ptr.stream_offsets();
+        let index = self.unresolved.len();
+        self.unresolved
+            .push((self.coords.into(), key, self.font.clone()));
+        let result = GlyphEntryStatus::Unresolved { index };
         self.map.insert(
             key,
             GlyphEntry {
-                encoding: encoding.clone(),
-                stream_sizes,
+                status: result.clone(),
                 serial: self.serial,
             },
         );
-        *self.cached_count += 1;
-        Some((encoding, stream_sizes))
+        result
+
+        // let outline = self.outlines.get(GlyphId::new(key.glyph_id))?;
+        // let mut encoding = Encoding::default();
+        // encoding.reset();
+        // let is_fill = match &self.style {
+        //     Style::Fill(fill) => {
+        //         encoding.encode_fill_style(*fill);
+        //         true
+        //     }
+        //     Style::Stroke(stroke) => {
+        //         encoding.encode_stroke_style(stroke);
+        //         false
+        //     }
+        // };
+        // use skrifa::outline::DrawSettings;
+        // let mut path = encoding.encode_path(is_fill);
+        // let draw_settings = if key.hint {
+        //     if let Some(hinter) = self.hinter {
+        //         DrawSettings::hinted(hinter, false)
+        //     } else {
+        //         DrawSettings::unhinted(self.size, self.coords)
+        //     }
+        // } else {
+        //     DrawSettings::unhinted(self.size, self.coords)
+        // };
+        // outline.draw(draw_settings, &mut path).ok()?;
+        // if path.finish(false) == 0 {
+        //     encoding.reset();
+        // }
+        // let stream_sizes = encoding.stream_offsets();
+        // let arc_encoding = Arc::new(encoding);
+        // self.map.insert(
+        //     key,
+        //     GlyphEntry {
+        //         encoding: arc_encoding.clone(),
+        //         stream_sizes,
+        //         serial: self.serial,
+        //     },
+        // );
+        // *self.cached_count += 1;
+        // Some((arc_encoding, stream_sizes))
     }
 }
 
@@ -225,18 +294,28 @@ type VarKey = smallvec::SmallVec<[NormalizedCoord; 8]>;
 
 type GlyphMap = HashMap<GlyphKey, GlyphEntry>;
 
-#[derive(Clone, Default)]
+// #[derive(Clone, Default)]
 struct GlyphEntry {
-    encoding: Arc<Encoding>,
-    stream_sizes: StreamOffsets,
+    status: GlyphEntryStatus,
     /// Last use of this entry.
     serial: u64,
 }
 
+#[derive(Clone)]
+pub(crate) enum GlyphEntryStatus {
+    Resolved {
+        encoding: Arc<Encoding>,
+        stream_sizes: StreamOffsets,
+    },
+    Unresolved {
+        index: usize,
+    },
+}
+
 /// We keep this small to enable a simple LRU cache with a linear
 /// search. Regenerating hinting data is low to medium cost so it's fine
 /// to redo it occasionally.
-const MAX_CACHED_HINT_INSTANCES: usize = 8;
+const MAX_CACHED_HINT_INSTANCES: usize = 2;
 
 pub(crate) struct HintKey<'a> {
     font_id: u64,
@@ -264,7 +343,7 @@ const HINTING_OPTIONS: HintingOptions = HintingOptions {
 };
 
 #[derive(Default)]
-struct HintCache {
+pub(crate) struct HintCache {
     // Split caches for glyf/cff because the instance type can reuse
     // internal memory when reconfigured for the same format.
     glyf_entries: Vec<HintEntry>,
diff --git a/vello_encoding/src/path.rs b/vello_encoding/src/path.rs
index a1703ce0..98703a16 100644
--- a/vello_encoding/src/path.rs
+++ b/vello_encoding/src/path.rs
@@ -175,8 +175,7 @@ impl Style {
         Some((self.flags_and_miter_limit & Self::MITER_LIMIT_MASK) as u16)
     }
 
-    #[cfg(test)]
-    fn is_fill(self) -> bool {
+    pub(crate) fn is_fill(self) -> bool {
         (self.flags_and_miter_limit & Self::FLAGS_STYLE_BIT) == 0
     }
 }
diff --git a/vello_encoding/src/resolve.rs b/vello_encoding/src/resolve.rs
index 44c03e3f..2c9d5ee6 100644
--- a/vello_encoding/src/resolve.rs
+++ b/vello_encoding/src/resolve.rs
@@ -157,7 +157,7 @@ pub fn resolve_solid_paths_only(encoding: &Encoding, packed: &mut Vec<u8>) -> La
 #[derive(Default)]
 pub struct Resolver {
     glyph_cache: GlyphCache,
-    glyphs: Vec<Arc<Encoding>>,
+    glyphs: Vec<Option<Arc<Encoding>>>,
     ramp_cache: RampCache,
     image_cache: ImageCache,
     pending_images: Vec<PendingImage>,
@@ -211,7 +211,9 @@ impl Resolver {
                     }
                     for glyph in &self.glyphs[glyphs.clone()] {
                         data.extend_from_slice(bytemuck::bytes_of(&PathTag::TRANSFORM));
-                        data.extend_from_slice(bytemuck::cast_slice(&glyph.path_tags));
+                        data.extend_from_slice(bytemuck::cast_slice(
+                            &glyph.as_ref().unwrap().path_tags,
+                        ));
                     }
                     data.extend_from_slice(bytemuck::bytes_of(&PathTag::PATH));
                 }
@@ -239,7 +241,9 @@ impl Resolver {
                         pos = stream_offset;
                     }
                     for glyph in &self.glyphs[glyphs.clone()] {
-                        data.extend_from_slice(bytemuck::cast_slice(&glyph.path_data));
+                        data.extend_from_slice(bytemuck::cast_slice(
+                            &glyph.as_ref().unwrap().path_data,
+                        ));
                     }
                 }
             }
@@ -361,7 +365,9 @@ impl Resolver {
                         pos = stream_offset;
                     }
                     for glyph in &self.glyphs[glyphs.clone()] {
-                        data.extend_from_slice(bytemuck::cast_slice(&glyph.styles));
+                        data.extend_from_slice(bytemuck::cast_slice(
+                            &glyph.as_ref().unwrap().styles,
+                        ));
                     }
                 }
             }
@@ -382,6 +388,7 @@ impl Resolver {
         self.image_cache.clear();
         self.pending_images.clear();
         self.patches.clear();
+        let mut unresolved = Vec::new();
         let mut sizes = StreamOffsets::default();
         let resources = &encoding.resources;
         for patch in &resources.patches {
@@ -398,9 +405,9 @@ impl Resolver {
                         extend: *extend,
                     });
                 }
-                Patch::GlyphRun { index } => {
+                Patch::GlyphRun { index: run_index } => {
                     let mut run_sizes = StreamOffsets::default();
-                    let run = &resources.glyph_runs[*index];
+                    let run = &resources.glyph_runs[*run_index];
                     let glyphs = &resources.glyphs[run.glyphs.clone()];
                     let coords = &resources.normalized_coords[run.normalized_coords.clone()];
                     let mut hint = run.hint;
@@ -429,24 +436,33 @@ impl Resolver {
                         continue;
                     };
                     let glyph_start = self.glyphs.len();
-                    for glyph in glyphs {
-                        let (encoding, stream_sizes) =
-                            session.get_or_insert(glyph.id).unwrap_or_else(|| {
-                                // HACK: We pretend that the encoding was empty.
-                                // In theory, we should be able to skip this glyph, but there is also
-                                // a corresponding entry in `resources`, which means that we would
-                                // need to make the patching process skip this glyph.
-                                (Arc::new(Encoding::new()), StreamOffsets::default())
-                            });
-                        run_sizes.add(&stream_sizes);
-                        self.glyphs.push(encoding);
+                    for glyph in glyphs.iter() {
+                        let status = session.get_or_insert(glyph.id);
+                        match status {
+                            crate::glyph_cache::GlyphEntryStatus::Resolved {
+                                encoding,
+                                stream_sizes,
+                            } => {
+                                run_sizes.add(&stream_sizes);
+                                self.glyphs.push(Some(encoding));
+                            }
+                            crate::glyph_cache::GlyphEntryStatus::Unresolved {
+                                index: resolved_index,
+                            } => {
+                                unresolved.push((resolved_index, self.glyphs.len()));
+                                self.glyphs.push(None);
+                            }
+                        }
                     }
                     let glyph_end = self.glyphs.len();
                     run_sizes.path_tags += glyphs.len() + 1;
                     run_sizes.transforms += glyphs.len();
+                    if run_sizes.draw_data > 0 {
+                        panic!("We would corrupt the images or gradients.");
+                    }
                     sizes.add(&run_sizes);
                     self.patches.push(ResolvedPatch::GlyphRun {
-                        index: *index,
+                        index: *run_index,
                         glyphs: glyph_start..glyph_end,
                         transform,
                         scale,
@@ -468,6 +484,14 @@ impl Resolver {
                 }
             }
         }
+
+        self.glyph_cache.resolve_in_parallel();
+        for (resolved_index, glyphs_idx) in unresolved {
+            let (encoding, stream_size) = self.glyph_cache.get_resolved(resolved_index);
+            sizes.add(&stream_size);
+            self.glyphs[glyphs_idx] = Some(encoding);
+        }
+
         sizes
     }
 

From 8f2534f5d91287bf983d02f03be5e1def8d570c7 Mon Sep 17 00:00:00 2001
From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
Date: Thu, 9 Jan 2025 17:33:34 +0000
Subject: [PATCH 2/4] Actually enter the spans

---
 vello_encoding/src/glyph_cache.rs | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/vello_encoding/src/glyph_cache.rs b/vello_encoding/src/glyph_cache.rs
index 974ec11f..0ee4ca23 100644
--- a/vello_encoding/src/glyph_cache.rs
+++ b/vello_encoding/src/glyph_cache.rs
@@ -106,12 +106,13 @@ impl GlyphCache {
         });
     }
     pub(crate) fn resolve_in_parallel(&mut self) {
-        tracing::trace_span!("Resolving glyph outlines", count = self.unresolved.len());
+        let _span = tracing::info_span!("Resolving glyph outlines", count = self.unresolved.len())
+            .entered();
         let result = self
             .unresolved
             .par_iter()
             .map_init(HintCache::default, |hint_cache, (coords, glyph, font)| {
-                tracing::trace_span!("Resolving single glyph");
+                let _span = tracing::trace_span!("Resolving single glyph").entered();
                 (
                     resolve_single_glyph(hint_cache, coords, glyph, font),
                     coords,

From 5c0d094d1dc2b2ff31fd405a8845b51cef37aa88 Mon Sep 17 00:00:00 2001
From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
Date: Thu, 9 Jan 2025 17:41:49 +0000
Subject: [PATCH 3/4] Add more profiling info

---
 vello_encoding/src/glyph_cache.rs | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/vello_encoding/src/glyph_cache.rs b/vello_encoding/src/glyph_cache.rs
index 0ee4ca23..86c8467b 100644
--- a/vello_encoding/src/glyph_cache.rs
+++ b/vello_encoding/src/glyph_cache.rs
@@ -159,16 +159,26 @@ fn resolve_single_glyph(
     let font_id = font.data.id();
     let font_index = font.index;
     let font = skrifa::FontRef::from_index(font.data.as_ref(), font.index).ok()?;
-    let outlines = font.outline_glyphs();
+
+    let outlines = {
+        let _span = tracing::trace_span!("Getting font outline builder").entered();
+        font.outline_glyphs()
+    };
     let size = Size::new(f32::from_bits(glyph.font_size_bits));
-    let outline = outlines.get(GlyphId::new(glyph.glyph_id))?;
+    let outline = {
+        let _span = tracing::trace_span!("Getting Glyph Outline").entered();
+        outlines.get(GlyphId::new(glyph.glyph_id))?
+    };
     let mut encoding = Encoding::default();
     encoding.reset();
     let style: crate::Style = bytemuck::cast(glyph.style_bits);
     encoding.encode_style(style);
     let is_fill = style.is_fill();
     use skrifa::outline::DrawSettings;
-    let mut path = encoding.encode_path(is_fill);
+    let mut path = {
+        let _span = tracing::trace_span!("Encoding path").entered();
+        encoding.encode_path(is_fill)
+    };
     let hinter = if glyph.hint {
         let key = HintKey {
             font_id,
@@ -186,7 +196,10 @@ fn resolve_single_glyph(
     } else {
         DrawSettings::unhinted(size, &**coords)
     };
-    outline.draw(draw_settings, &mut path).ok()?;
+    {
+        let _span = tracing::trace_span!("Drawing span").entered();
+        outline.draw(draw_settings, &mut path).ok()?;
+    }
     if path.finish(false) == 0 {
         encoding.reset();
     }

From 7cc7a9760d7ed45a47a33f9b75aa49de86eae707 Mon Sep 17 00:00:00 2001
From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
Date: Fri, 10 Jan 2025 08:50:10 +0000
Subject: [PATCH 4/4] Add a span around the hinter

---
 vello_encoding/src/glyph_cache.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vello_encoding/src/glyph_cache.rs b/vello_encoding/src/glyph_cache.rs
index 86c8467b..12f34b5d 100644
--- a/vello_encoding/src/glyph_cache.rs
+++ b/vello_encoding/src/glyph_cache.rs
@@ -180,6 +180,7 @@ fn resolve_single_glyph(
         encoding.encode_path(is_fill)
     };
     let hinter = if glyph.hint {
+        let _span = tracing::trace_span!("Getting hinter").entered();
         let key = HintKey {
             font_id,
             font_index,