Improve terminal rendering performance (#33345)

Closes #18263

Improvements:

• **Batch text rendering** - Combine adjacent cells with identical
styling into single text runs to reduce draw calls
• **Throttle hyperlink searches** - Limit hyperlink detection to every
100ms or when mouse moves >5px to reduce CPU usage
• **Pre-allocate collections** - Use `Vec::with_capacity()` for cells,
runs, and regions to minimize reallocations
• **Optimize background regions** - Merge adjacent background rectangles
to reduce number of draw operations
• **Cache selection text** - Only compute terminal selection string when
selection exists

Release Notes:

- Improved terminal rendering performance.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Alisina Bahadori 2025-07-08 11:05:01 -04:00 committed by GitHub
parent bcac748c2b
commit 925464cfc6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 609 additions and 177 deletions

View file

@ -487,7 +487,7 @@ impl Element for TextElement {
let font_size = style.font_size.to_pixels(window.rem_size());
let line = window
.text_system()
.shape_line(display_text, font_size, &runs);
.shape_line(display_text, font_size, &runs, None);
let cursor_pos = line.x_for_index(cursor);
let (selection, cursor) = if selected_range.is_empty() {

View file

@ -357,6 +357,7 @@ impl WindowTextSystem {
text: SharedString,
font_size: Pixels,
runs: &[TextRun],
force_width: Option<Pixels>,
) -> ShapedLine {
debug_assert!(
text.find('\n').is_none(),
@ -384,7 +385,7 @@ impl WindowTextSystem {
});
}
let layout = self.layout_line(&text, font_size, runs);
let layout = self.layout_line(&text, font_size, runs, force_width);
ShapedLine {
layout,
@ -524,6 +525,7 @@ impl WindowTextSystem {
text: Text,
font_size: Pixels,
runs: &[TextRun],
force_width: Option<Pixels>,
) -> Arc<LineLayout>
where
Text: AsRef<str>,
@ -544,9 +546,9 @@ impl WindowTextSystem {
});
}
let layout = self
.line_layout_cache
.layout_line(text, font_size, &font_runs);
let layout =
self.line_layout_cache
.layout_line_internal(text, font_size, &font_runs, force_width);
font_runs.clear();
self.font_runs_pool.lock().push(font_runs);

View file

@ -482,6 +482,7 @@ impl LineLayoutCache {
font_size,
runs,
wrap_width,
force_width: None,
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame.upgradable_read();
@ -516,6 +517,7 @@ impl LineLayoutCache {
font_size,
runs: SmallVec::from(runs),
wrap_width,
force_width: None,
});
let mut current_frame = self.current_frame.write();
@ -534,6 +536,20 @@ impl LineLayoutCache {
font_size: Pixels,
runs: &[FontRun],
) -> Arc<LineLayout>
where
Text: AsRef<str>,
SharedString: From<Text>,
{
self.layout_line_internal(text, font_size, runs, None)
}
pub fn layout_line_internal<Text>(
&self,
text: Text,
font_size: Pixels,
runs: &[FontRun],
force_width: Option<Pixels>,
) -> Arc<LineLayout>
where
Text: AsRef<str>,
SharedString: From<Text>,
@ -543,6 +559,7 @@ impl LineLayoutCache {
font_size,
runs,
wrap_width: None,
force_width,
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame.upgradable_read();
@ -557,16 +574,30 @@ impl LineLayoutCache {
layout
} else {
let text = SharedString::from(text);
let layout = Arc::new(
self.platform_text_system
.layout_line(&text, font_size, runs),
);
let mut layout = self
.platform_text_system
.layout_line(&text, font_size, runs);
if let Some(force_width) = force_width {
let mut glyph_pos = 0;
for run in layout.runs.iter_mut() {
for glyph in run.glyphs.iter_mut() {
if (glyph.position.x - glyph_pos * force_width).abs() > px(1.) {
glyph.position.x = glyph_pos * force_width;
}
glyph_pos += 1;
}
}
}
let key = Arc::new(CacheKey {
text,
font_size,
runs: SmallVec::from(runs),
wrap_width: None,
force_width,
});
let layout = Arc::new(layout);
current_frame.lines.insert(key.clone(), layout.clone());
current_frame.used_lines.push(key);
layout
@ -591,6 +622,7 @@ struct CacheKey {
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,
force_width: Option<Pixels>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
@ -599,6 +631,7 @@ struct CacheKeyRef<'a> {
font_size: Pixels,
runs: &'a [FontRun],
wrap_width: Option<Pixels>,
force_width: Option<Pixels>,
}
impl PartialEq for (dyn AsCacheKeyRef + '_) {
@ -622,6 +655,7 @@ impl AsCacheKeyRef for CacheKey {
font_size: self.font_size,
runs: self.runs.as_slice(),
wrap_width: self.wrap_width,
force_width: self.force_width,
}
}
}