Skip to content

Commit

Permalink
Add damage tracking and reporting to compatible compositors
Browse files Browse the repository at this point in the history
This allows compositors to only process damaged (that is, updated)
regions of our window buffer, which for larger window sizes (think 4k)
should significantly reduce compositing workload under compositors that
support/honor it, which is good for performance, battery life and lower
latency over remote connections like VNC.

On Wayland, clients are expected to always report correct damage, so
this makes us a good citizen there. It can also aid remote desktop
(waypipe, rdp, vnc, ...) and other types of screencopy by having damage
bubble up correctly.

Fixes alacritty#3186.
  • Loading branch information
kchibisov authored Feb 1, 2022
1 parent d58dff1 commit 8f1abe1
Show file tree
Hide file tree
Showing 19 changed files with 991 additions and 135 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Added

- Option `font.builtin_box_drawing` to disable the built-in font for drawing box characters
- Option `font.builtin_box_drawing` to disable the built-in font for drawing box characters
- Track and report surface damage information to Wayland compositors

### Changed

Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions alacritty.yml
Original file line number Diff line number Diff line change
Expand Up @@ -876,3 +876,6 @@

# Print all received window events.
#print_events: false

# Highlight window damage information.
#highlight_damage: false
2 changes: 1 addition & 1 deletion alacritty/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ rust-version = "1.56.0"

[dependencies.alacritty_terminal]
path = "../alacritty_terminal"
version = "0.16.1-dev"
version = "0.17.0-dev"
default-features = false

