aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/display/mod.rs
diff options
context:
space:
mode:
authorKirill Chibisov <contact@kchibisov.com>2022-02-02 00:12:58 +0300
committerGitHub <noreply@github.com>2022-02-02 00:12:58 +0300
commit8f1abe13e6b80da181ee856e6d5a19c7731dbedc (patch)
treeafab9579c3fb1019cdda9fb7d006a51ebcd929d6 /alacritty/src/display/mod.rs
parentd58dff18effc204d7fc9f05dac9d0b25be26ee1a (diff)
downloadr-alacritty-8f1abe13e6b80da181ee856e6d5a19c7731dbedc.tar.gz
r-alacritty-8f1abe13e6b80da181ee856e6d5a19c7731dbedc.tar.bz2
r-alacritty-8f1abe13e6b80da181ee856e6d5a19c7731dbedc.zip
Add damage tracking and reporting to compatible compositors
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 #3186.
Diffstat (limited to 'alacritty/src/display/mod.rs')
-rw-r--r--alacritty/src/display/mod.rs185
1 files changed, 170 insertions, 15 deletions
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs
index d9ec8593..7d53e678 100644
--- a/alacritty/src/display/mod.rs
+++ b/alacritty/src/display/mod.rs
@@ -1,13 +1,12 @@
//! The display subsystem including window management, font rasterization, and
//! GPU drawing.
-use std::cmp::min;
use std::convert::TryFrom;
use std::fmt::{self, Formatter};
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
use std::sync::atomic::Ordering;
use std::time::Instant;
-use std::{f64, mem};
+use std::{cmp, mem};
use glutin::dpi::PhysicalSize;
use glutin::event::ModifiersState;
@@ -15,6 +14,7 @@ use glutin::event_loop::EventLoopWindowTarget;
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
use glutin::platform::unix::EventLoopWindowTargetExtUnix;
use glutin::window::CursorIcon;
+use glutin::Rect as DamageRect;
use log::{debug, info};
use parking_lot::MutexGuard;
use unicode_width::UnicodeWidthChar;
@@ -24,12 +24,16 @@ use wayland_client::EventQueue;
use crossfont::{self, Rasterize, Rasterizer};
use alacritty_terminal::ansi::NamedColor;
+use alacritty_terminal::config::MAX_SCROLLBACK_LINES;
use alacritty_terminal::event::{EventListener, OnResize};
use alacritty_terminal::grid::Dimensions as _;
use alacritty_terminal::index::{Column, Direction, Line, Point};
-use alacritty_terminal::selection::Selection;
+use alacritty_terminal::selection::{Selection, SelectionRange};
use alacritty_terminal::term::cell::Flags;
-use alacritty_terminal::term::{SizeInfo, Term, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES};
+use alacritty_terminal::term::color::Rgb;
+use alacritty_terminal::term::{
+ SizeInfo, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES,
+};
use crate::config::font::Font;
#[cfg(not(windows))]
@@ -40,6 +44,7 @@ use crate::display::bell::VisualBell;
use crate::display::color::List;
use crate::display::content::RenderableContent;
use crate::display::cursor::IntoRects;
+use crate::display::damage::RenderDamageIterator;
use crate::display::hint::{HintMatch, HintState};
use crate::display::meter::Meter;
use crate::display::window::Window;
@@ -55,6 +60,7 @@ pub mod window;
mod bell;
mod color;
+mod damage;
mod meter;
/// Maximum number of linewraps followed outside of the viewport during search highlighting.
@@ -66,6 +72,9 @@ const FORWARD_SEARCH_LABEL: &str = "Search: ";
/// Label for the backward terminal search bar.
const BACKWARD_SEARCH_LABEL: &str = "Backward Search: ";
+/// Color which is used to highlight damaged rects when debugging.
+const DAMAGE_RECT_COLOR: Rgb = Rgb { r: 255, g: 0, b: 255 };
+
#[derive(Debug)]
pub enum Error {
/// Error with window management.
@@ -193,6 +202,9 @@ pub struct Display {
/// Unprocessed display updates.
pub pending_update: DisplayUpdate,
+ is_damage_supported: bool,
+ debug_damage: bool,
+ damage_rects: Vec<DamageRect>,
renderer: QuadRenderer,
glyph_cache: GlyphCache,
meter: Meter,
@@ -319,6 +331,13 @@ impl Display {
}
let hint_state = HintState::new(config.hints.alphabet());
+ let is_damage_supported = window.swap_buffers_with_damage_supported();
+ let debug_damage = config.debug.highlight_damage;
+ let damage_rects = if is_damage_supported || debug_damage {
+ Vec::with_capacity(size_info.screen_lines())
+ } else {
+ Vec::new()
+ };
Ok(Self {
window,
@@ -335,6 +354,9 @@ impl Display {
visual_bell: VisualBell::from(&config.bell),
colors: List::from(&config.colors),
pending_update: Default::default(),
+ is_damage_supported,
+ debug_damage,
+ damage_rects,
})
}
@@ -457,10 +479,58 @@ impl Display {
self.window.resize(physical);
self.renderer.resize(&self.size_info);
+ if self.collect_damage() {
+ let lines = self.size_info.screen_lines();
+ if lines > self.damage_rects.len() {
+ self.damage_rects.reserve(lines);
+ } else {
+ self.damage_rects.shrink_to(lines);
+ }
+ }
+
info!("Padding: {} x {}", self.size_info.padding_x(), self.size_info.padding_y());
info!("Width: {}, Height: {}", self.size_info.width(), self.size_info.height());
}
+ fn update_damage<T: EventListener>(
+ &mut self,
+ terminal: &mut MutexGuard<'_, Term<T>>,
+ selection_range: Option<SelectionRange>,
+ search_state: &SearchState,
+ ) {
+ let requires_full_damage = self.visual_bell.intensity() != 0.
+ || self.hint_state.active()
+ || search_state.regex().is_some();
+ if requires_full_damage {
+ terminal.mark_fully_damaged();
+ }
+
+ self.damage_highlighted_hints(terminal);
+ let size_info: SizeInfo<u32> = self.size_info.into();
+ match terminal.damage(selection_range) {
+ TermDamage::Full => {
+ let screen_rect =
+ DamageRect { x: 0, y: 0, width: size_info.width(), height: size_info.height() };
+ self.damage_rects.push(screen_rect);
+ },
+ TermDamage::Partial(damaged_lines) => {
+ let damaged_rects = RenderDamageIterator::new(damaged_lines, size_info);
+ for damaged_rect in damaged_rects {
+ self.damage_rects.push(damaged_rect);
+ }
+ },
+ }
+ terminal.reset_damage();
+
+ // Ensure that the content requiring full damage is cleaned up again on the next frame.
+ if requires_full_damage {
+ terminal.mark_fully_damaged();
+ }
+
+ // Damage highlighted hints for the next frame as well, so we'll clear them.
+ self.damage_highlighted_hints(terminal);
+ }
+
/// Draw the screen.
///
/// A reference to Term whose state is being drawn must be provided.
@@ -468,7 +538,7 @@ impl Display {
/// This call may block if vsync is enabled.
pub fn draw<T: EventListener>(
&mut self,
- terminal: MutexGuard<'_, Term<T>>,
+ mut terminal: MutexGuard<'_, Term<T>>,
message_buffer: &MessageBuffer,
config: &UiConfig,
search_state: &SearchState,
@@ -479,6 +549,7 @@ impl Display {
for cell in &mut content {
grid_cells.push(cell);
}
+ let selection_range = content.selection_range();
let background_color = content.color(NamedColor::Background as usize);
let display_offset = content.display_offset();
let cursor = content.cursor();
@@ -491,6 +562,11 @@ impl Display {
let vi_mode = terminal.mode().contains(TermMode::VI);
let vi_mode_cursor = if vi_mode { Some(terminal.vi_mode_cursor) } else { None };
+ if self.collect_damage() {
+ self.damage_rects.clear();
+ self.update_damage(&mut terminal, selection_range, search_state);
+ }
+
// Drop terminal as early as possible to free lock.
drop(terminal);
@@ -549,11 +625,9 @@ impl Display {
self.draw_line_indicator(config, &size_info, total_lines, None, display_offset);
}
- // Push the cursor rects for rendering.
- if let Some(cursor) = cursor {
- for rect in cursor.rects(&size_info, config.terminal_config.cursor.thickness()) {
- rects.push(rect);
- }
+ // Draw cursor.
+ for rect in cursor.rects(&size_info, config.terminal_config.cursor.thickness()) {
+ rects.push(rect);
}
// Push visual bell after url/underline/strikeout rects.
@@ -570,6 +644,10 @@ impl Display {
rects.push(visual_bell_rect);
}
+ if self.debug_damage {
+ self.highlight_damage(&mut rects);
+ }
+
if let Some(message) = message_buffer.message() {
let search_offset = if search_state.regex().is_some() { 1 } else { 0 };
let text = message.text(&size_info);
@@ -636,7 +714,12 @@ impl Display {
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
self.request_frame(&self.window);
- self.window.swap_buffers();
+ // Clearing debug highlights from the previous frame requires full redraw.
+ if self.is_damage_supported && !self.debug_damage {
+ self.window.swap_buffers_with_damage(&self.damage_rects);
+ } else {
+ self.window.swap_buffers();
+ }
#[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))]
if self.is_x11 {
@@ -651,6 +734,7 @@ impl Display {
/// Update to a new configuration.
pub fn update_config(&mut self, config: &UiConfig) {
+ self.debug_damage = config.debug.highlight_damage;
self.visual_bell.update_config(&config.bell);
self.colors = List::from(&config.colors);
}
@@ -722,7 +806,7 @@ impl Display {
let num_cols = size_info.columns();
let label_len = search_label.chars().count();
let regex_len = formatted_regex.chars().count();
- let truncate_len = min((regex_len + label_len).saturating_sub(num_cols), regex_len);
+ let truncate_len = cmp::min((regex_len + label_len).saturating_sub(num_cols), regex_len);
let index = formatted_regex.char_indices().nth(truncate_len).map(|(i, _c)| i).unwrap_or(0);
let truncated_regex = &formatted_regex[index..];
@@ -758,13 +842,15 @@ impl Display {
return;
}
- let glyph_cache = &mut self.glyph_cache;
-
let timing = format!("{:.3} usec", self.meter.average());
let point = Point::new(size_info.screen_lines().saturating_sub(2), Column(0));
let fg = config.colors.primary.background;
let bg = config.colors.normal.red;
+ // Damage the entire line.
+ self.damage_from_point(point, self.size_info.columns() as u32);
+
+ let glyph_cache = &mut self.glyph_cache;
self.renderer.with_api(config, size_info, |mut api| {
api.draw_string(glyph_cache, point, fg, bg, &timing);
});
@@ -779,8 +865,26 @@ impl Display {
obstructed_column: Option<Column>,
line: usize,
) {
+ const fn num_digits(mut number: u32) -> usize {
+ let mut res = 0;
+ loop {
+ number /= 10;
+ res += 1;
+ if number == 0 {
+ break res;
+ }
+ }
+ }
+
let text = format!("[{}/{}]", line, total_lines - 1);
let column = Column(size_info.columns().saturating_sub(text.len()));
+ let point = Point::new(0, column);
+
+ // Damage the maximum possible length of the format text, which could be achieved when
+ // using `MAX_SCROLLBACK_LINES` as current and total lines adding a `3` for formatting.
+ const MAX_LEN: usize = num_digits(MAX_SCROLLBACK_LINES) + 3;
+ self.damage_from_point(Point::new(0, point.column - MAX_LEN), MAX_LEN as u32 * 2);
+
let colors = &config.colors;
let fg = colors.line_indicator.foreground.unwrap_or(colors.primary.background);
let bg = colors.line_indicator.background.unwrap_or(colors.primary.foreground);
@@ -789,11 +893,60 @@ impl Display {
if obstructed_column.map_or(true, |obstructed_column| obstructed_column < column) {
let glyph_cache = &mut self.glyph_cache;
self.renderer.with_api(config, size_info, |mut api| {
- api.draw_string(glyph_cache, Point::new(0, column), fg, bg, &text);
+ api.draw_string(glyph_cache, point, fg, bg, &text);
});
}
}
+ /// Damage `len` starting from a `point`.
+ #[inline]
+ fn damage_from_point(&mut self, point: Point<usize>, len: u32) {
+ if !self.collect_damage() {
+ return;
+ }
+
+ let size_info: SizeInfo<u32> = self.size_info.into();
+ let x = size_info.padding_x() + point.column.0 as u32 * size_info.cell_width();
+ let y_top = size_info.height() - size_info.padding_y();
+ let y = y_top - (point.line as u32 + 1) * size_info.cell_height();
+ let width = len as u32 * size_info.cell_width();
+ self.damage_rects.push(DamageRect { x, y, width, height: size_info.cell_height() })
+ }
+
+ /// Damage currently highlighted `Display` hints.
+ #[inline]
+ fn damage_highlighted_hints<T: EventListener>(&self, terminal: &mut Term<T>) {
+ let display_offset = terminal.grid().display_offset();
+ for hint in self.highlighted_hint.iter().chain(&self.vi_highlighted_hint) {
+ for point in (hint.bounds.start().line.0..=hint.bounds.end().line.0).flat_map(|line| {
+ point_to_viewport(display_offset, Point::new(Line(line), Column(0)))
+ }) {
+ terminal.damage_line(point.line, 0, terminal.columns() - 1);
+ }
+ }
+ }
+
+ /// Returns `true` if damage information should be collected, `false` otherwise.
+ #[inline]
+ fn collect_damage(&self) -> bool {
+ self.is_damage_supported || self.debug_damage
+ }
+
+ /// Highlight damaged rects.
+ ///
+ /// This function is for debug purposes only.
+ fn highlight_damage(&self, render_rects: &mut Vec<RenderRect>) {
+ for damage_rect in &self.damage_rects {
+ let x = damage_rect.x as f32;
+ let height = damage_rect.height as f32;
+ let width = damage_rect.width as f32;
+ let y = self.size_info.height() - damage_rect.y as f32 - height;
+ let render_rect = RenderRect::new(x, y, width, height, DAMAGE_RECT_COLOR, 0.5);
+
+ render_rects.push(render_rect);
+ }
+ }
+
/// Requst a new frame for a window on Wayland.
#[inline]
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
@@ -824,12 +977,14 @@ impl Drop for Display {
}
/// Convert a terminal point to a viewport relative point.
+#[inline]
pub fn point_to_viewport(display_offset: usize, point: Point) -> Option<Point<usize>> {
let viewport_line = point.line.0 + display_offset as i32;
usize::try_from(viewport_line).ok().map(|line| Point::new(line, point.column))
}
/// Convert a viewport relative point to a terminal point.
+#[inline]
pub fn viewport_to_point(display_offset: usize, point: Point<usize>) -> Point {
let line = Line(point.line as i32) - display_offset;
Point::new(line, point.column)