Skip to content

Cache primary font queries and font metrics across layouts in FontContext - #19

Open
nicoburns wants to merge 3 commits into
devin/1788383056-vertical-alignfrom
devin/1788393297-font-cache
Open

Cache primary font queries and font metrics across layouts in FontContext#19
nicoburns wants to merge 3 commits into
devin/1788383056-vertical-alignfrom
devin/1788393297-font-cache

Conversation

@nicoburns

Copy link
Copy Markdown
Member

LLM Contributions: This PR was written with Devin, which also ran the benchmarks below.

Stacks on #18 (devin/1788383056-vertical-align). #18 re-runs a fontique::Query per style per layout (resolve_style_metrics) and re-parses fonts for FontMetrics; the shaper's FontSelector repeats the same query per item. This adds a cross-layout cache owned by FontContext:

  • Primary font cache: (FamilyId stack, FontWidth, FontWeight, FontStyle) -> Option<FontInstance + CharmapIndex>. Keyed on the actual FamilyId list because ResolveContext family-stack handles are per-LayoutContext interned indices, not stable across layouts. A miss runs the query but only accepts a font from the requested families (never from the query's fallback families), so the result is independent of fallback settings and the cached None means "no family font".
  • Metrics cache: (blob id, font index, size bits, synthesis variation settings + style variations) -> FontMetrics.
  • Both are bounded (MAX_ENTRIES = 1024, cleared when full).
  • Invalidation: new fontique::Collection::generation() counter, bumped on every mutating Collection method (register/unregister fonts, load system fonts, load from paths, generic families, fallbacks, clear) and when a shared collection syncs a newer version from another clone. FontContext compares it before each layout and drops the cache on change. FontContext::clear_cache() exists for mutations the collection can't see (e.g. swapping blob data); FontContext::cache_len() exposes entry counts. Struct-literal construction of FontContext is replaced with FontContext::from_parts.
  • The per-layout FontKey dedup in style_metrics.rs is kept as the first-level (hash-free) cache.
  • Shaper: FontSelector takes the cached primary font for the current style. A cluster fully covered by it is resolved without touching the query (this is exactly what the query would yield first). Otherwise the normal per-cluster fallback query runs, seeded with the primary font's coverage so it isn't computed twice. Fallback behaviour is unchanged; emoji clusters bypass the cache entirely as before.
  • Also adds the core_maths::CoreFloat import style_metrics.rs needed for --no-default-features --features libm (x86_64-unknown-none), which was failing on Implement CSS vertical-align for inline formatting contexts #18.

Tests: parley/src/tests/test_font_cache.rs covers reuse across layouts, invalidation on register_fonts / set_fallbacks, negative caching for unknown families, and Collection::generation() bumps.

cargo fmt --all --check, cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings pass.

Tango (parley_bench), base = #18 head ff83800, this branch = 5c31f79

cd parley-base && cargo export ../parley/target/benchmarks -- bench --bench=main   # at ff83800
cd parley/parley_bench && cargo bench -q --bench=main -- compare ../target/benchmarks/main -t 2
Benchmark base ... this PR Δ
Default Style - arabic 20 characters [12.6 us ... 12.2 us] -2.81%*
Default Style - latin 20 characters [6.2 us ... 5.7 us] -8.38%*
Default Style - japanese 20 characters [11.4 us ... 11.5 us] +0.98%
Default Style - arabic 1 paragraph [67.3 us ... 65.6 us] -2.49%*
Default Style - latin 1 paragraph [24.2 us ... 21.5 us] -11.39%*
Default Style - japanese 1 paragraph [92.9 us ... 93.2 us] +0.35%
Default Style - arabic 4 paragraph [284.5 us ... 284.2 us] -0.13%
Default Style - latin 4 paragraph [91.8 us ... 82.1 us] -10.62%*
Default Style - japanese 4 paragraph [134.3 us ... 134.0 us] -0.20%
Styled - arabic 20 characters [15.0 us ... 14.5 us] -3.26%*
Styled - latin 20 characters [8.3 us ... 7.7 us] -6.77%*
Styled - japanese 20 characters [13.1 us ... 12.9 us] -1.56%*
Styled - arabic 1 paragraph [71.5 us ... 71.5 us] -0.02%
Styled - latin 1 paragraph [30.7 us ... 28.3 us] -7.97%*
Styled - japanese 1 paragraph [103.3 us ... 107.1 us] +3.65%*
Styled - arabic 4 paragraph [323.3 us ... 324.7 us] +0.42%
Styled - latin 4 paragraph [119.4 us ... 109.9 us] -7.92%*
Styled - japanese 4 paragraph [146.0 us ... 151.4 us] +3.67%*
Word + Letter Spacing - arabic 20 characters [12.4 us ... 12.3 us] -0.27%
Word + Letter Spacing - latin 20 characters [6.3 us ... 5.7 us] -10.13%*
Word + Letter Spacing - japanese 20 characters [12.1 us ... 11.7 us] -2.88%
Word + Letter Spacing - arabic 1 paragraph [67.1 us ... 66.0 us] -1.62%*
Word + Letter Spacing - latin 1 paragraph [24.4 us ... 22.0 us] -9.98%*
Word + Letter Spacing - japanese 1 paragraph [95.3 us ... 94.0 us] -1.37%*
Word + Letter Spacing - arabic 4 paragraph [287.7 us ... 288.0 us] +0.12%
Word + Letter Spacing - latin 4 paragraph [94.9 us ... 82.3 us] -13.34%*
Word + Letter Spacing - japanese 4 paragraph [133.8 us ... 133.6 us] -0.11%
Repeated Justification - latin 4 paragraph [1.0 us ... 984.3 ns] -1.67%*
Caused by:
process didn't exit successfully: `/home/ubuntu/ epos/parley/target/release deps/main-a3b52844cca21a67 compare ../target/benchmarks/main -t 2 --bench` (exit status: 1)

Latin (where the primary font covers everything) gains 5–13%, which more than recovers the +3–10% #18 cost on short styled layouts. Arabic/Japanese rows (primary Roboto covers nothing, so every cluster goes through fallback) are within noise except a ~+3.5% on two "Styled - japanese" rows that was not reproducible across three runs (−1.7%..+3.9%).

Changelog

Added

  • fontique::Collection::generation() returns a counter that changes whenever the collection is mutated.
  • parley::FontContext caches primary font selection and font metrics across layouts; FontContext::from_parts, FontContext::clear_cache, FontContext::cache_len.

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/3a0b8cc210c041d8955c841fb2a76a8e
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/3a0b8cc210c041d8955c841fb2a76a8e?variant=devin-insiders
Requested by: @nicoburns

@staging-devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1788393297-font-cache branch 2 times, most recently from f1dfc77 to 7a2665b Compare September 3, 2026 14:05
@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1788393297-font-cache branch 2 times, most recently from 2bf324d to 441c7bd Compare September 3, 2026 15:47
@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1788393297-font-cache branch 3 times, most recently from be970e2 to 758580b Compare September 8, 2026 14:42
@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1788393297-font-cache branch 4 times, most recently from fc8a766 to f3463d2 Compare September 9, 2026 13:39
@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1788393297-font-cache branch 2 times, most recently from e5e21be to 533d713 Compare September 9, 2026 14:39
Cache fontique primary-font queries (keyed on the FamilyId stack and
attributes) and FontMetrics (keyed on blob id, index, size bits and
variations) in FontContext so they survive across layouts. The cache is
invalidated via a new Collection::generation() counter that is bumped on
every mutation of the fontique collection.

The shaper uses the cached primary font only for clusters it fully
covers; anything else goes through the existing per-cluster fallback
query so font fallback behaviour is unchanged.
When the cached primary font does not fully cover a cluster, seed the
fallback query with its coverage and skip it when the query yields it
again, so that no cluster computes coverage for the same font twice.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant