-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Synchronize updates with terminal clients #598
Comments
I've experienced this as well. This bug turns out to be an interesting problem! To see why we get the flickering and why it's difficult to resolve, consider how the read/parse/draw loop works:
The call at the end to swap buffers will block for ~17ms, and another frame cannot be drawn until it returns. The flickering happens because:
To me, this seems like a problem with terminal protocol. There's no framing, so we cannot know whether more data is coming or not. Since we can't fix terminal protocol, we're left with doing something heuristical like trying to read with a short timeout. The trick with such a heuristic is providing sufficient time for related data to arrive without ever causing a frame skip. If you or anyone else has other ideas on how to address this, or even ideas about how such a heuristic could work, I'm all ears. I've been thinking about this problem for a while and haven't come up with a solution I love. |
@fuine I'm using this plugin, and not experienced the flicker with it, might want to check it out: https://github.com/yuttie/comfortable-motion.vim. It's also pretty cool to use. |
@jwilm thanks for the explanation, I will think about that problem. @asilvadesigns I've tried that plugin before but I don't like the CPU overhead that it brings to the table |
There is also ongoing work in neovim to improve the terminal gui. For example: When using alacritty in neovim I notice a lot more cursor flickering than with other terminals. However, I think this is a problem with vim/neovim because there as been quite a few pull requests that tries to clean up the terminal ui code so that the cursor doesn't jump around so much when drawing updates. (Like this one: neovim/neovim#6366) Speaking of which, @jwilm could there perhaps be a debug mode in alacritty where you can step though the screen updates? That is, you can freeze the screen and then step through the cell updates. I think it would be really helpful when debugging flickering issues in neovim (so you can see what neovim does when it redraws the cursor position for example). |
If your program operates as described, which I am taking on trust and have not looked at your program to check, then this does have its roots in your program. Programs like vim and nvim collect screen output into in-memory buffers and flush it at certain points. (This is their own buffering system. Of course, full-screen programs that use standard I/O buffering instead, still do the same thing.) This results (as one can see from tools like Your best strategy in receiving this is to attempt to read and process all of that lump in one go before letting your renderer lock the terminal state for a vertical blanking interval. It is a falsehood to say that there's no way of doing this. There are three different approaches, each with their own tradeoffs:
Instead of simply reading a portion, processing it whilst holding a lock, sending the other thread a shoulder-tap, then going back to look for another portion; make your program repeatedly read and process all portions until the master side of the pseudo-terminal is drained of output sent from the slave side, and only send the shoulder-tap when that is the case. Or make your rendering side use a snapshot of the terminal state, and not lock the terminal state for the entire length of time that takes to render. |
That's actually how alacritty currently works: https://github.com/jwilm/alacritty/blob/3c8514ba103d5d987d2269c421a3cc09e6784904/src/event_loop.rs#L259-L307 However, I think the idea of snapshotting the terminal state instead of holding a lock sound promising. |
No, that's not how that code works. That reads a portion, processes it whilst holding a lock, sends the other thread a shoulder-tap the first time around, and then releases the lock and goes back to look for another portion. That's not the same as sending the shoulder tap after the master has been drained; it is not the same as checking that there's no more to read before sending a shoulder tap. Moreover, programs like Programs such as So the problem behaviour is as described. You are starting the render, locking the terminal state for a vertical blanking interval, when there's more than half of the |
@jdebp thanks for all the feedback. I'm working on this right now, and I think the way to go is just reading until getting an EWOULDBLOCK.
This sounds great, but there's no equivalent for epoll.
Doesn't work in my testing on macOS. Upon the event loop signaling read readiness, the ioctl just returns 0.
I've been experimenting with this approach locally with good results. Note that
I'll follow up again here once there's a patch for others to try. |
Please see #650 for the patch I promised. Although it seems to resolve the issue with I'm looking for some specific feedback on the patch. It seems that, on my system, this patch causes some hiccups where a frame is skipped, or input isn't delivered in a timely fashion. Could have been my imagination too. Would love to hear how it works for others! |
@jwilm no flicker in neovim, or neovim in tmux!! |
@jwilm I get very occasional flickers on |
FWIW, applying #650 on top of the current master helped with the flickering issues I previously had with regular vim 8.0 inside tmux 2.5 when scrolling files up and down. There's still some flickering, but the patch helped a lot.
|
I am not experiencing any more flickers while scrolling since #1403 merge. Can anyone confirm this? |
@fuine Has this issue been resolved for you with the latest master? |
Closing as resolved by #1403. Should this issue still occur, please let me know and I'll re-open. |
I'm on MacOS, this fix the flickering issue startup_mode: SimpleFullscreen |
No? Rendering a frame does not take a fixed amount of time, it depends on vsync which is its purpose. We might have approximately the same amount of time between frames, but even that would only be true if every frame was dirty. While it might be possible to delay the rendering a bit, up to the point where vsync blocks for basically zero seconds, this is not reliable, hard to determine consistently and would only give an extremely small timeframe that likely isn't going to help much. XTerm's 25ms are massive for example. No matter how far away you are from the next vsync, that'll cause troubles. Which means frames are actually delayed, causing increased input latency.
Not working, yes. No matter how long you delay, it will never stop you from an application pushing new stuff right when you decide to render.
Delaying rendering is not a solution. It's a hack that should help users perceive the problem less in some scenarios. The issue persists, it's just less prominent. That is not what a solution is.
Just like the assumption that all connected data will be done in a single write. Completely pointless if you have to make assumptions about a client you have no control over.
The suggestion is just that fast terminals make it more obvious, which is true based on my testing. |
Obviously, if you include the wait time caused by vsync. But that's an implementation artifact. Actual rendering is more or less fixed. At least if we for simplicity assume for example a full window with no empty cells.
I did say it was tricky. My CPU-based terminal renders a full window update in ~5ms on a laptop with
Of course. It's a mitigation. Not a solution. While it's not "working", in theory, it actually does work in practice. But if it works in all the applications I use, then it is working. For me.
Yes, it's a hack. And I'll agree to not calling it a solution.
Except that in practice, clients aren't malicious entities trying to break your terminal. This isn't about providing a 100% fool proof, always working solution, but, yes, to hide the problem from the users. A temporary workaround until clients, and terminals, implement synchronized updates.
Did you check if they implement workarounds? |
That highly depends on the content of your full window and its size. Worst case you have basically no time to do any additional waiting and then just spend valuable resources that could have been used to actually improve the performance itself. That just doesn't align with Alacritty's goals. I'm fully willing to implement the escape as soon as it's standardized on though and I'm perfectly happy driving that forwards. I've got only limited time and the time I have I'd rather spend bothering people to drive forwards a proper solution rather than implementing a hack that doesn't work, goes against the project's goals and has only negative impacts on people that do not experience these issues.
I tested Alacritty against itself. So yeah, pretty sure it doesn't implement any hacks for this. |
To save a future reader/searcher of GitHub issues some time (and to save the maintainers having to answer the same thing twice ;-)... This issue makes most programs using ncurses susceptible to flickering. I did a bit of digging into ncurses itself to find out why this is the case; links below. By default, ncurses buffers up changes made to the screen, and writes out only the difference between the last screen and the current one on a call to A program can ask ncurses to not do this diffing, and instead redraw the entire screen, using That is to say, there is no obvious workaround here short of patching/refactoring ncurses itself to use a big enough buffer to perform the entire update in one go. |
In that case can this be reported to ncurses? |
Chiming in here, please do tell if you wan't me to provide any sorts of information! It's still happening for me (running alacritty 0.5 on Windows) Same behavior with scrolling in fzf as in #3819 (with and without tmux, doesn't matter) but it also happens when typing (filtering) in fzf. Highlighting things in vim with * causes the cursor to disappear completely and generally just moving with hjkl causes ghost cursors to appear (for me) both in bottom bar and in buffer/tab bar. Another interesting problem occurs when running tmux with 2 windows, one containing vim and one just a terminal running This, unfortunately, makes the terminal emulator borderline to unusable, as a daily driver, to me :-( I'm not sure if the disappearing cursor on highlight is related, or if it's a separate issue (let me know and i'll create one with more info) |
This adds support for the synchronized updates escape sequence (`DCS = 1 s`/`DCS = 2 s`). This allows terminal applications to freeze Alacritty's rendering temporarily to prevent excessive redraws and flickering. There seem to be two possible approaches for implementing this escape sequence. Some terminal emulators like Kitty have an internal escape sequence buffer which reads every byte, then upon receiving the freezing escape they stop reading from this buffer and keep going when the terminal is unfrozen or the buffer is full. The alternative implementation, which this patch has taken, is to freeze the rendering completely, while still updating the grid state in the background. The approach taken in this patch has the advantage that the number of bytes written can be optimally compressed, there's no need for an additional buffer and no risk of running out of buffer space and having to end synchronization prematurely. On the other hand since only the visible part of the terminal can be frozen because of performance and memory limitations, many user interactions with the terminal need to forcefully interrupt the synchronized update. While the advantages and disadvantages seem somewhat on par for both approaches and most limitations shouldn't be noticeable with the synchronized updates under normal operating conditions, the rendering freeze was much easier to implement efficiently in Alacritty. Since this is an escape sequence, all the rendering freezes must be handled by `alacritty_terminal` internally. Because of this a new intermediate `RenderableContent` struct has been introduced which contains all the information necessary for rendering the visible terminal region. This single interface allows for decoupling the integration between terminal and rendering and at the same time makes it possible to clone the entire structure to preserve during a freeze. Fixes alacritty#598.
This adds support for the synchronized updates escape sequence (`DCS = 1 s ST`/`DCS = 2 s ST`). This allows terminal applications to freeze Alacritty's rendering temporarily to prevent excessive redraws and flickering. There seem to be two possible approaches for implementing this escape sequence. Some terminal emulators like Kitty have an internal escape sequence buffer which reads every byte, then upon receiving the freezing escape they stop reading from this buffer and keep going when the terminal is unfrozen or the buffer is full. The alternative implementation, which this patch has taken, is to freeze the rendering completely, while still updating the grid state in the background. The approach taken in this patch has the advantage that the number of bytes written can be optimally compressed, there's no need for an additional buffer and no risk of running out of buffer space and having to end synchronization prematurely. On the other hand since only the visible part of the terminal can be frozen because of performance and memory limitations, many user interactions with the terminal need to forcefully interrupt the synchronized update. While the advantages and disadvantages seem somewhat on par for both approaches and most limitations shouldn't be noticeable with the synchronized updates under normal operating conditions, the rendering freeze was much easier to implement efficiently in Alacritty. Since this is an escape sequence, all the rendering freezes must be handled by `alacritty_terminal` internally. Because of this a new intermediate `RenderableContent` struct has been introduced which contains all the information necessary for rendering the visible terminal region. This single interface allows for decoupling the integration between terminal and rendering and at the same time makes it possible to clone the entire structure to preserve during a freeze. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes alacritty#598.
This implements support for temporarily freezing the terminal grid to prevent rendering of incomplete frames. This can be triggered using the escapes `DCS = 1 s` (start) and `DCS = 2 s` (end). The synchronization is implemented by forwarding all received PTY bytes to a 2 MiB buffer. This should allow updating the entire grid even if it is fairly dense. Unfortunately this also means that another branch is necessary in Alacritty's parser which does have a slight performance impact. In a previous version the freezing was implemented by caching the renderable grid state whenever a synchronized update is started. While this strategy makes it possible to implement this without any performance impact without synchronized updates, a significant performance overhead is introduced whenever a synchronized update is started. Since this can happen thousands of times per frame, it is not a feasible solution. While it would be possible to render at most one synchronized update per frame, it is possible that another synchronized update comes in at any time and stays active for an extended period. As a result the state visible before the long synchronization would be the first received update per frame, not the last, which could lead to the user missing important information during the long freezing interval. Fixes #598.
Whenever I use
<C-d>
or<C-f>
in vim while scrolling through a large file the screen flickers - it looks like for a moment the screen goes black and then the new content is redrawn, but it takes enough time to be noticeable and annoying. What's perhaps more interesting is that when I scroll half of the page via<C-d>
only the bottom part of the window flickers, while top one does not. This issue is observed both inside and outside of tmux. Other terminal emulators don't have this issue.alacritty
is built withcargo
from sources, repo at commit d5ab418.4.10.13-1-ARCH
intel (unloaded: modesetting)
Mesa DRI Intel Ivybridge Mobile
3.0 Mesa 17.1.0
I have also reproduced it on a different machine, with nvidia's GPU, alas low in terms of performance:
4.11.2-1-ARCH
nvidia
Quadro 600/PCIe/SSE2
4.5.0 NVIDIA 381.22
Yes
Do you have any idea what could be a problem here? This issue prevents me from using
alacritty
on a daily basis, because said flashing constantly distracts me.This problem is also mentioned in #555
The text was updated successfully, but these errors were encountered: