Make Line::items() glyph-run iteration linear and cluster-free - #21
Open
nicoburns wants to merge 7 commits into
Open
Make Line::items() glyph-run iteration linear and cluster-free#21nicoburns wants to merge 7 commits into
nicoburns wants to merge 7 commits into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Adds long-single-line benchmarks to measure cases where performance is `O(line length)` These have been used to test linebender#762 **LLM Contributions**: Generated with Fable 5.1 Low **Changelog**: None
<!-- Please ensure that you have reviewed our LLM ("AI") policy at
https://linebender.org/wiki/llm-policy/.
If you did not use any LLM tools, please replace `Unspecified` with
`None`. -->
LLM Contributions: Mostly generated.
Follow-up to
linebender#738 (comment).
This is also what Gecko and Blink do (with whitespace collapsing off).
Compare:
https://developer.mozilla.org/en-US/play?id=gilITaZ%2B0hCJVuNWyCoME9xqzkpiKGs0u2alPNKuvKXYzUEm2XX1efjm7j5qJPFZdiEZU%2FYl7eSQxZlN.
<!--
If our users need to know about this change, please describe that in the
quote block below.
What you write here will be edited by us later - it doesn't need to be
perfect.
If this change doesn't need a changelog entry, please replace the next
line with `**Changelog: None**`.
-->
**Changelog: None**
… hard/explicit newline (linebender#746) LLM Contributions: Bug identified and code generated with Devin Ultra. Fix `calculate_content_widths` when an inline boxes immediately follows a hard/explicit newline. They were previously incorrectly counted as being before the newline because the "Mandatory Break" is only set on the character *following* the line break. **This change mirrors what the line-breaking is already doing into the content width calculation.** **Changelog** > ### Added > > - Fix `calculate_content_widths` when an inline boxes immediately follows a hard/explicit newline. They were previously incorrectly counted as being before the newline. --------- Signed-off-by: Nico Burns <nico@nicoburns.com> Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
… glyphs Each call to GlyphRunIter::next rebuilt the run's cluster/glyph iterator and skipped glyph_start elements, making iteration over a run's glyph runs O(glyphs * glyph runs). Keep a peekable cluster cursor across calls instead.
staging-devin-ai-integration
Bot
force-pushed
the
devin/1788398127-glyph-run-iter
branch
from
September 3, 2026 18:56
5a2c020 to
8253301
Compare
…lusters Reading style index, glyph counts and advances from the run's ShapedSlice avoids constructing a Cluster (Run + Atom + Grapheme) and its composed glyph iterator for every cluster, which dominated the cost of iterating a line's positioned items.
staging-devin-ai-integration
Bot
force-pushed
the
devin/1788398127-glyph-run-iter
branch
from
September 3, 2026 19:08
8253301 to
ef346d5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Contributions: This PR was written with Devin, which also ran the benchmarks below.
Line::items()(the positionedGlyphRuniterator used by every renderer, e.g. Blitz/Vello) rebuiltrun.visual_clusters().flat_map(|c| c.glyphs()).skip(self.glyph_start)on everynext(). That is O(glyphs × glyph runs) per run, and since theparley_enginerefactor each skipped element is a by-valueCluster(Run+Atom+Grapheme) plus a large composed glyph-iterator state, so this path went from ~200 ms to ~4 s of a Blitz layout of the Barack Obama Wikipedia page (see benchmark comment on DioxusLabs/blitz#832).Two independent fixes, as separate commits so they can be measured separately:
28ea7e7):GlyphRunIterkeeps a peekable cluster cursor for the current run and resumes from it instead of restarting +skip. Removes the quadratic term.5a2c020):GlyphRunIterno longer materialisesClusters or callsCluster::glyphs(). A smallAtomCursorwalks the run'sShapedSliceatoms (atoms_start()/atoms_end(),prev()for RTL), reads the style index fromatom.characters()[0], and sums glyph counts/advances fromshaped_cluster_glyphs()plusEffectiveSpacing::gaps(atom)— the same quantitiesRun::glyphs_inproduces, without constructing the composed iterator. Glyph runs always end on atom boundaries (style is per character,Cluster::style_indexalready used the atom's first character), so resuming at atom granularity is exact.Semantics (
style_index,glyph_start,glyph_count,offset,advance, RTL order, letter/word spacing, justification, ligatures/glyph-less clusters) are unchanged;GlyphRun::glyphs()still usesglyphs_in(...).skip(glyph_start).take(glyph_count). TheClustersiterator is untouched for other consumers.Also:
parley_bench: newStyled Itemsbenchmark that walksline.items()/positioned_glyphs()of a pre-built styled layout. None of the existing benchmarks executedGlyphRunIter::next, which is why this regression was invisible to Tango.parley/src/tests/test_glyph_runs.rs: checksitems()against a naive split ofvisual_clusters().flat_map(glyphs)(style, glyph ids, advances, offsets) for latin/arabic/japanese/mixed text × spacing on/off × start/justify.cargo fmt --all --check,cargo test --workspace,cargo clippy --workspace --all-targets -- -D warningspass.Benchmarks
Variants (all include the
Styled Itemsbench commitf224cc7so Tango can compare it):f224cc7main(83acf8f) + bench only28ea7e75a2c020with the cursor disabled —AtomCursor::new(run)pernext()and skippingglyph_startglyphs atom by atom5a2c020Tango,
parley_benchStyled Items(<variant>/target/benchmarks/main compare <base>/target/benchmarks/main -t 1 -o; columns[base ... variant])All other benchmarks (
Default Style,Styled,Word + Letter Spacing,Repeated Justification) don't callitems(); they moved within ±3% in either direction across the three comparisons (noise/code layout).Blitz, Barack Obama Wikipedia page, headless, 1280 px (same harness/inputs as the DioxusLabs/blitz#832 report)
Blitz
main(1884860, with the API-compat edits needed for Parleymain) built--releasewith[patch]pointing at each Parley worktree; 3 warm-ups + 20 iterations per run, 3 interleaved rounds (base cursor ref both ×3), medians of the per-round medians:main)Per-round relayout medians: base 929/926/946, cursor 352/348/353, ref-only 185/181/183, both 144/151/156. For reference, Blitz
mainwith crates.io Parley0.11.1measured 147 ms relayout on the same machine, so this restores the pre-refactor cost of the renderer's item walk.Interpretation: the slice walk alone buys more than the cursor alone (the constant per skipped element was ~18× the quadratic-term exposure at Wikipedia-paragraph glyph-run counts), but only both together make the iteration O(n) with cheap elements; the remaining
line.items()cost in the Blitz profile is now negligible.Changelog
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