[dependencies.alacritty_config_derive]
Expand Down
4 changes: 4 additions & 0 deletions alacritty/src/config/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub struct Debug {
/// Should show render timer.
pub render_timer: bool,

/// Highlight damage information produced by alacritty.
pub highlight_damage: bool,

/// Record ref test.
#[config(skip)]
pub ref_test: bool,
Expand All @@ -27,6 +30,7 @@ impl Default for Debug {
print_events: Default::default(),
persistent_logging: Default::default(),
render_timer: Default::default(),
highlight_damage: Default::default(),
ref_test: Default::default(),
}
}
Expand Down
53 changes: 31 additions & 22 deletions alacritty/src/display/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use alacritty_terminal::ansi::{Color, CursorShape, NamedColor};
use alacritty_terminal::event::EventListener;
use alacritty_terminal::grid::{Dimensions, Indexed};
use alacritty_terminal::index::{Column, Direction, Line, Point};
use alacritty_terminal::selection::SelectionRange;
use alacritty_terminal::term::cell::{Cell, Flags};
use alacritty_terminal::term::color::{CellRgb, Rgb};
use alacritty_terminal::term::search::{Match, RegexIter, RegexSearch};
Expand All @@ -26,7 +27,7 @@ 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>,
cursor: Option<RenderableCursor>,
cursor: RenderableCursor,
cursor_shape: CursorShape,
cursor_point: Point<usize>,
search: Option<Regex<'a>>,
Expand Down Expand Up @@ -73,7 +74,7 @@ impl<'a> RenderableContent<'a> {

Self {
colors: &display.colors,
cursor: None,
cursor: RenderableCursor::new_hidden(),
terminal_content,
focused_match,
cursor_shape,
Expand All @@ -90,7 +91,7 @@ impl<'a> RenderableContent<'a> {
}

/// Get the terminal cursor.
pub fn cursor(mut self) -> Option<RenderableCursor> {
pub fn cursor(mut self) -> RenderableCursor {
// Assure this function is only called after the iterator has been drained.
debug_assert!(self.next().is_none());

Expand All @@ -102,14 +103,14 @@ impl<'a> RenderableContent<'a> {
self.terminal_content.colors[color].unwrap_or(self.colors[color])
}

pub fn selection_range(&self) -> Option<SelectionRange> {
self.terminal_content.selection
}

/// Assemble the information required to render the terminal cursor.
///
/// This will return `None` when there is no cursor visible.
fn renderable_cursor(&mut self, cell: &RenderableCell) -> Option<RenderableCursor> {
if self.cursor_shape == CursorShape::Hidden {
return None;
}

fn renderable_cursor(&mut self, cell: &RenderableCell) -> RenderableCursor {
// Cursor colors.
let color = if self.terminal_content.mode.contains(TermMode::VI) {
self.config.colors.vi_mode_cursor
Expand All @@ -134,13 +135,13 @@ impl<'a> RenderableContent<'a> {
text_color = self.config.colors.primary.background;
}

Some(RenderableCursor {
RenderableCursor {
is_wide: cell.flags.contains(Flags::WIDE_CHAR),
shape: self.cursor_shape,
point: self.cursor_point,
cursor_color,
text_color,
})
}
}
}

Expand All @@ -159,18 +160,15 @@ impl<'a> Iterator for RenderableContent<'a> {

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 {
cell.fg = cursor.text_color;
cell.bg = cursor.cursor_color;

// Since we draw Block cursor by drawing cell below it with a proper color,
// we must adjust alpha to make it visible.
cell.bg_alpha = 1.;
}

cursor
});
self.cursor = self.renderable_cursor(&cell);
if self.cursor.shape == CursorShape::Block {
cell.fg = self.cursor.text_color;
cell.bg = self.cursor.cursor_color;

// Since we draw Block cursor by drawing cell below it with a proper color,
// we must adjust alpha to make it visible.
cell.bg_alpha = 1.;
}

return Some(cell);
} else if !cell.is_empty() && !cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
Expand Down Expand Up @@ -371,6 +369,17 @@ pub struct RenderableCursor {
point: Point<usize>,
}

impl RenderableCursor {
fn new_hidden() -> Self {
let shape = CursorShape::Hidden;
let cursor_color = Rgb::default();
let text_color = Rgb::default();
let is_wide = false;
let point = Point::default();
Self { shape, cursor_color, text_color, is_wide, point }
}
}

impl RenderableCursor {
pub fn color(&self) -> Rgb {
self.cursor_color
Expand Down
86 changes: 86 additions & 0 deletions alacritty/src/display/damage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::cmp;
use std::iter::Peekable;

use glutin::Rect;

use alacritty_terminal::term::{LineDamageBounds, SizeInfo, TermDamageIterator};

/// Iterator which converts `alacritty_terminal` damage information into renderer damaged rects.
pub struct RenderDamageIterator<'a> {
damaged_lines: Peekable<TermDamageIterator<'a>>,
size_info: SizeInfo<u32>,
}

impl<'a> RenderDamageIterator<'a> {
pub fn new(damaged_lines: TermDamageIterator<'a>, size_info: SizeInfo<u32>) -> Self {
Self { damaged_lines: damaged_lines.peekable(), size_info }
}

#[inline]
fn rect_for_line(&self, line_damage: LineDamageBounds) -> Rect {
let size_info = &self.size_info;
let y_top = size_info.height() - size_info.padding_y();
let x = size_info.padding_x() + line_damage.left as u32 * size_info.cell_width();
let y = y_top - (line_damage.line + 1) as u32 * size_info.cell_height();
let width = (line_damage.right - line_damage.left + 1) as u32 * size_info.cell_width();
Rect { x, y, height: size_info.cell_height(), width }
}

// Make sure to damage near cells to include wide chars.
#[inline]
fn overdamage(&self, mut rect: Rect) -> Rect {
let size_info = &self.size_info;
rect.x = rect.x.saturating_sub(size_info.cell_width());
rect.width = cmp::min(size_info.width() - rect.x, rect.width + 2 * size_info.cell_width());
rect.y = rect.y.saturating_sub(size_info.cell_height() / 2);
rect.height = cmp::min(size_info.height() - rect.y, rect.height + size_info.cell_height());

rect
}
}

impl<'a> Iterator for RenderDamageIterator<'a> {
type Item = Rect;

fn next(&mut self) -> Option<Rect> {
let line = self.damaged_lines.next()?;
let mut total_damage_rect = self.overdamage(self.rect_for_line(line));

// Merge rectangles which overlap with each other.
while let Some(line) = self.damaged_lines.peek().copied() {
let next_rect = self.overdamage(self.rect_for_line(line));
if !rects_overlap(total_damage_rect, next_rect) {
break;
}

total_damage_rect = merge_rects(total_damage_rect, next_rect);
let _ = self.damaged_lines.next();
}

Some(total_damage_rect)
}
}

/// Check if two given [`glutin::Rect`] overlap.
fn rects_overlap(lhs: Rect, rhs: Rect) -> bool {
!(
// `lhs` is left of `rhs`.
lhs.x + lhs.width < rhs.x
// `lhs` is right of `rhs`.
|| rhs.x + rhs.width < lhs.x
// `lhs` is below `rhs`.
|| lhs.y + lhs.height < rhs.y
// `lhs` is above `rhs`.
|| rhs.y + rhs.height < lhs.y
)
}

/// Merge two [`glutin::Rect`] by producing the smallest rectangle that contains both.
#[inline]
fn merge_rects(lhs: Rect, rhs: Rect) -> Rect {
let left_x = cmp::min(lhs.x, rhs.x);
let right_x = cmp::max(lhs.x + lhs.width, rhs.x + rhs.width);
let y_top = cmp::max(lhs.y + lhs.height, rhs.y + rhs.height);
let y_bottom = cmp::min(lhs.y, rhs.y);
Rect { x: left_x, y: y_bottom, width: right_x - left_x, height: y_top - y_bottom }
}
2 changes: 1 addition & 1 deletion alacritty/src/display/meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub struct Meter {
/// Average sample time in microseconds.
avg: f64,

/// Index of next time to update..
/// Index of next time to update.
index: usize,
}

Expand Down
Loading

0 comments on commit 8f1abe1

Please sign in to comment.