Copied to clipboard
Swap · TextSystem

Bring your own text engine.

The engine draws text through a single trait. bite-gp-parley is a second implementation of it — install the crate, hand it to the application at startup, and every existing div() keeps rendering. Nothing else in the tree changes.

2.4× faster wrapping the same paragraph at the same width
11.5× faster line metrics — ascent, descent, baseline
5.1× slower to rasterize one 16 px glyph. That is the trade
The seam

One trait to implement.

TextSystem lives in gpui_engine and covers shaping, layout and rasterization. The facade exposes exactly one way to replace it: Application::with_text_system. A text engine written outside the repository plugs in through that method, which is what makes this an install rather than a patch.

The interface contract gpui_engine::TextSystem
Host binary Application::run() Draws text through the installed system and never asks which one it is. One trait, called per run, per line, per glyph.
with_text_system(…)
one choice per process
bite-gp-parley this page · Linebender shaping, host fonts Active
bite-gp-engine-default DefaultTextSystem over CosmicTextSystem Stock
# Cargo.toml
[dependencies]
bite-gpui = "1.21"
bite-gp-parley = "1.21"
use gpui::*;
use gpui_parley::ParleyTextSystem;

fn main() {
    application()
        // new() already returns an Arc, which is what this wants
        .with_text_system(ParleyTextSystem::new())
        .run(|cx: &mut App| {
            cx.open_window(WindowOptions::default(), |_, cx| {
                cx.new(|_| MyView)
            })
            .unwrap();
        });
}
No rename needed

Cargo exposes a dependency under its library name, and the published package keeps its upstream lib target — so bite-gp-parley is use gpui_parley::…, with no package = key in your manifest.

Measured

What it changes.

Both columns below are the same operations on the same font and the same 16 px text, called through &dyn TextSystem so the implementation is the only variable. The vanilla column is what a Linux user gets without asking for anything — DefaultTextSystem over CosmicTextSystem. The parley column is bite-gp-parley 1.21.1.

Criterion microbenchmarks, 16 px faster slower
operation vanilla parley result
new — cold, registers one face 5.75 µs 44.24 ms 7,700× slower
resolve_font 67.9 ns 99.5 ns 1.5× slower
bounding_box 18.1 ns 4.8 ns 3.8× faster
line_metrics — ascent + descent + baseline 68.0 ns 5.9 ns 11.5× faster
layout_line — a 3-line paragraph 131.30 µs 83.31 µs 1.58× faster
wrap_line — the same paragraph, 420 px 79.07 µs 33.27 µs 2.4× faster
raster_bounds, cached 22.3 ns 62.5 ns 2.8× slower
raster_bounds, uncached 3.36 µs 7.79 µs 2.3× slower
rasterize_glyph 3.06 µs 15.48 µs 5.1× slower

Mean estimates from criterion, 100 measurements per benchmark. Host: Intel i7-8750H, linux x86_64 — a laptop, not a controlled benchmarking host.

It is a trade, not a win.

Parley is faster where the engine drives text in bulk: wrapping the same paragraph is 2.4× faster, a three-line layout 1.58×, and the per-font metrics — the numbers a rearchitected engine reads constantly — 3.8× and 11.5×. It is slower at putting one glyph on screen: 15.48 µs to rasterize a 16 px glyph against vanilla's 3.06 µs.

A project that swaps the text system is buying shaping — complex scripts, font fallback, family stacks — and paying for it in rasterization. The point of publishing both columns is that this is visible before you install it rather than after.

Startup costs 44 ms

ParleyTextSystem::new is 7,700× vanilla's, and that is the host's fonts, not a regression in the same work: it builds a fontique collection with system_fonts: true, enumerating every font on the machine. The previous release, which did not, constructed in 33.88 µs. It is paid once per process, and it is what buys the host-font fallback the crate exists for — worth knowing before installing it in something that starts often.

The cache

The cache both systems share.

Memoizing glyph raster bounds is the mechanism behind the dramatic ratio on the marketing page, so the honest measurement is cached against uncached within one implementation:

raster_bounds, cached against not one implementation at a time
implementation uncached cached the cache buys
vanilla 3.36 µs 22.3 ns 151×
parley 1.21.1 7.79 µs 62.5 ns 125×
parley 1.21.0 (previous) 36.30 µs 61.2 ns 593×

The two parley rows differ only in the uncached path: 36.30 µs down to 7.79 µs, with the cached path unchanged at roughly 62 ns.

1.21.1

What the release changed.

The rasterization cost above was not always 15.48 µs. The hinting instance is now cached per face and size instead of rebuilt once per rasterized glyph, which is the whole of the improvement below — measured on the same host within an hour of each other.

The first amendment, side by side same host, within an hour
operation 1.21.0 1.21.1 result
rasterize_glyph 78.30 µs 15.48 µs 5.1× faster
raster_bounds, uncached 36.30 µs 7.79 µs 4.7× faster
layout_line 95.93 µs 83.31 µs 1.15× faster
wrap_line 15.56 µs 33.27 µs 2.1× slower
new 33.88 µs 44.24 ms host fonts

The wrap_line loss and the metric costs in the first table are the same host-font-database change as the 44 ms: each lookup now consults the shaper's own database rather than a table held by the crate.

Reproduce

Run it yourself.

The harness is one file, and its two columns are two implementations behind the same trait — one implementation measured alone would say what Parley costs, not what choosing Parley costs.

# the harness the table above came from
# the harness the table above came from, in the usage suite cd parley-demo CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 \ CARGO_PROFILE_BENCH_DEBUG=0 cargo bench --bench text_system

That harness is part of the project's own usage suite, which is not published yet — so the table above is backed by a recorded run, with its command, toolchain, host and every sample, rather than by a repository you can clone today. What you can run right now is the crate's own cost examples.