Skip to content

gpui_wgpu: Respect buffer_font_fallbacks setting#54878

Merged
ConradIrwin merged 10 commits into
zed-industries:mainfrom
Albab-Hasan:feat/linux-font-fallback
May 15, 2026
Merged

gpui_wgpu: Respect buffer_font_fallbacks setting#54878
ConradIrwin merged 10 commits into
zed-industries:mainfrom
Albab-Hasan:feat/linux-font-fallback

Conversation

@Albab-Hasan

@Albab-Hasan Albab-Hasan commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

wires user configured FontFallbacks into the cosmic text path. the chain is resolved at font load time and stored on each LoadedFont. layout_line splits each FontRun into spans by codepoint coverage and emits one Attrs per slot so cosmic text shapes each span with the correct face. inheriting codepoints (marks, zwj, zwnj, variation selectors) stick to the current span so emoji zwj sequences and combining marks are not torn across faces.

Closes #17254

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • Performance impact has been considered and is acceptable
  • Tests cover the new/changed behavior
  • [] The content is consistent with the UI/UX checklist

Release Notes:

  • added support for buffer_font_fallbacks on linux

wires user configured `FontFallbacks` into the cosmic text path. the
chain is resolved at font load time and stored on each `LoadedFont`.
`layout_line` splits each `FontRun` into spans by codepoint coverage and
emits one `Attrs` per slot so cosmic text shapes each span with the
correct face. inheriting codepoints (marks, zwj, zwnj, variation
selectors) stick to the current span so emoji zwj sequences and
combining marks are not torn across faces.

Closes zed-industries#17254
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Apr 25, 2026
@maxdeviant maxdeviant changed the title gpui_wgpu: respect buffer_font_fallbacks setting gpui_wgpu: Respect buffer_font_fallbacks setting Apr 25, 2026
@ConradIrwin

Copy link
Copy Markdown
Member

@Albab-Hasan thanks for this! I'm a little worried about performance. How expensive is looking up each character every time? How many microseconds does this add to rendering a typical page of code?

Claude also noticed that this defaults to continuing the run in the fallback font, it should probably switch back to the main font more aggressively (e.g. 字a should use the main font for "a").

I am not sure that we should here, but we do use the unicode-segmentation library to do glyph grouping in other places. Should we also use that here?

@ConradIrwin ConradIrwin self-assigned this Apr 27, 2026
@Albab-Hasan

Albab-Hasan commented Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

@ConradIrwin thanks for the review. pushed the bug fix in the latest commit.

as for performance i considered a few approaches before landing on the charmap probe:

  • pre-built coverage bitset per font at load time (faster): enumerate all supported codepoints once on load store a roaring bitmap or sparse bitset. lookup becomes O(1) vs the current binary search over cmap segments. obvious win if the charmap probe shows up in profiles.

  • unicode block range pre-filter (faster but brittle): skip the probe entirely for chars whose unicode block clearly maps to one font. fast but breaks on cross-script composition and needs maintenance.

  • per-(FontId, char) cache (mixed): high hit rate for real text but adds lock contention on the hot path.

  • try-shape-and-check (much slower): shape the character, check for .notdef. only useful as last resort.

i started with the charmap probe since its simple and already correct. happy to add a benchmark or switch to bitsets if you want numbers before merging.

@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

for the unicode segmentation yeah i think we should. the mark category heuristic misses hangul jamo, regional indicator pairs and indic prepend chars. switching to .graphemes(true) lets us delete is_inheriting_codepoint entirely and just check the first codepoint of each cluster. the crates already a workspace dep. what do you think?

@ConradIrwin

Copy link
Copy Markdown
Member

Sounds good to me. Are you able to run a micro benchmark? (e.g. Load a page full of code and see how many microseconds it takes to render a frame before and after with no font caching). If it's <5% that's seems ok; but if it's 10% or more we should think about optimization (like just ignoring ascii or something).

@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

ran a bench directly on layout_line (criterion, 100 samples, no layout cache):

scenario time
no fallback configured 3.94 ms
1 fallback, all-ASCII code text (primary covers all chars) 4.17 ms
overhead ~231 µs (~6%)

input is ~3 800 chars of ASCII code in one run (worst case — in practice Zed splits a line into many short runs).

6% is just over your threshold. the cost is one charmap.map() probe per character. skipping codepoints ≤ U+007F would eliminate almost all of it for code text since every latin font covers ASCII — happy to add that if you'd like:

fn pick_covering_slot(...) -> Option<usize> {
    if (ch as u32) <= 0x7F || is_inheriting_codepoint(ch) {
        return current;
    }
    ...
}

the zero-overhead case (no fallbacks configured, the default) is unchanged.

will open the unicode-segmentation follow-up as a separate pr.

@ConradIrwin

Copy link
Copy Markdown
Member

👍 Thank you for testing it!

I think we should do both the latin optimization and the unicode segmentation so we can land this in one cohesive chunk. It'd be annoying to merge this and then have to rush follow up changes before the next release ships.

@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

@ConradIrwin any updates?

@ConradIrwin

Copy link
Copy Markdown
Member

Sorry I don't get notified when the PR is pushed to (and I'm super behind on my reviews).

Happy to merge this, but looks like the linter is unhappy; what's the overhead with the optimization and unicode segmentation?

@Albab-Hasan

Albab-Hasan commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

@ConradIrwin My bad I'll fix all the issues. And yeah this pr just disappeared for some reason that might explain you not getting the notification.

Benchmarks:

No fallback configured | 3.87 ms | baseline
1 fallback, all ASCII code text | 3.94 ms | ~70 µs (~1.8%)

Down from ~6% overhead before. The ASCII skip (char <= '\u{007F}') removes the charmap probe for the most code text and grapheme segmentation replaced the is_inheriting_codepoint heuristic entirely.

@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

@ConradIrwin done

@ConradIrwin ConradIrwin added this pull request to the merge queue May 15, 2026
@ConradIrwin

Copy link
Copy Markdown
Member

Thank you!

Merged via the queue into zed-industries:main with commit 1c16e13 May 15, 2026
32 checks passed
@zed-zippy zed-zippy Bot added the PR state:needs review Used to label PRs that are in need of a post-merge approval label May 15, 2026
@cole-miller

Copy link
Copy Markdown
Member

@zed-industries/approved

@zed-zippy zed-zippy Bot removed the PR state:needs review Used to label PRs that are in need of a post-merge approval label May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

buffer_font_fallbacks doesn't seem to work under Linux

3 participants