parent
b77b9b3bcd
commit
2bd26fbeb0
|
@ -31,6 +31,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Crash while typing on Wayland
|
- Crash while typing on Wayland
|
||||||
- Multi-line semantic bracket selection
|
- Multi-line semantic bracket selection
|
||||||
- Reduced GPU memory usage
|
- Reduced GPU memory usage
|
||||||
|
- Low frame rate when multiple windows render at the same time
|
||||||
|
- Redraw hanging until a keypress on X11 in rare cases
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@ use std::fmt::{self, Formatter};
|
||||||
use std::mem::{self, ManuallyDrop};
|
use std::mem::{self, ManuallyDrop};
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use glutin::context::{NotCurrentContext, PossiblyCurrentContext};
|
use glutin::context::{NotCurrentContext, PossiblyCurrentContext};
|
||||||
use glutin::prelude::*;
|
use glutin::prelude::*;
|
||||||
use glutin::surface::{Rect as DamageRect, Surface, SwapInterval, WindowSurface};
|
use glutin::surface::{Rect as DamageRect, Surface, SwapInterval, WindowSurface};
|
||||||
|
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info};
|
||||||
use parking_lot::MutexGuard;
|
use parking_lot::MutexGuard;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
|
@ -46,10 +46,11 @@ use crate::display::damage::RenderDamageIterator;
|
||||||
use crate::display::hint::{HintMatch, HintState};
|
use crate::display::hint::{HintMatch, HintState};
|
||||||
use crate::display::meter::Meter;
|
use crate::display::meter::Meter;
|
||||||
use crate::display::window::Window;
|
use crate::display::window::Window;
|
||||||
use crate::event::{Mouse, SearchState};
|
use crate::event::{Event, EventType, Mouse, SearchState};
|
||||||
use crate::message_bar::{MessageBuffer, MessageType};
|
use crate::message_bar::{MessageBuffer, MessageType};
|
||||||
use crate::renderer::rects::{RenderLine, RenderLines, RenderRect};
|
use crate::renderer::rects::{RenderLine, RenderLines, RenderRect};
|
||||||
use crate::renderer::{self, GlyphCache, Renderer};
|
use crate::renderer::{self, GlyphCache, Renderer};
|
||||||
|
use crate::scheduler::{Scheduler, TimerId, Topic};
|
||||||
use crate::string::{ShortenDirection, StrShortener};
|
use crate::string::{ShortenDirection, StrShortener};
|
||||||
|
|
||||||
pub mod content;
|
pub mod content;
|
||||||
|
@ -367,6 +368,9 @@ pub struct Display {
|
||||||
/// The ime on the given display.
|
/// The ime on the given display.
|
||||||
pub ime: Ime,
|
pub ime: Ime,
|
||||||
|
|
||||||
|
/// The state of the timer for frame scheduling.
|
||||||
|
pub frame_timer: FrameTimer,
|
||||||
|
|
||||||
// Mouse point position when highlighting hints.
|
// Mouse point position when highlighting hints.
|
||||||
hint_mouse_point: Option<Point>,
|
hint_mouse_point: Option<Point>,
|
||||||
|
|
||||||
|
@ -487,13 +491,9 @@ impl Display {
|
||||||
(Vec::new(), Vec::new())
|
(Vec::new(), Vec::new())
|
||||||
};
|
};
|
||||||
|
|
||||||
// We use vsync everywhere except wayland.
|
// Disable vsync.
|
||||||
if !is_wayland {
|
if let Err(err) = surface.set_swap_interval(&context, SwapInterval::DontWait) {
|
||||||
if let Err(err) =
|
info!("Failed to disable vsync: {}", err);
|
||||||
surface.set_swap_interval(&context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
|
|
||||||
{
|
|
||||||
warn!("Error setting vsync: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -510,6 +510,7 @@ impl Display {
|
||||||
vi_highlighted_hint: None,
|
vi_highlighted_hint: None,
|
||||||
is_wayland,
|
is_wayland,
|
||||||
cursor_hidden: false,
|
cursor_hidden: false,
|
||||||
|
frame_timer: FrameTimer::new(),
|
||||||
visual_bell: VisualBell::from(&config.bell),
|
visual_bell: VisualBell::from(&config.bell),
|
||||||
colors: List::from(&config.colors),
|
colors: List::from(&config.colors),
|
||||||
pending_update: Default::default(),
|
pending_update: Default::default(),
|
||||||
|
@ -751,6 +752,7 @@ impl Display {
|
||||||
pub fn draw<T: EventListener>(
|
pub fn draw<T: EventListener>(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut terminal: MutexGuard<'_, Term<T>>,
|
mut terminal: MutexGuard<'_, Term<T>>,
|
||||||
|
scheduler: &mut Scheduler,
|
||||||
message_buffer: &MessageBuffer,
|
message_buffer: &MessageBuffer,
|
||||||
config: &UiConfig,
|
config: &UiConfig,
|
||||||
search_state: &SearchState,
|
search_state: &SearchState,
|
||||||
|
@ -966,10 +968,11 @@ impl Display {
|
||||||
self.draw_hyperlink_preview(config, cursor_point, display_offset);
|
self.draw_hyperlink_preview(config, cursor_point, display_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame event should be requested before swaping buffers, since it requires surface
|
// Frame event should be requested before swapping buffers on Wayland, since it requires
|
||||||
// `commit`, which is done by swap buffers under the hood.
|
// surface `commit`, which is done by swap buffers under the hood.
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
if self.is_wayland {
|
||||||
self.request_frame(&self.window);
|
self.request_frame(scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
// Clearing debug highlights from the previous frame requires full redraw.
|
// Clearing debug highlights from the previous frame requires full redraw.
|
||||||
self.swap_buffers();
|
self.swap_buffers();
|
||||||
|
@ -982,6 +985,12 @@ impl Display {
|
||||||
self.renderer.finish();
|
self.renderer.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: Request the new frame after swapping buffers, so the
|
||||||
|
// time to finish OpenGL operations is accounted for in the timeout.
|
||||||
|
if !self.is_wayland {
|
||||||
|
self.request_frame(scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
self.damage_rects.clear();
|
self.damage_rects.clear();
|
||||||
|
|
||||||
// Append damage rects we've enqueued for the next frame.
|
// Append damage rects we've enqueued for the next frame.
|
||||||
|
@ -1376,23 +1385,40 @@ impl Display {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requst a new frame for a window on Wayland.
|
/// Requst a new frame for a window on Wayland.
|
||||||
#[inline]
|
fn request_frame(&mut self, scheduler: &mut Scheduler) {
|
||||||
|
// Mark that we've used a frame.
|
||||||
|
self.window.has_frame.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||||
fn request_frame(&self, window: &Window) {
|
if let Some(surface) = self.window.wayland_surface() {
|
||||||
let surface = match window.wayland_surface() {
|
let has_frame = self.window.has_frame.clone();
|
||||||
Some(surface) => surface,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let should_draw = self.window.should_draw.clone();
|
|
||||||
|
|
||||||
// Mark that window was drawn.
|
|
||||||
should_draw.store(false, Ordering::Relaxed);
|
|
||||||
|
|
||||||
// Request a new frame.
|
// Request a new frame.
|
||||||
surface.frame().quick_assign(move |_, _, _| {
|
surface.frame().quick_assign(move |_, _, _| {
|
||||||
should_draw.store(true, Ordering::Relaxed);
|
has_frame.store(true, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the display vblank interval.
|
||||||
|
let monitor_vblank_interval = 1_000_000.
|
||||||
|
/ self
|
||||||
|
.window
|
||||||
|
.current_monitor()
|
||||||
|
.and_then(|monitor| monitor.refresh_rate_millihertz())
|
||||||
|
.unwrap_or(60_000) as f64;
|
||||||
|
|
||||||
|
// Now convert it to micro seconds.
|
||||||
|
let monitor_vblank_interval =
|
||||||
|
Duration::from_micros((1000. * monitor_vblank_interval) as u64);
|
||||||
|
|
||||||
|
let swap_timeout = self.frame_timer.compute_timeout(monitor_vblank_interval);
|
||||||
|
|
||||||
|
let window_id = self.window.id();
|
||||||
|
let timer_id = TimerId::new(Topic::Frame, window_id);
|
||||||
|
let event = Event::new(EventType::Frame, window_id);
|
||||||
|
|
||||||
|
scheduler.schedule(event, swap_timeout, false, timer_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1534,6 +1560,54 @@ impl<T> DerefMut for Replaceable<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The frame timer state.
|
||||||
|
pub struct FrameTimer {
|
||||||
|
/// Base timestamp used to compute sync points.
|
||||||
|
base: Instant,
|
||||||
|
|
||||||
|
/// The last timestamp we synced to.
|
||||||
|
last_synced_timestamp: Instant,
|
||||||
|
|
||||||
|
/// The refresh rate we've used to compute sync timestamps.
|
||||||
|
refresh_interval: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameTimer {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let now = Instant::now();
|
||||||
|
Self { base: now, last_synced_timestamp: now, refresh_interval: Duration::ZERO }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the delay that we should use to achieve the target frame
|
||||||
|
/// rate.
|
||||||
|
pub fn compute_timeout(&mut self, refresh_interval: Duration) -> Duration {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
// Handle refresh rate change.
|
||||||
|
if self.refresh_interval != refresh_interval {
|
||||||
|
self.base = now;
|
||||||
|
self.last_synced_timestamp = now;
|
||||||
|
self.refresh_interval = refresh_interval;
|
||||||
|
return refresh_interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next_frame = self.last_synced_timestamp + self.refresh_interval;
|
||||||
|
|
||||||
|
if next_frame < now {
|
||||||
|
// Redraw immediately if we haven't drawn in over `refresh_interval` microseconds.
|
||||||
|
let elapsed_micros = (now - self.base).as_micros() as u64;
|
||||||
|
let refresh_micros = self.refresh_interval.as_micros() as u64;
|
||||||
|
self.last_synced_timestamp =
|
||||||
|
now - Duration::from_micros(elapsed_micros % refresh_micros);
|
||||||
|
Duration::ZERO
|
||||||
|
} else {
|
||||||
|
// Redraw on the next `refresh_interval` clock tick.
|
||||||
|
self.last_synced_timestamp = next_frame;
|
||||||
|
next_frame - now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate the cell dimensions based on font metrics.
|
/// Calculate the cell dimensions based on font metrics.
|
||||||
///
|
///
|
||||||
/// This will return a tuple of the cell width and height.
|
/// This will return a tuple of the cell width and height.
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
#[rustfmt::skip]
|
|
||||||
#[cfg(not(any(target_os = "macos", windows)))]
|
#[cfg(not(any(target_os = "macos", windows)))]
|
||||||
use {
|
use winit::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
|
||||||
std::sync::atomic::AtomicBool,
|
|
||||||
std::sync::Arc,
|
|
||||||
|
|
||||||
winit::platform::unix::{WindowBuilderExtUnix, WindowExtUnix},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||||
|
@ -28,6 +22,8 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use cocoa::base::{id, NO, YES};
|
use cocoa::base::{id, NO, YES};
|
||||||
|
@ -37,6 +33,7 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||||
|
|
||||||
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
use winit::dpi::{PhysicalPosition, PhysicalSize};
|
||||||
use winit::event_loop::EventLoopWindowTarget;
|
use winit::event_loop::EventLoopWindowTarget;
|
||||||
|
use winit::monitor::MonitorHandle;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use winit::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS};
|
use winit::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -106,9 +103,8 @@ impl From<crossfont::Error> for Error {
|
||||||
///
|
///
|
||||||
/// Wraps the underlying windowing library to provide a stable API in Alacritty.
|
/// Wraps the underlying windowing library to provide a stable API in Alacritty.
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
/// Flag tracking frame redraw requests from Wayland compositor.
|
/// Flag tracking that we have a frame we can draw.
|
||||||
#[cfg(not(any(target_os = "macos", windows)))]
|
pub has_frame: Arc<AtomicBool>,
|
||||||
pub should_draw: Arc<AtomicBool>,
|
|
||||||
|
|
||||||
/// Attached Wayland surface to request new frame events.
|
/// Attached Wayland surface to request new frame events.
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||||
|
@ -194,8 +190,7 @@ impl Window {
|
||||||
mouse_visible: true,
|
mouse_visible: true,
|
||||||
window,
|
window,
|
||||||
title: identity.title,
|
title: identity.title,
|
||||||
#[cfg(not(any(target_os = "macos", windows)))]
|
has_frame: Arc::new(AtomicBool::new(true)),
|
||||||
should_draw: Arc::new(AtomicBool::new(true)),
|
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||||
wayland_surface,
|
wayland_surface,
|
||||||
scale_factor,
|
scale_factor,
|
||||||
|
@ -388,6 +383,10 @@ impl Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||||
|
self.window.current_monitor()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn set_simple_fullscreen(&self, simple_fullscreen: bool) {
|
pub fn set_simple_fullscreen(&self, simple_fullscreen: bool) {
|
||||||
self.window.set_simple_fullscreen(simple_fullscreen);
|
self.window.set_simple_fullscreen(simple_fullscreen);
|
||||||
|
|
|
@ -10,6 +10,7 @@ use std::fmt::Debug;
|
||||||
use std::os::unix::io::RawFd;
|
use std::os::unix::io::RawFd;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{env, f32, mem};
|
use std::{env, f32, mem};
|
||||||
|
|
||||||
|
@ -101,6 +102,7 @@ pub enum EventType {
|
||||||
BlinkCursor,
|
BlinkCursor,
|
||||||
BlinkCursorTimeout,
|
BlinkCursorTimeout,
|
||||||
SearchNext,
|
SearchNext,
|
||||||
|
Frame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TerminalEvent> for EventType {
|
impl From<TerminalEvent> for EventType {
|
||||||
|
@ -1096,6 +1098,9 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
|
||||||
|
|
||||||
self.ctx.window().scale_factor = scale_factor;
|
self.ctx.window().scale_factor = scale_factor;
|
||||||
},
|
},
|
||||||
|
EventType::Frame => {
|
||||||
|
self.ctx.display.window.has_frame.store(true, Ordering::Relaxed);
|
||||||
|
},
|
||||||
EventType::SearchNext => self.ctx.goto_match(None),
|
EventType::SearchNext => self.ctx.goto_match(None),
|
||||||
EventType::Scroll(scroll) => self.ctx.scroll(scroll),
|
EventType::Scroll(scroll) => self.ctx.scroll(scroll),
|
||||||
EventType::BlinkCursor => {
|
EventType::BlinkCursor => {
|
||||||
|
@ -1450,11 +1455,6 @@ impl Processor {
|
||||||
},
|
},
|
||||||
// Process all pending events.
|
// Process all pending events.
|
||||||
WinitEvent::RedrawEventsCleared => {
|
WinitEvent::RedrawEventsCleared => {
|
||||||
*control_flow = match scheduler.update() {
|
|
||||||
Some(instant) => ControlFlow::WaitUntil(instant),
|
|
||||||
None => ControlFlow::Wait,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check for pending frame callbacks on Wayland.
|
// Check for pending frame callbacks on Wayland.
|
||||||
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
|
||||||
if let Some(wayland_event_queue) = self.wayland_event_queue.as_mut() {
|
if let Some(wayland_event_queue) = self.wayland_event_queue.as_mut() {
|
||||||
|
@ -1473,6 +1473,13 @@ impl Processor {
|
||||||
WinitEvent::RedrawEventsCleared,
|
WinitEvent::RedrawEventsCleared,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the scheduler after event processing to ensure
|
||||||
|
// the event loop deadline is as accurate as possible.
|
||||||
|
*control_flow = match scheduler.update() {
|
||||||
|
Some(instant) => ControlFlow::WaitUntil(instant),
|
||||||
|
None => ControlFlow::Wait,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
// Process config update.
|
// Process config update.
|
||||||
WinitEvent::UserEvent(Event { payload: EventType::ConfigReload(path), .. }) => {
|
WinitEvent::UserEvent(Event { payload: EventType::ConfigReload(path), .. }) => {
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub enum Topic {
|
||||||
DelayedSearch,
|
DelayedSearch,
|
||||||
BlinkCursor,
|
BlinkCursor,
|
||||||
BlinkTimeout,
|
BlinkTimeout,
|
||||||
|
Frame,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Event scheduled to be emitted at a specific time.
|
/// Event scheduled to be emitted at a specific time.
|
||||||
|
|
|
@ -7,7 +7,6 @@ use std::mem;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
#[cfg(not(any(target_os = "macos", windows)))]
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -478,9 +477,8 @@ impl WindowContext {
|
||||||
self.mouse.hint_highlight_dirty = false;
|
self.mouse.hint_highlight_dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip rendering on Wayland until we get frame event from compositor.
|
// Skip rendering until we get a new frame.
|
||||||
#[cfg(not(any(target_os = "macos", windows)))]
|
if !self.display.window.has_frame.load(Ordering::Relaxed) {
|
||||||
if self.display.is_wayland && !self.display.window.should_draw.load(Ordering::Relaxed) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,8 +493,14 @@ impl WindowContext {
|
||||||
self.display.window.request_redraw();
|
self.display.window.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redraw screen.
|
// Redraw the window.
|
||||||
self.display.draw(terminal, &self.message_buffer, &self.config, &self.search_state);
|
self.display.draw(
|
||||||
|
terminal,
|
||||||
|
scheduler,
|
||||||
|
&self.message_buffer,
|
||||||
|
&self.config,
|
||||||
|
&self.search_state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue