aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/display.rs
diff options
context:
space:
mode:
authorChristian Duerr <contact@christianduerr.com>2019-10-05 02:29:26 +0200
committerGitHub <noreply@github.com>2019-10-05 02:29:26 +0200
commit729eef0c933831bccfeac6a355bdb410787fbe5f (patch)
tree35cdf2e6427ad18bc53efbab4cab34a0af2054d7 /alacritty/src/display.rs
parentb0c6fdff763f7271506d26d7e768e6377fdc691b (diff)
downloadr-alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.tar.gz
r-alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.tar.bz2
r-alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.zip
Update to winit/glutin EventLoop 2.0
This takes the latest glutin master to port Alacritty to the EventLoop 2.0 rework. This changes a big part of the event loop handling by pushing the event loop in a separate thread from the renderer and running both in parallel. Fixes #2796. Fixes #2694. Fixes #2643. Fixes #2625. Fixes #2618. Fixes #2601. Fixes #2564. Fixes #2456. Fixes #2438. Fixes #2334. Fixes #2254. Fixes #2217. Fixes #1789. Fixes #1750. Fixes #1125.
Diffstat (limited to 'alacritty/src/display.rs')
-rw-r--r--alacritty/src/display.rs476
1 files changed, 476 insertions, 0 deletions
diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs
new file mode 100644
index 00000000..a8f72b3e
--- /dev/null
+++ b/alacritty/src/display.rs
@@ -0,0 +1,476 @@
+// 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::cmp::max;
+use std::f64;
+use std::fmt;
+use std::time::Instant;
+
+use glutin::dpi::{PhysicalPosition, PhysicalSize};
+use glutin::event_loop::EventLoop;
+use log::{debug, info};
+use parking_lot::MutexGuard;
+
+use font::{self, Rasterize, Size};
+
+use alacritty_terminal::config::StartupMode;
+use alacritty_terminal::event::{Event, OnResize};
+use alacritty_terminal::index::Line;
+use alacritty_terminal::message_bar::MessageBuffer;
+use alacritty_terminal::meter::Meter;
+use alacritty_terminal::renderer::rects::{RenderLines, RenderRect};
+use alacritty_terminal::renderer::{self, GlyphCache, QuadRenderer};
+use alacritty_terminal::term::color::Rgb;
+use alacritty_terminal::term::{RenderableCell, SizeInfo, Term};
+
+use crate::config::Config;
+use crate::event::{FontResize, Resize};
+use crate::window::{self, Window};
+
+/// Font size change interval
+pub const FONT_SIZE_STEP: f32 = 0.5;
+
+#[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),
+
+ /// Error during buffer swap
+ ContextError(glutin::ContextError),
+}
+
+impl std::error::Error for Error {
+ fn cause(&self) -> Option<&dyn (std::error::Error)> {
+ match *self {
+ Error::Window(ref err) => Some(err),
+ Error::Font(ref err) => Some(err),
+ Error::Render(ref err) => Some(err),
+ Error::ContextError(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(),
+ Error::ContextError(ref err) => err.description(),
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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),
+ Error::ContextError(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)
+ }
+}
+
+impl From<glutin::ContextError> for Error {
+ fn from(val: glutin::ContextError) -> Error {
+ Error::ContextError(val)
+ }
+}
+
+/// The display wraps a window, font rasterizer, and GPU renderer
+pub struct Display {
+ pub size_info: SizeInfo,
+ pub font_size: Size,
+ pub window: Window,
+
+ renderer: QuadRenderer,
+ glyph_cache: GlyphCache,
+ meter: Meter,
+}
+
+impl Display {
+ pub fn new(config: &Config, event_loop: &EventLoop<Event>) -> Result<Display, Error> {
+ // Guess DPR based on first monitor
+ let estimated_dpr =
+ event_loop.available_monitors().next().map(|m| m.hidpi_factor()).unwrap_or(1.);
+
+ // Guess the target window dimensions
+ let metrics = GlyphCache::static_metrics(config.font.clone(), estimated_dpr)?;
+ let (cell_width, cell_height) = compute_cell_size(config, &metrics);
+ let dimensions =
+ GlyphCache::calculate_dimensions(config, estimated_dpr, cell_width, cell_height);
+
+ debug!("Estimated DPR: {}", estimated_dpr);
+ debug!("Estimated Cell Size: {} x {}", cell_width, cell_height);
+ debug!("Estimated Dimensions: {:?}", dimensions);
+
+ // Create the window where Alacritty will be displayed
+ let logical = dimensions.map(|d| PhysicalSize::new(d.0, d.1).to_logical(estimated_dpr));
+
+ // Spawn window
+ let mut window = Window::new(event_loop, &config, logical)?;
+
+ let dpr = window.hidpi_factor();
+ info!("Device pixel ratio: {}", dpr);
+
+ // get window properties for initializing the other subsystems
+ let mut viewport_size = window.inner_size().to_physical(dpr);
+
+ // Create renderer
+ let mut renderer = QuadRenderer::new()?;
+
+ let (glyph_cache, cell_width, cell_height) =
+ Self::new_glyph_cache(dpr, &mut renderer, config)?;
+
+ let mut padding_x = f32::from(config.window.padding.x) * dpr as f32;
+ let mut padding_y = f32::from(config.window.padding.y) * dpr as f32;
+
+ if let Some((width, height)) =
+ GlyphCache::calculate_dimensions(config, dpr, cell_width, cell_height)
+ {
+ let PhysicalSize { width: w, height: h } = window.inner_size().to_physical(dpr);
+ if (w - width).abs() < f64::EPSILON && (h - height).abs() < f64::EPSILON {
+ info!("Estimated DPR correctly, skipping resize");
+ } else {
+ viewport_size = PhysicalSize::new(width, height);
+ window.set_inner_size(viewport_size.to_logical(dpr));
+ }
+ } else if config.window.dynamic_padding {
+ // Make sure additional padding is spread evenly
+ padding_x = dynamic_padding(padding_x, viewport_size.width as f32, cell_width);
+ padding_y = dynamic_padding(padding_y, viewport_size.height as f32, cell_height);
+ }
+
+ padding_x = padding_x.floor();
+ padding_y = padding_y.floor();
+
+ info!("Cell Size: {} x {}", cell_width, cell_height);
+ info!("Padding: {} x {}", padding_x, padding_y);
+
+ let size_info = SizeInfo {
+ dpr,
+ width: viewport_size.width as f32,
+ height: viewport_size.height as f32,
+ cell_width: cell_width as f32,
+ cell_height: cell_height as f32,
+ padding_x: padding_x as f32,
+ padding_y: padding_y as f32,
+ };
+
+ // Update OpenGL projection
+ renderer.resize(&size_info);
+
+ // Clear screen
+ let background_color = config.colors.primary.background;
+ renderer.with_api(&config, &size_info, |api| {
+ api.clear(background_color);
+ });
+
+ // We should call `clear` when window is offscreen, so when `window.show()` happens it
+ // would be with background color instead of uninitialized surface.
+ window.swap_buffers();
+
+ window.set_visible(true);
+
+ // Set window position
+ //
+ // TODO: replace `set_position` with `with_position` once available
+ // Upstream issue: https://github.com/tomaka/winit/issues/806
+ if let Some(position) = config.window.position {
+ let physical = PhysicalPosition::from((position.x, position.y));
+ let logical = physical.to_logical(dpr);
+ window.set_outer_position(logical);
+ }
+
+ #[allow(clippy::single_match)]
+ match config.window.startup_mode() {
+ StartupMode::Fullscreen => window.set_fullscreen(true),
+ #[cfg(target_os = "macos")]
+ StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true),
+ #[cfg(not(any(target_os = "macos", windows)))]
+ StartupMode::Maximized => window.set_maximized(true),
+ _ => (),
+ }
+
+ Ok(Display {
+ window,
+ renderer,
+ glyph_cache,
+ meter: Meter::new(),
+ size_info,
+ font_size: config.font.size,
+ })
+ }
+
+ fn new_glyph_cache(
+ dpr: f64,
+ renderer: &mut QuadRenderer,
+ config: &Config,
+ ) -> Result<(GlyphCache, f32, f32), Error> {
+ let font = config.font.clone();
+ let rasterizer = font::Rasterizer::new(dpr as f32, config.font.use_thin_strokes())?;
+
+ // Initialize glyph cache
+ let glyph_cache = {
+ info!("Initializing glyph cache...");
+ let init_start = 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 {}s", 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 (cw, ch) = compute_cell_size(config, &glyph_cache.font_metrics());
+
+ Ok((glyph_cache, cw, ch))
+ }
+
+ /// Update font size and cell dimensions
+ fn update_glyph_cache(&mut self, config: &Config, size: Size) {
+ let size_info = &mut self.size_info;
+ let cache = &mut self.glyph_cache;
+
+ let font = config.font.clone().with_size(size);
+
+ self.renderer.with_loader(|mut api| {
+ let _ = cache.update_font_size(font, size_info.dpr, &mut api);
+ });
+
+ // Update cell size
+ let (cell_width, cell_height) = compute_cell_size(config, &self.glyph_cache.font_metrics());
+ size_info.cell_width = cell_width;
+ size_info.cell_height = cell_height;
+ }
+
+ /// Process resize events
+ pub fn handle_resize<T>(
+ &mut self,
+ terminal: &mut Term<T>,
+ pty_resize_handle: &mut dyn OnResize,
+ message_buffer: &MessageBuffer,
+ config: &Config,
+ resize_pending: Resize,
+ ) {
+ // Update font size and cell dimensions
+ if let Some(resize) = resize_pending.font_size {
+ self.font_size = match resize {
+ FontResize::Delta(delta) => max(self.font_size + delta, FONT_SIZE_STEP.into()),
+ FontResize::Reset => config.font.size,
+ };
+
+ self.update_glyph_cache(config, self.font_size);
+ }
+
+ // Update the window dimensions
+ if let Some(size) = resize_pending.dimensions {
+ self.size_info.width = size.width as f32;
+ self.size_info.height = size.height as f32;
+ }
+
+ let dpr = self.size_info.dpr;
+ let width = self.size_info.width;
+ let height = self.size_info.height;
+ let cell_width = self.size_info.cell_width;
+ let cell_height = self.size_info.cell_height;
+
+ // Recalculate padding
+ let mut padding_x = f32::from(config.window.padding.x) * dpr as f32;
+ let mut padding_y = f32::from(config.window.padding.y) * dpr as f32;
+
+ if config.window.dynamic_padding {
+ padding_x = dynamic_padding(padding_x, width, cell_width);
+ padding_y = dynamic_padding(padding_y, height, cell_height);
+ }
+
+ self.size_info.padding_x = padding_x.floor() as f32;
+ self.size_info.padding_y = padding_y.floor() as f32;
+
+ let mut pty_size = self.size_info;
+
+ // Subtract message bar lines from pty size
+ if resize_pending.message_buffer.is_some() {
+ let lines =
+ message_buffer.message().map(|m| m.text(&self.size_info).len()).unwrap_or(0);
+ pty_size.height -= pty_size.cell_height * lines as f32;
+ }
+
+ // Resize PTY
+ pty_resize_handle.on_resize(&pty_size);
+
+ // Resize terminal
+ terminal.resize(&pty_size);
+
+ // Resize renderer
+ let physical =
+ PhysicalSize::new(f64::from(self.size_info.width), f64::from(self.size_info.height));
+ self.renderer.resize(&self.size_info);
+ self.window.resize(physical);
+ }
+
+ /// 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<T>(
+ &mut self,
+ terminal: MutexGuard<'_, Term<T>>,
+ message_buffer: &MessageBuffer,
+ config: &Config,
+ ) {
+ let grid_cells: Vec<RenderableCell> = terminal.renderable_cells(config).collect();
+ let visual_bell_intensity = terminal.visual_bell.intensity();
+ let background_color = terminal.background_color();
+ let metrics = self.glyph_cache.font_metrics();
+ let glyph_cache = &mut self.glyph_cache;
+ let size_info = self.size_info;
+
+ // Update IME position
+ #[cfg(not(windows))]
+ self.window.update_ime_position(&terminal, &self.size_info);
+
+ // Drop terminal as early as possible to free lock
+ drop(terminal);
+
+ self.renderer.with_api(&config, &size_info, |api| {
+ api.clear(background_color);
+ });
+
+ let mut lines = RenderLines::new();
+
+ // Draw grid
+ {
+ let _sampler = self.meter.sampler();
+
+ self.renderer.with_api(&config, &size_info, |mut api| {
+ // Iterate over all non-empty cells in the grid
+ for cell in grid_cells {
+ // Update underline/strikeout
+ lines.update(cell);
+
+ // Draw the cell
+ api.render_cell(cell, glyph_cache);
+ }
+ });
+ }
+
+ let mut rects = lines.into_rects(&metrics, &size_info);
+
+ if let Some(message) = message_buffer.message() {
+ let text = message.text(&size_info);
+
+ // Create a new rectangle for the background
+ let start_line = size_info.lines().0 - text.len();
+ let y = size_info.padding_y + size_info.cell_height * start_line as f32;
+ rects.push(RenderRect::new(
+ 0.,
+ y,
+ size_info.width,
+ size_info.height - y,
+ message.color(),
+ ));
+
+ // Draw rectangles including the new background
+ self.renderer.draw_rects(
+ &size_info,
+ config.visual_bell.color,
+ visual_bell_intensity,
+ rects,
+ );
+
+ // Relay messages to the user
+ let mut offset = 1;
+ for message_text in text.iter().rev() {
+ self.renderer.with_api(&config, &size_info, |mut api| {
+ api.render_string(
+ &message_text,
+ Line(size_info.lines().saturating_sub(offset)),
+ glyph_cache,
+ None,
+ );
+ });
+ offset += 1;
+ }
+ } else {
+ // Draw rectangles
+ self.renderer.draw_rects(
+ &size_info,
+ config.visual_bell.color,
+ visual_bell_intensity,
+ rects,
+ );
+ }
+
+ // Draw render timer
+ if config.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[..], size_info.lines() - 2, glyph_cache, Some(color));
+ });
+ }
+
+ self.window.swap_buffers();
+ }
+}
+
+/// Calculate padding to spread it evenly around the terminal content
+#[inline]
+fn dynamic_padding(padding: f32, dimension: f32, cell_dimension: f32) -> f32 {
+ padding + ((dimension - 2. * padding) % cell_dimension) / 2.
+}
+
+/// Calculate the cell dimensions based on font metrics.
+#[inline]
+fn compute_cell_size(config: &Config, metrics: &font::Metrics) -> (f32, f32) {
+ let offset_x = f64::from(config.font.offset.x);
+ let offset_y = f64::from(config.font.offset.y);
+ (
+ f32::max(1., ((metrics.average_advance + offset_x) as f32).floor()),
+ f32::max(1., ((metrics.line_height + offset_y) as f32).floor()),
+ )
+}