diff options
author | Christian Duerr <contact@christianduerr.com> | 2021-05-22 22:48:43 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-22 22:48:43 +0000 |
commit | 3c61e075fef7b02ae0d043e4a4e664b8bc7221e9 (patch) | |
tree | f1aa2b0bc18ddea72ef8b989d41fa9d915f6d300 /alacritty | |
parent | c17d8db16934fa2dbd667acea697cd0682826c80 (diff) | |
download | r-alacritty-3c61e075fef7b02ae0d043e4a4e664b8bc7221e9.tar.gz r-alacritty-3c61e075fef7b02ae0d043e4a4e664b8bc7221e9.tar.bz2 r-alacritty-3c61e075fef7b02ae0d043e4a4e664b8bc7221e9.zip |
Improve rendering performance
This PR combines a couple of optimizations to drastically reduce the
time it takes to gather everything necessary for rendering Alacritty's
terminal grid.
To help with the iteration over the grid, the `DisplayIter` which made
heavy use of dynamic dispatch has been replaced with a simple addition
to the `GridIterator` which also had the benefit of making the code a
little easier to understand.
The hints/search check for each cell was always performing an array
lookup before figuring out that the cell is not part of a hint or
search. Since the general case is that the cell is neither part of hints
or search, they've been wrapped in an `Option` to make verifying their
activity a simple `is_some()` check.
For some reason the compiler was also struggling with the `cursor`
method of the `RenderableContent`. Since the iterator is explicitly
drained, the performance took a hit of multiple milliseconds for a
single branch. Our implementation does never reach the case where
draining the iterator would be necessary, so this sanity check has just
been replaced with a `debug_assert`.
Overall this has managed to reduce the time it takes to collect all
renderable content from ~7-8ms in my large grid test to just ~3-4ms.
Diffstat (limited to 'alacritty')
-rw-r--r-- | alacritty/src/display/content.rs | 80 | ||||
-rw-r--r-- | alacritty/src/input.rs | 5 |
2 files changed, 44 insertions, 41 deletions
diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs index 4bef44f5..926ac7bd 100644 --- a/alacritty/src/display/content.rs +++ b/alacritty/src/display/content.rs @@ -11,9 +11,7 @@ use alacritty_terminal::index::{Column, Direction, Line, Point}; use alacritty_terminal::term::cell::{Cell, Flags}; use alacritty_terminal::term::color::{CellRgb, Rgb}; use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch}; -use alacritty_terminal::term::{ - RenderableContent as TerminalContent, RenderableCursor as TerminalCursor, Term, TermMode, -}; +use alacritty_terminal::term::{RenderableContent as TerminalContent, Term, TermMode}; use crate::config::ui_config::UiConfig; use crate::display::color::{List, DIM_FACTOR}; @@ -29,10 +27,11 @@ pub const MIN_CURSOR_CONTRAST: f64 = 1.5; /// This provides the terminal cursor and an iterator over all non-empty cells. pub struct RenderableContent<'a> { terminal_content: TerminalContent<'a>, - terminal_cursor: TerminalCursor, cursor: Option<RenderableCursor>, - search: Regex<'a>, - hint: Hint<'a>, + cursor_shape: CursorShape, + cursor_point: Point<usize>, + search: Option<Regex<'a>>, + hint: Option<Hint<'a>>, config: &'a Config<UiConfig>, colors: &'a List, focused_match: Option<&'a Match>, @@ -45,31 +44,41 @@ impl<'a> RenderableContent<'a> { term: &'a Term<T>, search_state: &'a SearchState, ) -> Self { - let search = search_state.dfas().map(|dfas| Regex::new(&term, dfas)).unwrap_or_default(); + let search = search_state.dfas().map(|dfas| Regex::new(&term, dfas)); let focused_match = search_state.focused_match(); let terminal_content = term.renderable_content(); - // Copy the cursor and override its shape if necessary. - let mut terminal_cursor = terminal_content.cursor; - - if terminal_cursor.shape == CursorShape::Hidden + // Find terminal cursor shape. + let cursor_shape = if terminal_content.cursor.shape == CursorShape::Hidden || display.cursor_hidden || search_state.regex().is_some() { - terminal_cursor.shape = CursorShape::Hidden; + CursorShape::Hidden } else if !term.is_focused && config.cursor.unfocused_hollow { - terminal_cursor.shape = CursorShape::HollowBlock; - } + CursorShape::HollowBlock + } else { + terminal_content.cursor.shape + }; + + // Convert terminal cursor point to viewport position. + let cursor_point = terminal_content.cursor.point; + let display_offset = terminal_content.display_offset; + let cursor_point = display::point_to_viewport(display_offset, cursor_point).unwrap(); - display.hint_state.update_matches(term); - let hint = Hint::from(&display.hint_state); + let hint = if display.hint_state.active() { + display.hint_state.update_matches(term); + Some(Hint::from(&display.hint_state)) + } else { + None + }; Self { colors: &display.colors, cursor: None, terminal_content, - terminal_cursor, focused_match, + cursor_shape, + cursor_point, search, config, hint, @@ -83,8 +92,8 @@ impl<'a> RenderableContent<'a> { /// Get the terminal cursor. pub fn cursor(mut self) -> Option<RenderableCursor> { - // Drain the iterator to make sure the cursor is created. - while self.next().is_some() && self.cursor.is_none() {} + // Assure this function is only called after the iterator has been drained. + debug_assert!(self.next().is_none()); self.cursor } @@ -98,7 +107,7 @@ impl<'a> RenderableContent<'a> { /// /// This will return `None` when there is no cursor visible. fn renderable_cursor(&mut self, cell: &RenderableCell) -> Option<RenderableCursor> { - if self.terminal_cursor.shape == CursorShape::Hidden { + if self.cursor_shape == CursorShape::Hidden { return None; } @@ -125,17 +134,12 @@ impl<'a> RenderableContent<'a> { let text_color = text_color.color(cell.fg, cell.bg); let cursor_color = cursor_color.color(cell.fg, cell.bg); - // Convert cursor point to viewport position. - let cursor_point = self.terminal_cursor.point; - let display_offset = self.terminal_content.display_offset; - let point = display::point_to_viewport(display_offset, cursor_point).unwrap(); - Some(RenderableCursor { is_wide: cell.flags.contains(Flags::WIDE_CHAR), - shape: self.terminal_cursor.shape, + shape: self.cursor_shape, + point: self.cursor_point, cursor_color, text_color, - point, }) } } @@ -151,10 +155,9 @@ impl<'a> Iterator for RenderableContent<'a> { fn next(&mut self) -> Option<Self::Item> { loop { let cell = self.terminal_content.display_iter.next()?; - let cell_point = cell.point; let mut cell = RenderableCell::new(self, cell); - if self.terminal_cursor.point == cell_point { + if self.cursor_point == cell.point { // Store the cursor which should be rendered. self.cursor = self.renderable_cursor(&cell).map(|cursor| { if cursor.shape == CursorShape::Block { @@ -203,17 +206,22 @@ impl RenderableCell { Self::compute_bg_alpha(cell.bg) }; - let is_selected = content - .terminal_content - .selection - .map_or(false, |selection| selection.contains_cell(&cell, content.terminal_cursor)); + let is_selected = content.terminal_content.selection.map_or(false, |selection| { + selection.contains_cell( + &cell, + content.terminal_content.cursor.point, + content.cursor_shape, + ) + }); let display_offset = content.terminal_content.display_offset; let viewport_start = Point::new(Line(-(display_offset as i32)), Column(0)); let colors = &content.config.ui_config.colors; let mut character = cell.c; - if let Some((c, is_first)) = content.hint.advance(viewport_start, cell.point) { + if let Some((c, is_first)) = + content.hint.as_mut().and_then(|hint| hint.advance(viewport_start, cell.point)) + { let (config_fg, config_bg) = if is_first { (colors.hints.start.foreground, colors.hints.start.background) } else { @@ -233,7 +241,7 @@ impl RenderableCell { bg = content.color(NamedColor::Foreground as usize); bg_alpha = 1.0; } - } else if content.search.advance(cell.point) { + } else if content.search.as_mut().map_or(false, |search| search.advance(cell.point)) { let focused = content.focused_match.map_or(false, |fm| fm.contains(&cell.point)); let (config_fg, config_bg) = if focused { (colors.search.focused_match.foreground, colors.search.focused_match.background) @@ -261,9 +269,9 @@ impl RenderableCell { /// Check if cell contains any renderable content. fn is_empty(&self) -> bool { self.bg_alpha == 0. - && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE) && self.character == ' ' && self.zerowidth.is_none() + && !self.flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT | Flags::DOUBLE_UNDERLINE) } /// Apply [`CellRgb`] colors to the cell's colors. diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs index 3559b85e..948ec67f 100644 --- a/alacritty/src/input.rs +++ b/alacritty/src/input.rs @@ -996,7 +996,6 @@ mod tests { use glutin::event::{Event as GlutinEvent, VirtualKeyCode, WindowEvent}; use alacritty_terminal::event::Event as TerminalEvent; - use alacritty_terminal::selection::Selection; use crate::config::Binding; use crate::message_bar::MessageBuffer; @@ -1008,7 +1007,6 @@ mod tests { struct ActionContext<'a, T> { pub terminal: &'a mut Term<T>, - pub selection: &'a mut Option<Selection>, pub size_info: &'a SizeInfo, pub mouse: &'a mut Mouse, pub clipboard: &'a mut Clipboard, @@ -1145,13 +1143,10 @@ mod tests { ..Mouse::default() }; - let mut selection = None; - let mut message_buffer = MessageBuffer::new(); let context = ActionContext { terminal: &mut terminal, - selection: &mut selection, mouse: &mut mouse, size_info: &size, clipboard: &mut clipboard, |