aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/display/mod.rs
diff options
context:
space:
mode:
authorKirill Chibisov <contact@kchibisov.com>2022-11-03 19:37:54 +0300
committerGitHub <noreply@github.com>2022-11-03 19:37:54 +0300
commit0e418bc2f761617455cc58aaabc375055dfe4284 (patch)
treefc15d2260404914e21297d392f7f9c32a5f2bffc /alacritty/src/display/mod.rs
parent578e08486dfcdee0b2cd0e7a66752ff50edc46b8 (diff)
downloadr-alacritty-0e418bc2f761617455cc58aaabc375055dfe4284.tar.gz
r-alacritty-0e418bc2f761617455cc58aaabc375055dfe4284.tar.bz2
r-alacritty-0e418bc2f761617455cc58aaabc375055dfe4284.zip
Update glutin to 0.30.0
The glutin 0.30.0 update decouples glutin from winit which provides us with basis for a multithreaded renderer. This also improves robustness of our configuration picking, context creation, and surface handling. As an example we're now able to start on systems without a vsync, we don't try to build lots of contexts to check if some config works, and so on. That also brings us possibility to handle context losses, but that's a future work. Fixes #1268.
Diffstat (limited to 'alacritty/src/display/mod.rs')
-rw-r--r--alacritty/src/display/mod.rs451
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);