diff options
Diffstat (limited to 'alacritty/src/display/mod.rs')
-rw-r--r-- | alacritty/src/display/mod.rs | 451 |
1 files changed, 254 insertions, 197 deletions
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index c17d8aa7..ed1b1fc5 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -1,23 +1,24 @@ //! The display subsystem including window management, font rasterization, and //! GPU drawing. +use std::cmp; use std::fmt::{self, Formatter}; +use std::mem::{self, ManuallyDrop}; +use std::num::NonZeroU32; +use std::ops::{Deref, DerefMut}; #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] use std::sync::atomic::Ordering; -use std::{cmp, mem}; - -use glutin::dpi::PhysicalSize; -use glutin::event::ModifiersState; -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 glutin::context::{NotCurrentContext, PossiblyCurrentContext}; +use glutin::prelude::*; +use glutin::surface::{Rect as DamageRect, Surface, SwapInterval, WindowSurface}; + +use log::{debug, info, warn}; use parking_lot::MutexGuard; use serde::{Deserialize, Serialize}; -#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] -use wayland_client::EventQueue; +use winit::dpi::PhysicalSize; +use winit::event::ModifiersState; +use winit::window::CursorIcon; use crossfont::{self, Rasterize, Rasterizer}; use unicode_width::UnicodeWidthChar; @@ -33,9 +34,9 @@ use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::{self, Term, TermDamage, TermMode, MIN_COLUMNS, MIN_SCREEN_LINES}; use crate::config::font::Font; +use crate::config::window::Dimensions; #[cfg(not(windows))] use crate::config::window::StartupMode; -use crate::config::window::{Dimensions, Identity}; use crate::config::UiConfig; use crate::display::bell::VisualBell; use crate::display::color::List; @@ -84,8 +85,8 @@ pub enum Error { /// Error in renderer. Render(renderer::Error), - /// Error during buffer swap. - Context(glutin::ContextError), + /// Error during context operations. + Context(glutin::error::Error), } impl std::error::Error for Error { @@ -128,8 +129,8 @@ impl From<renderer::Error> for Error { } } -impl From<glutin::ContextError> for Error { - fn from(val: glutin::ContextError) -> Self { +impl From<glutin::error::Error> for Error { + fn from(val: glutin::error::Error) -> Self { Error::Context(val) } } @@ -334,17 +335,17 @@ impl DisplayUpdate { /// The display wraps a window, font rasterizer, and GPU renderer. pub struct Display { - pub size_info: SizeInfo, pub window: Window, + pub size_info: SizeInfo, + /// Hint highlighted by the mouse. pub highlighted_hint: Option<HintMatch>, /// Hint highlighted by the vi mode cursor. pub vi_highlighted_hint: Option<HintMatch>, - #[cfg(not(any(target_os = "macos", windows)))] - pub is_x11: bool, + pub is_wayland: bool, /// UI cursor visibility for blinking. pub cursor_hidden: bool, @@ -369,161 +370,57 @@ pub struct Display { // Mouse point position when highlighting hints. hint_mouse_point: Option<Point>, - is_damage_supported: bool, + renderer: ManuallyDrop<Renderer>, + + surface: ManuallyDrop<Surface<WindowSurface>>, + + context: ManuallyDrop<Replaceable<PossiblyCurrentContext>>, + debug_damage: bool, damage_rects: Vec<DamageRect>, next_frame_damage_rects: Vec<DamageRect>, - renderer: Renderer, glyph_cache: GlyphCache, meter: Meter, } -/// Input method state. -#[derive(Debug, Default)] -pub struct Ime { - /// Whether the IME is enabled. - enabled: bool, - - /// Current IME preedit. - preedit: Option<Preedit>, -} - -impl Ime { - pub fn new() -> Self { - Default::default() - } - - #[inline] - pub fn set_enabled(&mut self, is_enabled: bool) { - if is_enabled { - self.enabled = is_enabled - } else { - // Clear state when disabling IME. - *self = Default::default(); - } - } - - #[inline] - pub fn is_enabled(&self) -> bool { - self.enabled - } - - #[inline] - pub fn set_preedit(&mut self, preedit: Option<Preedit>) { - self.preedit = preedit; - } - - #[inline] - pub fn preedit(&self) -> Option<&Preedit> { - self.preedit.as_ref() - } -} - -#[derive(Debug, Default, PartialEq, Eq)] -pub struct Preedit { - /// The preedit text. - text: String, - - /// Byte offset for cursor start into the preedit text. - /// - /// `None` means that the cursor is invisible. - cursor_byte_offset: Option<usize>, - - /// The cursor offset from the end of the preedit in char width. - cursor_end_offset: Option<usize>, -} - -impl Preedit { - pub fn new(text: String, cursor_byte_offset: Option<usize>) -> Self { - let cursor_end_offset = if let Some(byte_offset) = cursor_byte_offset { - // Convert byte offset into char offset. - let cursor_end_offset = - text[byte_offset..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1)); - - Some(cursor_end_offset) - } else { - None - }; - - Self { text, cursor_byte_offset, cursor_end_offset } - } -} - -/// Pending renderer updates. -/// -/// All renderer updates are cached to be applied just before rendering, to avoid platform-specific -/// rendering issues. -#[derive(Debug, Default, Copy, Clone)] -pub struct RendererUpdate { - /// Should resize the window. - resize: bool, - - /// Clear font caches. - clear_font_cache: bool, -} - impl Display { - pub fn new<E>( + pub fn new( + window: Window, + gl_context: NotCurrentContext, config: &UiConfig, - event_loop: &EventLoopWindowTarget<E>, - identity: &Identity, - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - wayland_event_queue: Option<&EventQueue>, ) -> Result<Display, Error> { - #[cfg(any(not(feature = "x11"), target_os = "macos", windows))] - let is_x11 = false; - #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] - let is_x11 = event_loop.is_x11(); + #[cfg(any(not(feature = "wayland"), target_os = "macos", windows))] + let is_wayland = false; + #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] + let is_wayland = window.wayland_surface().is_some(); - // Guess scale_factor based on first monitor. On Wayland the initial frame always renders at - // a scale factor of 1. - let estimated_scale_factor = if cfg!(any(target_os = "macos", windows)) || is_x11 { - event_loop.available_monitors().next().map_or(1., |m| m.scale_factor()) - } else { - 1. - }; + let scale_factor = window.scale_factor as f32; + let rasterizer = Rasterizer::new(scale_factor)?; - // Guess the target window dimensions. debug!("Loading \"{}\" font", &config.font.normal().family); - let font = &config.font; - let rasterizer = Rasterizer::new(estimated_scale_factor as f32)?; - let mut glyph_cache = GlyphCache::new(rasterizer, font)?; + let mut glyph_cache = GlyphCache::new(rasterizer, &config.font)?; + let metrics = glyph_cache.font_metrics(); let (cell_width, cell_height) = compute_cell_size(config, &metrics); - // Guess the target window size if the user has specified the number of lines/columns. - let dimensions = config.window.dimensions(); - let estimated_size = dimensions.map(|dimensions| { - window_size(config, dimensions, cell_width, cell_height, estimated_scale_factor) - }); + // Resize the window to account for the user configured size. + if let Some(dimensions) = config.window.dimensions() { + let size = window_size(config, dimensions, cell_width, cell_height, scale_factor); + window.set_inner_size(size); + } - debug!("Estimated scaling factor: {}", estimated_scale_factor); - debug!("Estimated window size: {:?}", estimated_size); - debug!("Estimated cell size: {} x {}", cell_width, cell_height); - - // Spawn the Alacritty window. - let window = Window::new( - event_loop, - config, - identity, - estimated_size, - #[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))] - wayland_event_queue, + // Create the GL surface to draw into. + let surface = renderer::platform::create_gl_surface( + &gl_context, + window.inner_size(), + window.raw_window_handle(), )?; - // Create renderer. - let mut renderer = Renderer::new()?; - - let scale_factor = window.scale_factor; - info!("Display scale factor: {}", scale_factor); + // Make the context current. + let context = gl_context.make_current(&surface)?; - // If the scaling factor changed update the glyph cache and mark for resize. - let should_resize = (estimated_scale_factor - window.scale_factor).abs() > f64::EPSILON; - let (cell_width, cell_height) = if should_resize { - Self::update_font_size(&mut glyph_cache, scale_factor, config, font) - } else { - (cell_width, cell_height) - }; + // Create renderer. + let mut renderer = Renderer::new(&context)?; // Load font common glyphs to accelerate rendering. debug!("Filling glyph cache with common glyphs"); @@ -531,14 +428,7 @@ impl Display { glyph_cache.reset_glyph_cache(&mut api); }); - if let Some(dimensions) = dimensions.filter(|_| should_resize) { - // Resize the window again if the scale factor was not estimated correctly. - let size = - window_size(config, dimensions, cell_width, cell_height, window.scale_factor); - window.set_inner_size(size); - } - - let padding = config.window.padding(window.scale_factor); + let padding = config.window.padding(window.scale_factor as f32); let viewport_size = window.inner_size(); // Create new size with at least one column and row. @@ -549,7 +439,7 @@ impl Display { cell_height, padding.0, padding.1, - config.window.dynamic_padding && dimensions.is_none(), + config.window.dynamic_padding && config.window.dimensions().is_none(), ); info!("Cell size: {} x {}", cell_width, cell_height); @@ -570,8 +460,8 @@ impl Display { // On Wayland we can safely ignore this call, since the window isn't visible until you // actually draw something into it and commit those changes. #[cfg(not(any(target_os = "macos", windows)))] - if is_x11 { - window.swap_buffers(); + if !is_wayland { + surface.swap_buffers(&context).expect("failed to swap buffers."); renderer.finish(); } @@ -582,24 +472,35 @@ impl Display { match config.window.startup_mode { #[cfg(target_os = "macos")] StartupMode::SimpleFullscreen => window.set_simple_fullscreen(true), - #[cfg(not(target_os = "macos"))] - StartupMode::Maximized if is_x11 => window.set_maximized(true), + #[cfg(not(any(target_os = "macos", windows)))] + StartupMode::Maximized if !is_wayland => window.set_maximized(true), _ => (), } 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, next_frame_damage_rects) = if is_damage_supported || debug_damage { + let (damage_rects, next_frame_damage_rects) = if is_wayland || debug_damage { let vec = Vec::with_capacity(size_info.screen_lines()); (vec.clone(), vec) } else { (Vec::new(), Vec::new()) }; + // We use vsync everywhere except wayland. + if !is_wayland { + if let Err(err) = + surface.set_swap_interval(&context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) + { + warn!("Error setting vsync: {:?}", err); + } + } + Ok(Self { window, - renderer, + context: ManuallyDrop::new(Replaceable::new(context)), + surface: ManuallyDrop::new(surface), + renderer: ManuallyDrop::new(renderer), glyph_cache, hint_state, meter: Meter::new(), @@ -607,14 +508,12 @@ impl Display { ime: Ime::new(), highlighted_hint: None, vi_highlighted_hint: None, - #[cfg(not(any(target_os = "macos", windows)))] - is_x11, + is_wayland, cursor_hidden: false, visual_bell: VisualBell::from(&config.bell), colors: List::from(&config.colors), pending_update: Default::default(), pending_renderer_update: Default::default(), - is_damage_supported, debug_damage, damage_rects, next_frame_damage_rects, @@ -622,6 +521,42 @@ impl Display { }) } + #[inline] + pub fn gl_context(&self) -> &PossiblyCurrentContext { + self.context.get() + } + + pub fn make_not_current(&mut self) { + if self.context.get().is_current() { + self.context.replace_with(|context| { + context + .make_not_current() + .expect("failed to disable context") + .treat_as_possibly_current() + }); + } + } + + pub fn make_current(&self) { + if !self.context.get().is_current() { + self.context.make_current(&self.surface).expect("failed to make context current") + } + } + + fn swap_buffers(&self) { + #[allow(clippy::single_match)] + match (self.surface.deref(), &self.context.get()) { + #[cfg(not(any(target_os = "macos", windows)))] + (Surface::Egl(surface), PossiblyCurrentContext::Egl(context)) + if self.is_wayland && !self.debug_damage => + { + surface.swap_buffers_with_damage(context, &self.damage_rects) + }, + (surface, context) => surface.swap_buffers(context), + } + .expect("failed to swap buffers."); + } + /// Update font size and cell dimensions. /// /// This will return a tuple of the cell width and height. @@ -690,7 +625,7 @@ impl Display { renderer_update.resize = true } - let padding = config.window.padding(self.window.scale_factor); + let padding = config.window.padding(self.window.scale_factor as f32); self.size_info = SizeInfo::new( width, @@ -731,13 +666,13 @@ impl Display { // Resize renderer. if renderer_update.resize { - let physical = - PhysicalSize::new(self.size_info.width() as _, self.size_info.height() as _); - self.window.resize(physical); + let width = NonZeroU32::new(self.size_info.width() as u32).unwrap(); + let height = NonZeroU32::new(self.size_info.height() as u32).unwrap(); + self.surface.resize(&self.context, width, height); } // Ensure we're modifying the correct OpenGL context. - self.window.make_current(); + self.make_current(); if renderer_update.clear_font_cache { self.reset_glyph_cache(); @@ -763,12 +698,8 @@ impl Display { /// Damage the entire window. fn fully_damage(&mut self) { - let screen_rect = DamageRect { - x: 0, - y: 0, - width: self.size_info.width() as u32, - height: self.size_info.height() as u32, - }; + let screen_rect = + DamageRect::new(0, 0, self.size_info.width() as i32, self.size_info.height() as i32); self.damage_rects.push(screen_rect); } @@ -847,7 +778,7 @@ impl Display { drop(terminal); // Make sure this window's OpenGL context is active. - self.window.make_current(); + self.make_current(); self.renderer.clear(background_color, config.window_opacity()); let mut lines = RenderLines::new(); @@ -1036,14 +967,10 @@ impl Display { self.request_frame(&self.window); // 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(); - } + self.swap_buffers(); #[cfg(all(feature = "x11", not(any(target_os = "macos", windows))))] - if self.is_x11 { + if !self.is_wayland { // On X11 `swap_buffers` does not block for vsync. However the next OpenGl command // will block to synchronize (this is `glClear` in Alacritty), which causes a // permanent one frame delay. @@ -1397,7 +1324,7 @@ impl Display { 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 * size_info.cell_width(); - DamageRect { x, y, width, height: size_info.cell_height() } + DamageRect::new(x as i32, y as i32, width as i32, size_info.cell_height() as i32) } /// Damage currently highlighted `Display` hints. @@ -1420,7 +1347,7 @@ impl Display { /// Returns `true` if damage information should be collected, `false` otherwise. #[inline] fn collect_damage(&self) -> bool { - self.is_damage_supported || self.debug_damage + self.is_wayland || self.debug_damage } /// Highlight damaged rects. @@ -1462,8 +1389,138 @@ impl Display { impl Drop for Display { fn drop(&mut self) { // Switch OpenGL context before dropping, otherwise objects (like programs) from other - // contexts might be deleted. - self.window.make_current() + // contexts might be deleted during droping renderer. + self.make_current(); + unsafe { + ManuallyDrop::drop(&mut self.renderer); + ManuallyDrop::drop(&mut self.context); + ManuallyDrop::drop(&mut self.surface); + } + } +} + +/// Input method state. +#[derive(Debug, Default)] +pub struct Ime { + /// Whether the IME is enabled. + enabled: bool, + + /// Current IME preedit. + preedit: Option<Preedit>, +} + +impl Ime { + pub fn new() -> Self { + Default::default() + } + + #[inline] + pub fn set_enabled(&mut self, is_enabled: bool) { + if is_enabled { + self.enabled = is_enabled + } else { + // Clear state when disabling IME. + *self = Default::default(); + } + } + + #[inline] + pub fn is_enabled(&self) -> bool { + self.enabled + } + + #[inline] + pub fn set_preedit(&mut self, preedit: Option<Preedit>) { + self.preedit = preedit; + } + + #[inline] + pub fn preedit(&self) -> Option<&Preedit> { + self.preedit.as_ref() + } +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct Preedit { + /// The preedit text. + text: String, + + /// Byte offset for cursor start into the preedit text. + /// + /// `None` means that the cursor is invisible. + cursor_byte_offset: Option<usize>, + + /// The cursor offset from the end of the preedit in char width. + cursor_end_offset: Option<usize>, +} + +impl Preedit { + pub fn new(text: String, cursor_byte_offset: Option<usize>) -> Self { + let cursor_end_offset = if let Some(byte_offset) = cursor_byte_offset { + // Convert byte offset into char offset. + let cursor_end_offset = + text[byte_offset..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1)); + + Some(cursor_end_offset) + } else { + None + }; + + Self { text, cursor_byte_offset, cursor_end_offset } + } +} + +/// Pending renderer updates. +/// +/// All renderer updates are cached to be applied just before rendering, to avoid platform-specific +/// rendering issues. +#[derive(Debug, Default, Copy, Clone)] +pub struct RendererUpdate { + /// Should resize the window. + resize: bool, + + /// Clear font caches. + clear_font_cache: bool, +} + +/// Struct for safe in-place replacement. +/// +/// This struct allows easily replacing struct fields that provide `self -> Self` methods in-place, +/// without having to deal with constantly unwrapping the underlying [`Option`]. +struct Replaceable<T>(Option<T>); + +impl<T> Replaceable<T> { + pub fn new(inner: T) -> Self { + Self(Some(inner)) + } + + /// Replace the contents of the container. + pub fn replace_with<F: FnMut(T) -> T>(&mut self, f: F) { + self.0 = self.0.take().map(f); + } + + /// Get immutable access to the wrapped value. + pub fn get(&self) -> &T { + self.0.as_ref().unwrap() + } + + /// Get mutable access to the wrapped value. + pub fn get_mut(&mut self) -> &mut T { + self.0.as_mut().unwrap() + } +} + +impl<T> Deref for Replaceable<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.get() + } +} + +impl<T> DerefMut for Replaceable<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.get_mut() } } @@ -1486,7 +1543,7 @@ fn window_size( dimensions: Dimensions, cell_width: f32, cell_height: f32, - scale_factor: f64, + scale_factor: f32, ) -> PhysicalSize<u32> { let padding = config.window.padding(scale_factor); |