-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve dirty detection when scrolling and typing characters #6206
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of these are scenarios where the user is interactively using Alacritty, which usually makes a couple extra redraws completely insignificant.
Is there a deeper reason behind these changes, or are you just trying to narrow down unnecessary dirty flags?
alacritty_terminal/src/term/mod.rs
Outdated
/// Scrolls the terminal display verically. | ||
/// | ||
/// A boolean is returned that indicates whether the terminal is dirty after this change and | ||
/// needs to be redrawn. | ||
#[inline] | ||
pub fn scroll_display(&mut self, scroll: Scroll) | ||
pub fn scroll_display(&mut self, scroll: Scroll) -> bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Way too much complexity for something so insignificant. Why not just check if scrolling is necessary before calling the method?
For the scroll and mouse press, the idea was that less redraws would be better for battery life. However, I haven't tested this so I don't know. For the typing, it's to reduce latency when typing if VSync is enabled. Imagine the following scenario:
In fact, I feel like there is a slight difference in latency between master and this, but I can't tell for sure. |
Redraws will still be limited by refresh rate. So a couple extra redraws should be pretty insignificant.
"Feel like" is a notoriously terrible metric for latency. And all of these latency suggestions are irrelevant for Wayland and other future systems where blocking on vsync isn't used. |
Ok, I can remove these parts.
Sure. What I mean is that not having the extra redraw should reduce the typing latency, and although I don't have a way to measure it, it feels like it works. Do you know a way to measure input latency on Wayland? I know typometer but it doesn't work on Wayland unfortunately.
Even if a frame request mechanism is used instead of blocking, there is still the same issue, because Alacritty will wait until the frame callback to start rendering, and frame callbacks are usually fired right after V-Blank or some fixed time after V-Blank. On Wayland:
|
Just to be sure, you've compared master to your patch, and not v0.10.1? Since master has some optimizations wrt rendering on Wayland and composing. |
Ok, so I couldn't find a way to measure input latency on Wayland so I wrote this small tool. It types a character, measures the times it takes for it to appear in the terminal by checking if a rectangle is completely black (my alacritty config has a #000000 background), types backspace to remove the character, waits for it to disappear, and starts again. I can add documentation if you want to use it. Here is a graph comparing this PR to 40bbdce in terms of time it takes between the character is typed and it is detected in nanoseconds: The X axis is the iteration count. I don't know why the first few iterations are not the same as the rest. It seems that currently, there is one frame (16ms) of extra latency. |
Just to be sure. You offset one frame already? Since I'm not sure how this tool works, but I know that you get a next frame, not the current one? So you should take that into account. + not sure if sway doesn't add this frame due to reasons. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I don't like the scrolling part of it, but the keyboard part makes sense, but the integration is a bit questionable.
In general what should be done.
- The
received_char
method shouldn't mark the terminal dirty. - The marking is done via
on_typing_start
, we should make all the callers of that function to not rely on that behavior by setting dirty bits themselves(they are likely doing so already). - The
on_typing_start
function should set dirty only when a cursor shape chages.
The scrolling should likely be removed since there's no latency addition from the mouse/selection, because the result is visible immediately.
However when writing to pty there's a roundtrip, since pty should write you back. That's why you don't need to mark dirty
when writing to the pty. Though, you should remembed that pty may not write you back, so you shouldn't straight clear the dirty bit in those cases, but rahther not set it.
It sends a key press, and then whenever it receives a new frame, it checks if the character is in the frame. The character can't be in the current frame because it was sent after the current frame was rendered. Sorry, I'm not too sure I understood what you said. I added a
Just to be sure, 1. and 3. is already good and I should add alacritty/alacritty/src/input.rs Lines 143 to 200 in 40bbdce
Actually, it's the same thing for the scrolling. If you use a touchpad or a wheel that doesn't scroll in lines, and you start scrolling:
If you remove step 2, then Alacritty doesn't have to wait at step 5 because it has already received a frame event possible from a long time ago. |
I was trying to say that you need to have a timing offset between a frame and a key event due to the protocol limitations.
So you should prevent it from setting dirty in that particular case then. Not to alter function that actually scroll to return information whether it scrolled or not. |
Unfortunatelly it doesn't work for me. It never sends any key events and just processes dmabufs :/ . It does create virtual keyboard manager. Maybe my system is too fast for it do anything, idk. |
I don't know the limitations but here is a measurement on my PC with a random delay (between 50ms and 100ms) added before every key press and release: With averages:
There is also another case which is when you scroll past the viewport. It doesn't really matter for latency but it's still extra redraws. Just to be sure, you don't care about those redraws so a check about scrolling past the viewport should not be added, right?
Weird. I'm using sway too. I added a little bit of error printing so could you try again quickly to check if it shows an error please? If not, then I don't really know. I definitely don't know what I'm doing with dmabufs, OpenGL, and the XKB layout stuff so I don't know. Also sorry the code is a mess. |
In general I think something like this should do basically what you're doing. diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index b4248dc7..270b92be 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -652,12 +652,11 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
if self.scheduler.unschedule(timer_id).is_some() {
self.schedule_blinking();
self.display.cursor_hidden = false;
+ *self.dirty = true;
} else if *self.cursor_blink_timed_out {
self.update_cursor_blinking();
}
- *self.dirty = true;
-
// Hide mouse cursor.
if self.config.mouse.hide_when_typing {
self.display.window.set_mouse_visible(false);
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index 37cd4f92..a6338ac5 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -141,7 +141,6 @@ impl<T: EventListener> Execute<T> for Action {
match self {
Action::Esc(s) => {
ctx.on_typing_start();
-
ctx.clear_selection();
ctx.scroll(Scroll::Bottom);
ctx.write_to_pty(s.clone().into_bytes())
@@ -686,9 +685,12 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
let multiplier = f64::from(self.ctx.config().terminal_config.scrolling.multiplier);
self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier;
- let lines = self.ctx.mouse().scroll_px / height;
+ let lines = (self.ctx.mouse().scroll_px / height) as i32;
- self.ctx.scroll(Scroll::Delta(lines as i32));
+ // Scroll only if we've actually scrolled.
+ if lines != 0 {
+ self.ctx.scroll(Scroll::Delta(lines as i32));
+ }
}
self.ctx.mouse_mut().scroll_px %= height;
|
Wrt tool it outputs for me |
Ok, I force pushed my branch with this diff. Note that only doing self.display.cursor_hidden = false;
+ *self.dirty = true;
} else if *self.cursor_blink_timed_out { won't work because the aim is to not always set the dirty flag, but only when the Also, in I respectfully still find it a bit unfortunate that Alacritty will do extra redraws when the user press/release the left mouse button, when they try to scrolls past the viewport, and when they type the first character in a series and the cursor timed out, which are cases the previous patch fixed, because it's wasting CPU and GPU cycles for nothing, but I understand that there can be different possible opinions on that matter. |
Could you also add. diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index a6338ac5..7244f3e7 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -817,6 +817,10 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
return;
}
+ // XXX when we're writing keyboard input to pty we must not mark the terminal as dirty
+ // unless there's a need for visual update, like clearing a selection. Or scrolling to
+ // the viewport to bottom.
+
self.ctx.on_typing_start();
if self.ctx.terminal().grid().display_offset() != 0 {
|
Check if the selection was changed before setting the dirty flag.
In general I like your initiative and it makes sense. The point is that the approach originally is a bit misleading and we need a policy to prevent such breakages in a future. So I think what should be done is the following (you can leave it up to us if you don't feel like):
And we should clearly document how and when dirty bit should be used, since right now it's more of a history spread around event.rs and input.rs, since this bit setting migrates with the code ¯_(ツ)_/¯ it was originally in. Would like to hear for @chrisduerr here before continuing. |
I don't think there's much need for documenting how the dirty bit should be used, especially because there isn't really any good place to put that documentation. It should be relatively clear that this triggers redraws for the UI, so it should be used whenever that is necessary. As far as excessive redraws are concerned that don't actually affect anything, I really don't care. If it's easy to fix, sure, but if it introduces a bunch of pointless code just to save a single redraw that is hit like once a month, then I think you're missing the purpose of the flag. |
Yeah, that's the problem. The idea is to move dirty setting only to the place mutation will be performed, so we the docs I think of is to make a note that it should be placed when the actual mutation is happening, since right now we have them all over the place.
I think it should remove code and not add it, since right now we have dirty set in lots of events, but those events don't mutate anything, they only call functions which may mutate and require a redraw. So @greg904 if you want to take a look go ahead, otherwise I'll open an issue and go to it at some point. |
For the cases where it simplifies things I obviously have no problem with making things more granular. But most places that are kinda coarse right now are that way because it's the simpler implementation and doesn't make any performance difference. |
Correct me if I'm wrong @kchibisov but in the buffer age PR #5863, there seems to be a struggle because of the lack of signalling of what cells exactly are dirty for the stuff that is not in the terminal (hints, search, message bar, ...). It seems like it hacks a bit around the architecture by looking inside Display::draw at what has been mutated after the fact, and marking it as dirty there, instead of doing it while mutating which would be less error prone. In fact, I assume that it's why there is still some stuff that doesn't work and it's hard to fix. So maybe the dirty mechanism is not even the right mechanism for the future? Maybe it should be replace by keep track of the exact cells damaged to help the buffer age use case? |
@greg904 we do keep track of the damage. And buffer_age works, the problem is that damage tracking works on a cell level, however it should work on the glyph level, and that's the main issue here. I have plans for buffer age and I know how to fix it, I just not there yet, since it I don't want to dive into it that much, and it doesn't really bothers me right now(low priority, since it reduces not that much on my machine). Like the signaling should be done by maintaining a damage ring on the display and all the visible updates should go into there likely damaging. I'll go back to that issue once I have time and energy for it. But improving dirty handling would help here, since it'll be easier to handle UI damage. (Terminal damage is very well handled). Another issue is that terminal size and UI size doesn't always match, so the termianl damage is a subset of UI damage. I've already taken steps to help me here, but moving the entire |
Yeah, what I mean is that with the current design there would be two places where damage would be tracked for a UI change (not the terminal's contents itself). For example, to underline the hints:
This could be error prone because someone could forget one place when modifying the code. Wouldn't it be better to, calculate the damage rects directly in 1. (when we are actually mutating the UI), add it to some "pending UI damage rects", then |
I mean it'll be in one place? The UI will maintain a ring buffer of damages and the terminal damage will extended it basically? And things, etc, etc will basically be added to that damage. Like it all will be in one place, just requires me to write it at this point :/. Another part of that issue is that you need to go throughout of alacritty_terminal and actually understand how the entire occ stuff works wrt background colors, so we can optimize the rest of the damage. |
Ok so I made another version of this PR which overall should simplify the code while also removing the same redraws at #6208. It should also do what's requested in this comment #6206 (comment). I closed this one because I don't want to open too many PRs open about the same thing at the same time but if you don't like the approach in the new PR, I can close it and reopen this one. |
Alacritty currently redraws the terminal:
This can be seen using the
option in the configuration file.
Stop unconditionally setting the dirty flag in those cases.
I tested scrolling, cursor blinking with and without Vi Mode, and selection and haven't found bugs.