Skip to content

plot: Paint grids as quads and cache tessellated line and area paths - #3071

Merged
huacnlee merged 1 commit into
mainfrom
perf/plot-paint-cache
Sep 14, 2026
Merged

huacnlee merged 1 commit into
mainfrom
perf/plot-paint-cache

Conversation

@huacnlee

Copy link
Copy Markdown
Member

Description

A chart card repaints on every frame it is on screen — a scrolling list moves it — and profiling the Longbridge AI chat on iOS (Time Profiler, iPhone 17 Pro simulator, Release) put the chart cards at 12% of all CPU while visible. Two things dominated:

  • Grid::paint stroked every grid line as a path: lyon tessellation per line, and for dashed grids a PathMeasurements + sampling pass first. Grid lines are axis-aligned, so each line (or each dash of one) is now a 1px paint_quad centred on the coordinate — the pixels the 1px stroke covered. Dash segmentation follows the SVG stroke-dasharray rules PathBuilder implements (odd arrays repeat), with unit tests.
  • Line::paint / Area::paint tessellated their curves every frame, though the vertices only depend on the projected points relative to the chart origin. New in plot:
    • PathCache — builds a path once at a zero origin and moves its vertices to the frame's origin afterwards;
    • ShapeKey — cache key from the projected points and stroke style;
    • PathCaches — a plot's caches kept in window element state (use_keyed_state), since plots are values rebuilt on every render;
    • Line::paint_cached / Area::paint_cached — opt-in; the plain paint methods are unchanged.

Adoption in a plot:

let caches = PathCaches::for_paint("lines", window, cx);
caches.update(cx, |caches, _| {
    for (ix, line) in lines.iter().enumerate() {
        line.paint_cached(&bounds, caches.slot(ix), window);
    }
});

Measured on the AI chat with several vis-chart cards and a quote card on screen, same scroll script:

share of draw time before after
Grid::paint 5.6% 1.1%
lyon tessellation 5.3% 1.1%
chart cards (vis_chart_card) 11.6% 5.3%

StrokeStyle gains Hash/PartialEq/Eq so it can key a cache.

How to Test

  • cargo test -p gpui-component --lib plot:: — grid dash segmentation and 1px line boxes; PathCache builds once per key and translates per origin; ShapeKey changes with points and extras.
  • Any chart story: grids (solid and dashed), bar, line, area, pie render as before.
  • Passed cargo run for story tests related to the changes. (not run — verified in the AI chat on the iOS simulator: dashed grids, bars, pies render as before)

🤖 Generated with Claude Code

https://claude.ai/code/session_01HY6KNBpkTcKkW2PpjrFhji

A chart card repaints on every frame it is on screen — a scrolling list
moves it — and profiling the Longbridge AI chat on iOS put the chart
cards at 12% of all CPU while visible. Two things dominated:

Grid::paint stroked every grid line as a path: lyon tessellation per
line, and for dashed grids a path measurement and sampling pass first.
Grid lines are axis-aligned, so each line (or each dash) is now a 1px
quad centred on the coordinate, which is what the 1px stroke covered.

Line::paint and Area::paint tessellated their curves every frame,
though the vertices only depend on the projected points relative to the
chart origin. PathCache builds a path once at a zero origin and moves
its vertices to the frame's origin afterwards; ShapeKey derives the
cache key from the projected points and the stroke style; PathCaches
keeps a plot's caches in window element state, since plots are values
rebuilt on every render. Line::paint_cached and Area::paint_cached use
them; the plain paint methods are unchanged.

On the AI chat with several cards on screen (iPhone 17 Pro simulator,
Release), Grid::paint went from 5.6% to 1.1% of draw time, lyon from
5.3% to 1.1%, and the chart cards' share of draw from 11.6% to 5.3%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HY6KNBpkTcKkW2PpjrFhji
@huacnlee
huacnlee merged commit 501c739 into main Sep 14, 2026
11 checks passed
@huacnlee
huacnlee deleted the perf/plot-paint-cache branch September 14, 2026 15:51
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