mirror of
https://github.com/wez/wezterm.git
synced 2024-12-24 05:42:03 +03:00
micro-optimize clustering
This improves it by ~4x for long lines, taking it from 120us to ~30us.
This commit is contained in:
parent
8953bff276
commit
f91ca30008
@ -539,7 +539,12 @@ impl Cell {
|
||||
|
||||
/// Returns the number of cells visually occupied by this grapheme
|
||||
pub fn width(&self) -> usize {
|
||||
grapheme_column_width(self.str())
|
||||
let s = self.str();
|
||||
if s.len() == 1 {
|
||||
1
|
||||
} else {
|
||||
grapheme_column_width(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the attributes of the cell
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::cell::{Cell, CellAttributes};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A `CellCluster` is another representation of a Line.
|
||||
/// A `Vec<CellCluster>` is produced by walking through the Cells in
|
||||
@ -9,34 +10,97 @@ use crate::cell::{Cell, CellAttributes};
|
||||
pub struct CellCluster {
|
||||
pub attrs: CellAttributes,
|
||||
pub text: String,
|
||||
pub byte_to_cell_idx: Vec<usize>,
|
||||
byte_to_cell_idx: Vec<usize>,
|
||||
first_cell_idx: usize,
|
||||
}
|
||||
|
||||
impl CellCluster {
|
||||
/// Given a byte index into `self.text`, return the corresponding
|
||||
/// cell index in the originating line.
|
||||
pub fn byte_to_cell_idx(&self, byte_idx: usize) -> usize {
|
||||
if self.byte_to_cell_idx.is_empty() {
|
||||
self.first_cell_idx + byte_idx
|
||||
} else {
|
||||
self.byte_to_cell_idx[byte_idx]
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the list of CellClusters from a set of visible cells.
|
||||
/// The input is typically the result of calling `Line::visible_cells()`.
|
||||
pub fn make_cluster<'a>(iter: impl Iterator<Item = (usize, &'a Cell)>) -> Vec<CellCluster> {
|
||||
pub fn make_cluster<'a>(
|
||||
hint: usize,
|
||||
iter: impl Iterator<Item = (usize, &'a Cell)>,
|
||||
) -> Vec<CellCluster> {
|
||||
let mut last_cluster = None;
|
||||
let mut clusters = Vec::new();
|
||||
let mut whitespace_run = 0;
|
||||
let mut only_whitespace = false;
|
||||
|
||||
for (cell_idx, c) in iter {
|
||||
let cell_str = c.str();
|
||||
let normalized_attr = c.attrs().clone().set_wrapped(false).clone();
|
||||
let normalized_attr = if c.attrs().wrapped() {
|
||||
let mut attr_storage = c.attrs().clone();
|
||||
attr_storage.set_wrapped(false);
|
||||
Cow::Owned(attr_storage)
|
||||
} else {
|
||||
Cow::Borrowed(c.attrs())
|
||||
};
|
||||
|
||||
last_cluster = match last_cluster.take() {
|
||||
None => {
|
||||
// Start new cluster
|
||||
Some(CellCluster::new(c.attrs().clone(), cell_str, cell_idx))
|
||||
only_whitespace = cell_str == " ";
|
||||
whitespace_run = if only_whitespace { 1 } else { 0 };
|
||||
Some(CellCluster::new(
|
||||
hint,
|
||||
normalized_attr.into_owned(),
|
||||
cell_str,
|
||||
cell_idx,
|
||||
))
|
||||
}
|
||||
Some(mut last) => {
|
||||
if last.attrs != normalized_attr {
|
||||
if last.attrs != *normalized_attr {
|
||||
// Flush pending cluster and start a new one
|
||||
clusters.push(last);
|
||||
Some(CellCluster::new(normalized_attr, cell_str, cell_idx))
|
||||
|
||||
only_whitespace = cell_str == " ";
|
||||
whitespace_run = if only_whitespace { 1 } else { 0 };
|
||||
Some(CellCluster::new(
|
||||
hint,
|
||||
normalized_attr.into_owned(),
|
||||
cell_str,
|
||||
cell_idx,
|
||||
))
|
||||
} else {
|
||||
// Add to current cluster
|
||||
last.add(cell_str, cell_idx);
|
||||
Some(last)
|
||||
// Add to current cluster.
|
||||
|
||||
// Force cluster to break when we get a run of 2 whitespace
|
||||
// characters following non-whitespace.
|
||||
// This reduces the amount of shaping work for scenarios where
|
||||
// the terminal is wide and a long series of short lines are printed;
|
||||
// the shaper can cache the few variations of trailing whitespace
|
||||
// and focus on shaping the shorter cluster sequences.
|
||||
if cell_str == " " {
|
||||
whitespace_run += 1;
|
||||
} else {
|
||||
whitespace_run = 0;
|
||||
only_whitespace = false;
|
||||
}
|
||||
if !only_whitespace && whitespace_run > 2 {
|
||||
clusters.push(last);
|
||||
|
||||
only_whitespace = cell_str == " ";
|
||||
whitespace_run = 1;
|
||||
Some(CellCluster::new(
|
||||
hint,
|
||||
normalized_attr.into_owned(),
|
||||
cell_str,
|
||||
cell_idx,
|
||||
))
|
||||
} else {
|
||||
last.add(cell_str, cell_idx);
|
||||
Some(last)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -51,22 +115,43 @@ impl CellCluster {
|
||||
}
|
||||
|
||||
/// Start off a new cluster with some initial data
|
||||
fn new(attrs: CellAttributes, text: &str, cell_idx: usize) -> CellCluster {
|
||||
fn new(hint: usize, attrs: CellAttributes, text: &str, cell_idx: usize) -> CellCluster {
|
||||
let mut idx = Vec::new();
|
||||
for _ in 0..text.len() {
|
||||
idx.push(cell_idx);
|
||||
if text.len() > 1 {
|
||||
// Prefer to avoid pushing any index data; this saves
|
||||
// allocating any storage until we have any cells that
|
||||
// are multibyte
|
||||
for _ in 0..text.len() {
|
||||
idx.push(cell_idx);
|
||||
}
|
||||
}
|
||||
let mut storage = String::with_capacity(hint);
|
||||
storage.push_str(text);
|
||||
|
||||
CellCluster {
|
||||
attrs,
|
||||
text: text.into(),
|
||||
text: storage,
|
||||
byte_to_cell_idx: idx,
|
||||
first_cell_idx: cell_idx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add to this cluster
|
||||
fn add(&mut self, text: &str, cell_idx: usize) {
|
||||
for _ in 0..text.len() {
|
||||
self.byte_to_cell_idx.push(cell_idx);
|
||||
if !self.byte_to_cell_idx.is_empty() {
|
||||
// We had at least one multi-byte cell in the past
|
||||
for _ in 0..text.len() {
|
||||
self.byte_to_cell_idx.push(cell_idx);
|
||||
}
|
||||
} else if text.len() > 1 {
|
||||
// Extrapolate the indices so far
|
||||
for n in 0..self.text.len() {
|
||||
self.byte_to_cell_idx.push(n + self.first_cell_idx);
|
||||
}
|
||||
// Now add this new multi-byte cell text
|
||||
for _ in 0..text.len() {
|
||||
self.byte_to_cell_idx.push(cell_idx);
|
||||
}
|
||||
}
|
||||
self.text.push_str(text);
|
||||
}
|
||||
|
@ -432,7 +432,7 @@ impl Line {
|
||||
}
|
||||
|
||||
pub fn cluster(&self) -> Vec<CellCluster> {
|
||||
CellCluster::make_cluster(self.visible_cells())
|
||||
CellCluster::make_cluster(self.cells.len(), self.visible_cells())
|
||||
}
|
||||
|
||||
pub fn cells(&self) -> &[Cell] {
|
||||
|
@ -365,6 +365,7 @@ impl FontShaper for HarfbuzzShaper {
|
||||
dpi: u32,
|
||||
no_glyphs: &mut Vec<char>,
|
||||
) -> anyhow::Result<Vec<GlyphInfo>> {
|
||||
log::trace!("shape {} `{}`", text.len(), text);
|
||||
let start = std::time::Instant::now();
|
||||
let result = self.do_shape(0, text, size, dpi, no_glyphs);
|
||||
metrics::histogram!("shape.harfbuzz", start.elapsed());
|
||||
|
@ -720,7 +720,13 @@ impl super::TermWindow {
|
||||
}
|
||||
|
||||
// Break the line into clusters of cells with the same attributes
|
||||
let start = Instant::now();
|
||||
let cell_clusters = params.line.cluster();
|
||||
log::trace!(
|
||||
"cluster -> {} clusters, elapsed {:?}",
|
||||
cell_clusters.len(),
|
||||
start.elapsed()
|
||||
);
|
||||
|
||||
let mut last_cell_idx = 0;
|
||||
|
||||
@ -811,6 +817,8 @@ impl super::TermWindow {
|
||||
);
|
||||
|
||||
// Shape the printable text from this cluster
|
||||
|
||||
let shape_resolve_start = Instant::now();
|
||||
let glyph_info = {
|
||||
let key = BorrowedShapeCacheKey {
|
||||
style,
|
||||
@ -858,9 +866,14 @@ impl super::TermWindow {
|
||||
}
|
||||
}
|
||||
};
|
||||
log::trace!(
|
||||
"shape_resolve for cluster len {} -> elapsed {:?}",
|
||||
cluster.text.len(),
|
||||
shape_resolve_start.elapsed()
|
||||
);
|
||||
|
||||
for info in glyph_info.iter() {
|
||||
let cell_idx = cluster.byte_to_cell_idx[info.pos.cluster as usize];
|
||||
let cell_idx = cluster.byte_to_cell_idx(info.pos.cluster as usize);
|
||||
let glyph = &info.glyph;
|
||||
|
||||
let top = ((PixelLength::new(self.render_metrics.cell_size.height as f64)
|
||||
@ -1015,6 +1028,7 @@ impl super::TermWindow {
|
||||
},
|
||||
);
|
||||
|
||||
let right_fill_start = Instant::now();
|
||||
for cell_idx in last_cell_idx + 1..num_cols {
|
||||
// Even though we don't have a cell for these, they still
|
||||
// hold the cursor or the selection so we need to compute
|
||||
@ -1062,6 +1076,11 @@ impl super::TermWindow {
|
||||
);
|
||||
quad.set_cursor_color(params.cursor_border_color);
|
||||
}
|
||||
log::trace!(
|
||||
"right fill {} -> elapsed {:?}",
|
||||
num_cols.saturating_sub(last_cell_idx),
|
||||
right_fill_start.elapsed()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1277,7 +1296,7 @@ impl super::TermWindow {
|
||||
) -> anyhow::Result<Vec<Rc<CachedGlyph<SrgbTexture2d>>>> {
|
||||
let mut glyphs = Vec::with_capacity(infos.len());
|
||||
for info in infos {
|
||||
let cell_idx = cluster.byte_to_cell_idx[info.cluster as usize];
|
||||
let cell_idx = cluster.byte_to_cell_idx(info.cluster as usize);
|
||||
let followed_by_space = match line.cells().get(cell_idx + 1) {
|
||||
Some(cell) => cell.str() == " ",
|
||||
None => false,
|
||||
|
Loading…
Reference in New Issue
Block a user