Skip to content

perf(rendering): reuse word-highlighted caption layouts while their colours are unchanged - #2799

Open
richiemcilroy wants to merge 2 commits into
mainfrom
perf/rendering-caption-highlight-shaping
Open

richiemcilroy wants to merge 2 commits into
mainfrom
perf/rendering-caption-highlight-shaping

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Word-highlighted captions (active_word_highlight with the colour style) rebuilt a Buffer, ran full Shaping::Advanced shaping and dropped the layout cache on every frame. Plain captions (4577e9d) and the text overlay (164bfef) already reuse the last frame's layout when their inputs are unchanged.

Change

  • The highlight path now goes through the same cache as plain captions. The cache key is everything that reaches set_text / set_rich_text: text, font size, wrap width, family, weight and the colours. For highlighted captions the colours are the exact rich-text spans (byte range plus the u8 RGBA colour of each word and gap), so the active word, the transition blend and the caption fade are all in the key.
  • On a hit the previous Buffer is reused. On a miss it is built with the same calls, in the same order, with the same arguments as before. Output is unchanged by construction.
  • Shaping moved into shape_caption_text and a small CaptionText cache so it can be tested without a GPU. The per-word needle no longer clones when uppercase is off.

Why not shape once and recolour at draw time: glyphon 0.9 reads each glyph's colour from LayoutGlyph::color_opt, which cosmic-text 0.14 sets during shaping and exposes only through & accessors. Recolouring would need a fork of either crate. Keying on the u8 colours keeps output identical. Shaping is skipped on every frame where no word's colour or alpha changes: between transitions, once a fade-in has settled to its final u8 value, and on held words.

Tests

  • shaping_tests::cached_word_highlight_layout_matches_an_uncached_shape_every_frame (CPU, runs in cargo test): steps a highlighted caption at 60 fps through fade in, every word change and fade out, switching wrap width every 45 frames, for three variants (default, uppercase with Unicode and a gold highlight with a short transition, zero transition). Every frame it compares each laid out glyph (position, font, glyph id, colour, cluster range) from the cached buffer with a buffer built by a verbatim copy of the old per-frame code. It also asserts the cache reshapes on fewer frames than it draws.
  • shaping_tests::word_highlight_cache_reshapes_when_any_layout_input_changes: text, uppercase, font size, wrap width, family, weight, highlight colour, transition duration, active word and caption fade each force a reshape, the result matches the uncached build, and identical inputs do not reshape. Also covers switching between plain and highlighted text.
  • GPU tests (ignored, need an adapter): every_caption_frame_renders_like_a_fresh_layer gains two highlighted variants (gold, outline, uppercase, pop; zero transition with a long fade). New highlighted_frames_follow_output_size_changes_like_a_fresh_layer changes the output size every 7 frames. reused_layer_follows_style_and_output_size_changes gains nine highlighted cases. All compare rendered pixels with a fresh layer.
  • New ignored bench benchmark_word_highlight_prepare times CaptionsLayer::prepare over 600 frames at 1080p and prints an FNV digest of every rendered frame, so base and head output can be compared directly.

Measurements

benchmark_word_highlight_prepare, 1920x1080, 600 frames at 60 fps (2204 visible samples over 4 timed passes), base (main plus the bench commit) and head built with the same release profile on the same runner and run interleaved, 6 rounds each. Numbers are the median of the per-round total CaptionsLayer::prepare time for 600 frames. Scratch run: https://github.com/CapSoftware/Cap/actions/runs/39538547724

Case Linux base Linux head Change macOS base macOS head Change
plain (no highlight) 5.47 ms 5.54 ms +1.1% 15.62 ms 17.90 ms noise
colour highlight (default white) 38.70 ms 32.42 ms -16.2% 49.88 ms 38.50 ms -22.8%
colour highlight, gold, outline 56.39 ms 53.48 ms -5.2% 62.48 ms 58.76 ms -6.0%
colour highlight, back-to-back words 38.50 ms 27.46 ms -28.7% 50.51 ms 36.14 ms -28.4%

Linux is blacksmith-8vcpu-ubuntu-2404 (lavapipe), macOS is blacksmith-6vcpu-macos-26. The macOS VM drifts a lot between rounds (plain ranged 7.7 to 20.3 ms on both sides), so read its plain row as unchanged; Linux is stable and shows the plain path is unchanged.

The gain is bounded by how often no colour changes between frames. With the default 0.25 s word transition, a word's fade in keeps changing its u8 colour for most of the word, and a gold highlight changes the blue channel by 255, so it settles even later. Back-to-back words (the usual transcript shape, no fade out between words) hit the cache most.

Pixel output: the bench hashes every rendered frame. Base and head digests match for every case on both platforms (Linux: plain 46a57c3a96ae770f, highlight 07e8a12b482c5927, gold outline d0669b00b3f7ff1a, back-to-back 0070e1ef26b7663b; macOS: ab266f7b1fd93bfb, 5185a2325fe0539d, 741779ddcc4df37c, c76e43bfd068fbeb). The ignored GPU tests pass on both runners in the same run.

CI

  • Engine verify FULL dispatched for captions, export and playback on Linux, macOS and Windows: passed (https://github.com/CapSoftware/Cap/actions/runs/39539519286).
  • PR Engine verify: only the known macOS crash/recorder-kill/*/static-screen fragment-span flake fails.
  • sync-tests fails on main too: crates/rendering/tests/display_layout.rs still passes &ProjectConfiguration to ProjectUniforms::new (test added in 19de9e5). Not touched here.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no actionable issues were found.

Summary

Highlighted captions now reuse their shaped text when the text, font settings, width, and exact span colours stay unchanged.

  • Word-highlighted captions reuse their layout while its inputs stay the same.

Reviews (1) · Last reviewed commit: "perf(rendering): reuse word-highlighted ..." · Reviewed by Greptile

@blacksmith-sh

This comment has been minimized.

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Found 2 test failures on Blacksmith runners:

Failures

Test View Logs
cap-verify::crash/crash_safety View Logs
cap-verify::crash/crash_safety View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@richiemcilroy

Copy link
Copy Markdown
Member Author

@greptileai review

@richiemcilroy
richiemcilroy marked this pull request as ready for review October 22, 2026 06:21
@richiemcilroy

Copy link
Copy Markdown
Member Author

@greptileai review

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

View 1 other finding in Capy.

Open in Capy

This branch was successfully deployed

2 active deployments
Preview – cap-web — 0d59c166 Deployed Oct 21, 2026 by vercel[bot]
Preview – cap-status — 0d59c166 Deployed Oct 21, 2026 by vercel[bot]
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