blob: a022216fb34708a87ec2e6db134285cf31a4c4bb [file]
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The display subsystem including window management, font rasterization, and
//! GPU drawing.
use std::sync::mpsc;
use parking_lot::{MutexGuard};
use Rgb;
use cli;
use config::Config;
use font::{self, Rasterize, Metrics};
use meter::Meter;
use renderer::{self, GlyphCache, QuadRenderer, Rect};
use selection::Selection;
use term::{cell, Term, SizeInfo, RenderableCell};
use window::{self, Size, Pixels, Window, SetInnerSize};
#[derive(Debug)]
pub enum Error {
/// Error with window management
Window(window::Error),
/// Error dealing with fonts
Font(font::Error),
/// Error in renderer
Render(renderer::Error),
}
impl ::std::error::Error for Error {
fn cause(&self) -> Option<&::std::error::Error> {
match *self {
Error::Window(ref err) => Some(err),
Error::Font(ref err) => Some(err),
Error::Render(ref err) => Some(err),
}
}
fn description(&self) -> &str {
match *self {
Error::Window(ref err) => err.description(),
Error::Font(ref err) => err.description(),
Error::Render(ref err) => err.description(),
}
}
}
impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Error::Window(ref err) => err.fmt(f),
Error::Font(ref err) => err.fmt(f),
Error::Render(ref err) => err.fmt(f),
}
}
}
impl From<window::Error> for Error {
fn from(val: window::Error) -> Error {
Error::Window(val)
}
}
impl From<font::Error> for Error {
fn from(val: font::Error) -> Error {
Error::Font(val)
}
}
impl From<renderer::Error> for Error {
fn from(val: renderer::Error) -> Error {
Error::Render(val)
}
}
/// The display wraps a window, font rasterizer, and GPU renderer
pub struct Display {
window: Window,
renderer: QuadRenderer,
glyph_cache: GlyphCache,
render_timer: bool,
rx: mpsc::Receiver<(u32, u32)>,
tx: mpsc::Sender<(u32, u32)>,
meter: Meter,
font_size: font::Size,
size_info: SizeInfo,
last_background_color: Rgb,
}
/// Can wakeup the render loop from other threads
pub struct Notifier(window::Proxy);
/// Types that are interested in when the display is resized
pub trait OnResize {
fn on_resize(&mut self, size: &SizeInfo);
}
impl Notifier {
pub fn notify(&self) {
self.0.wakeup_event_loop();
}
}
impl Display {
pub fn notifier(&self) -> Notifier {
Notifier(self.window.create_window_proxy())
}
pub fn update_config(&mut self, config: &Config) {
self.render_timer = config.render_timer();
}
/// Get size info about the display
pub fn size(&self) -> &SizeInfo {
&self.size_info
}
pub fn new(
config: &Config,
options: &cli::Options,
) -> Result<Display, Error> {
// Extract some properties from config
let render_timer = config.render_timer();
// Create the window where Alacritty will be displayed
let mut window = Window::new(&options.title, config.window())?;
// get window properties for initializing the other subsystems
let mut viewport_size = window.inner_size_pixels()
.expect("glutin returns window size");
let dpr = window.hidpi_factor();
info!("device_pixel_ratio: {}", dpr);
// Create renderer
let mut renderer = QuadRenderer::new(config, viewport_size)?;
let (glyph_cache, cell_width, cell_height) =
Self::new_glyph_cache(&window, &mut renderer, config)?;
let dimensions = options.dimensions()
.unwrap_or_else(|| config.dimensions());
// Resize window to specified dimensions unless one or both dimensions are 0
if dimensions.columns_u32() > 0 && dimensions.lines_u32() > 0 {
let width = cell_width as u32 * dimensions.columns_u32();
let height = cell_height as u32 * dimensions.lines_u32();
let new_viewport_size = Size {
width: Pixels(width + 2 * config.padding().x as u32),
height: Pixels(height + 2 * config.padding().y as u32),
};
window.set_inner_size(&new_viewport_size);
renderer.resize(new_viewport_size.width.0 as _, new_viewport_size.height.0 as _);
viewport_size = new_viewport_size
}
info!("Cell Size: ({} x {})", cell_width, cell_height);
let size_info = SizeInfo {
width: viewport_size.width.0 as f32,
height: viewport_size.height.0 as f32,
cell_width: cell_width as f32,
cell_height: cell_height as f32,
padding_x: config.padding().x.floor(),
padding_y: config.padding().y.floor(),
};
// Channel for resize events
//
// macOS has a callback for getting resize events, the channel is used
// to queue resize events until the next draw call. Unfortunately, it
// seems that the event loop is blocked until the window is done
// resizing. If any drawing were to happen during a resize, it would
// need to be in the callback.
let (tx, rx) = mpsc::channel();
// Clear screen
let background_color = config.colors().primary.background;
renderer.with_api(config, &size_info, |api| {
api.clear(background_color);
});
Ok(Display {
window: window,
renderer: renderer,
glyph_cache: glyph_cache,
render_timer: render_timer,
tx: tx,
rx: rx,
meter: Meter::new(),
font_size: font::Size::new(0.),
size_info: size_info,
last_background_color: background_color,
})
}
fn new_glyph_cache(window : &Window, renderer : &mut QuadRenderer, config: &Config)
-> Result<(GlyphCache, f32, f32), Error>
{
let font = config.font().clone();
let dpr = window.hidpi_factor();
let rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?;
// Initialize glyph cache
let glyph_cache = {
info!("Initializing glyph cache");
let init_start = ::std::time::Instant::now();
let cache = renderer.with_loader(|mut api| {
GlyphCache::new(rasterizer, &font, &mut api)
})?;
let stop = init_start.elapsed();
let stop_f = stop.as_secs() as f64 + f64::from(stop.subsec_nanos()) / 1_000_000_000f64;
info!("Finished initializing glyph cache in {}", stop_f);
cache
};
// Need font metrics to resize the window properly. This suggests to me the
// font metrics should be computed before creating the window in the first
// place so that a resize is not needed.
let metrics = glyph_cache.font_metrics();
let cell_width = (metrics.average_advance + f64::from(font.offset().x)) as u32;
let cell_height = (metrics.line_height + f64::from(font.offset().y)) as u32;
Ok((glyph_cache, cell_width as f32, cell_height as f32))
}
pub fn update_glyph_cache(&mut self, config: &Config) {
let cache = &mut self.glyph_cache;
let size = self.font_size;
self.renderer.with_loader(|mut api| {
let _ = cache.update_font_size(config.font(), size, &mut api);
});
let metrics = cache.font_metrics();
self.size_info.cell_width = ((metrics.average_advance + f64::from(config.font().offset().x)) as f32).floor();
self.size_info.cell_height = ((metrics.line_height + f64::from(config.font().offset().y)) as f32).floor();
}
#[inline]
pub fn resize_channel(&self) -> mpsc::Sender<(u32, u32)> {
self.tx.clone()
}
pub fn window(&mut self) -> &mut Window {
&mut self.window
}
/// Process pending resize events
pub fn handle_resize(
&mut self,
terminal: &mut MutexGuard<Term>,
config: &Config,
items: &mut [&mut OnResize]
) {
// Resize events new_size and are handled outside the poll_events
// iterator. This has the effect of coalescing multiple resize
// events into one.
let mut new_size = None;
// Take most recent resize event, if any
while let Ok(sz) = self.rx.try_recv() {
new_size = Some(sz);
}
// Font size modification detected
if terminal.font_size != self.font_size {
self.font_size = terminal.font_size;
self.update_glyph_cache(config);
if new_size == None {
// Force a resize to refresh things
new_size = Some((self.size_info.width as u32,
self.size_info.height as u32));
}
}
// Receive any resize events; only call gl::Viewport on last
// available
if let Some((w, h)) = new_size.take() {
self.size_info.width = w as f32;
self.size_info.height = h as f32;
let size = &self.size_info;
terminal.resize(size);
for item in items {
item.on_resize(size)
}
self.window.resize(w, h);
self.renderer.resize(w as i32, h as i32);
}
}
/// Draw the screen
///
/// A reference to Term whose state is being drawn must be provided.
///
/// This call may block if vsync is enabled
pub fn draw(&mut self, mut terminal: MutexGuard<Term>, config: &Config, selection: Option<&Selection>) {
// Clear dirty flag
terminal.dirty = !terminal.visual_bell.completed();
if let Some(title) = terminal.get_next_title() {
self.window.set_title(&title);
}
if let Some(mouse_cursor) = terminal.get_next_mouse_cursor() {
self.window.set_mouse_cursor(mouse_cursor);
}
if let Some(is_urgent) = terminal.next_is_urgent.take() {
// We don't need to set the urgent flag if we already have the
// user's attention.
if !is_urgent || !self.window.is_focused {
self.window.set_urgent(is_urgent);
}
}
let size_info = *terminal.size_info();
let background_color = terminal.background_color();
let background_color_changed = background_color != self.last_background_color;
self.last_background_color = background_color;
{
let glyph_cache = &mut self.glyph_cache;
let metrics = glyph_cache.font_metrics();
let mut cell_line_rects = Vec::new();
// Draw grid
{
let _sampler = self.meter.sampler();
// Make a copy of size_info since the closure passed here
// borrows terminal mutably
//
// TODO I wonder if the renderable cells iter could avoid the
// mutable borrow
let window_focused = self.window.is_focused;
self.renderer.with_api(config, &size_info, |mut api| {
// Clear screen to update whole background with new color
if background_color_changed {
api.clear(background_color);
}
// Store underline/strikeout information beyond current cell
let mut last_cell = None;
let mut start_underline: Option<RenderableCell> = None;
let mut start_strikeout: Option<RenderableCell> = None;
// Iterate over all non-empty cells in the grid
for cell in terminal.renderable_cells(config, selection, window_focused) {
// Check if there is a new underline
if let Some(underline) = calculate_cell_line_state(
cell,
&mut start_underline,
&last_cell,
&metrics,
&size_info,
cell::Flags::UNDERLINE,
) {
cell_line_rects.push(underline);
}
// Check if there is a new strikeout
if let Some(strikeout) = calculate_cell_line_state(
cell,
&mut start_strikeout,
&last_cell,
&metrics,
&size_info,
cell::Flags::STRIKEOUT,
) {
cell_line_rects.push(strikeout);
}
// Change the last checked cell for underline/strikeout
last_cell = Some(cell);
// Draw the cell
api.render_cell(cell, glyph_cache);
}
// If underline hasn't been reset, draw until the last cell
if let Some(start) = start_underline {
cell_line_rects.push(
cell_line_rect(
&start,
&last_cell.unwrap(),
&metrics, &size_info,
cell::Flags::UNDERLINE
)
);
}
// If strikeout hasn't been reset, draw until the last cell
if let Some(start) = start_strikeout {
cell_line_rects.push(
cell_line_rect(
&start,
&last_cell.unwrap(),
&metrics, &size_info,
cell::Flags::STRIKEOUT
)
);
}
});
}
// Draw rectangles
let visual_bell_intensity = terminal.visual_bell.intensity();
self.renderer.draw_rects(config, &size_info, visual_bell_intensity, cell_line_rects);
// Draw render timer
if self.render_timer {
let timing = format!("{:.3} usec", self.meter.average());
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
self.renderer.with_api(config, &size_info, |mut api| {
api.render_string(&timing[..], glyph_cache, color);
});
}
}
// Unlock the terminal mutex; following call to swap_buffers() may block
drop(terminal);
self.window
.swap_buffers()
.expect("swap buffers");
// Clear after swap_buffers when terminal mutex isn't held. Mesa for
// some reason takes a long time to call glClear(). The driver descends
// into xcb_connect_to_fd() which ends up calling __poll_nocancel()
// which blocks for a while.
//
// By keeping this outside of the critical region, the Mesa bug is
// worked around to some extent. Since this doesn't actually address the
// issue of glClear being slow, less time is available for input
// handling and rendering.
self.renderer.with_api(config, &size_info, |api| {
api.clear(background_color);
});
}
pub fn get_window_id(&self) -> Option<usize> {
self.window.get_window_id()
}
/// Adjust the XIM editor position according to the new location of the cursor
pub fn update_ime_position(&mut self, terminal: &Term) {
use index::{Point, Line, Column};
use term::SizeInfo;
let Point{line: Line(row), col: Column(col)} = terminal.cursor().point;
let SizeInfo{cell_width: cw,
cell_height: ch,
padding_x: px,
padding_y: py, ..} = *terminal.size_info();
let nspot_y = (py + (row + 1) as f32 * ch) as i16;
let nspot_x = (px + col as f32 * cw) as i16;
self.window().send_xim_spot(nspot_x, nspot_y);
}
}
// Check if the underline/strikeout state has changed
// This returns a new rectangle whenever an underline/strikeout ends
fn calculate_cell_line_state(
cell: RenderableCell,
start_cell_line: &mut Option<RenderableCell>,
last_cell: &Option<RenderableCell>,
metrics: &Metrics,
size_info: &SizeInfo,
flag: cell::Flags,
) -> Option<(Rect<u32>, Rgb)> {
match *start_cell_line {
// If line is already started, check for end
Some(start) => {
// No change in line
if cell.line == start.line && cell.flags.contains(flag) && cell.fg == start.fg {
return None;
}
// Check if we need to start a new line
*start_cell_line = if cell.flags.contains(flag) {
// Start a new line
Some(cell)
} else {
// Disable line
None
};
return Some(cell_line_rect(
&start,
&last_cell.unwrap(),
metrics,
size_info,
flag,
));
}
// Check for new start of line
None => if cell.flags.contains(flag) {
*start_cell_line = Some(cell);
},
}
None
}
// Create a colored rectangle for an underline/strikeout based on two cells
fn cell_line_rect(
start: &RenderableCell,
end: &RenderableCell,
metrics: &Metrics,
size: &SizeInfo,
flag: cell::Flags,
) -> (Rect<u32>, Rgb) {
let x = (start.column.0 as f64 * metrics.average_advance) as u32;
let end_x = ((end.column.0 + 1) as f64 * metrics.average_advance) as u32;
let width = end_x - x;
let (y, height) = match flag {
cell::Flags::UNDERLINE => {
// Calculate the bottom point of the underline
let underline_bottom = metrics.line_height as f32 + metrics.descent
- metrics.underline_position
+ metrics.underline_thickness / 2.;
// Check if underline is out of bounds
let y = if underline_bottom > metrics.line_height as f32 {
// Put underline at the bottom of the cell rect
((start.line.0 as f32 + 1.) * metrics.line_height as f32
- metrics.underline_thickness)
.round() as u32
} else {
// Get the baseline position and offset it down by (-) underline position
// then move it up by half the underline thickness
((start.line.0 as f32 + 1.) * metrics.line_height as f32 + metrics.descent
- metrics.underline_position
- metrics.underline_thickness / 2.)
.round() as u32
};
let height = metrics.underline_thickness as u32;
(y, height)
}
cell::Flags::STRIKEOUT => {
// Get the baseline position and offset it up by strikeout position
// then move it up by half the strikeout thickness
let y = ((start.line.0 as f32 + 1.) * metrics.line_height as f32 + metrics.descent
- metrics.strikeout_position
- metrics.strikeout_thickness / 2.)
.round() as u32;
let height = metrics.strikeout_thickness as u32;
(y, height)
}
_ => panic!("Invalid flag for cell line drawing specified"),
};
let rect = Rect::new(
x + size.padding_x as u32,
y + size.padding_y as u32,
width,
height,
);
let color = start.fg;
(rect, color)
}