為什麼終端模擬器仍然有螢幕閃爍?
為什麼終端仿真器繪製基於文本的應用程序時仍然存在視覺偽影?這在最近渲染 3D 遊戲和 GUI 視窗的電腦上,包括沒有偽影的抗鋸齒矢量字型。
我經常看到以下工件,它們揭示了螢幕更新過程的中間步驟:
- 終端游標移動(更新期間游標在螢幕上閃爍或跳躍)
- 撕裂(螢幕的一部分顯示舊內容,而另一部分顯示新內容)
- 滾動(滾動很明顯,而不是立即顯示新的滾動位置)
這些偽影僅在亞秒間隔內可見,在大多數螢幕更新期間不會出現,但在無閃爍 GUI 上長大後,我仍然想知道如何避免它們。一旦開始繪製更複雜的螢幕,就可以在例如以下 ASCIInema 影片中看到上述所有工件(滾動除外):MapSCII - 控制台中的整個世界!
我也特別不是在談論緩慢的更新。如果更新總是即時的,那就太好了,但由於網路和處理延遲,這並不總是可能的。我的意思是,部分繪製的螢幕通常會在短時間內可見。在大多數現代 GUI 中,只向使用者顯示完全完成的螢幕,部分繪圖的工件非常罕見。
我的印像是終端仿真管道是這樣的:
- 使用者按下鍵盤上的鍵
- 核心將按鍵從鍵盤驅動程序傳遞到視窗系統
- 視窗系統將按鍵傳遞給終端仿真器
- 終端模擬器將按鍵傳遞給偽終端(pty)核心設備
- Pty 解釋按鍵並將結果傳遞給基於文本的應用程序
- 應用程序響應按鍵執行命令
- 應用程序將新螢幕(字元單元格)呈現到內部緩衝區
- 應用程序呼叫
curses
或其他庫將字元單元格網格轉換為 ANSI 轉義碼,這將在終端上呈現等效螢幕- 庫將這些 ANSI 轉義碼寫入 pty 設備
- Pty 以某種方式處理寫入的數據
- 終端模擬器以某些塊從 pty 讀取處理後的數據
- 終端仿真器呼叫視窗系統在終端視窗中渲染 ANSI 轉義碼的結果
上述哪個步驟可以減慢程序,使終端仿真器向我們顯示中間渲染步驟,而不是只顯示最終結果?
- 似乎硬體終端(串列埠連接)的速度取決於它們的波特率,可以改變
tcsetattr()
但我從多個來源中了解到波特率設置對終端使用的偽終端(pty)設備沒有影響模擬器。這是否意味著 Unix 核心不會故意限制 pty 通信的速率?- 應用程序或渲染庫(詛咒等)是否在多次寫入中發送文本和 ANSI 程式碼,而不是嘗試只使用一個
write()
?- Unix 核心對其內部 I/O 緩衝區有大小限制,這會影響諸如可以通過管道發送而不會阻塞的最大數據量。這是否會影響渲染具有大量細節的終端螢幕(一屏文本、大量顏色等)?我想組合的文本和 ANSI 轉義碼可能包含太多數據,以至於它不適合 pty 驅動程序的緩衝區,這會將螢幕更新拆分為應用程序的多個寫入操作和終端仿真器的多個讀取操作。如果終端仿真器急於在處理下一次讀取之前顯示每次讀取的結果,這將導致顯示閃爍,直到處理完批處理中的最終讀取。
- 終端仿真器或 pty 驅動程序是否有故意的批處理超時,以便它們的行為更接近於模擬硬體終端,感覺更自然,或者解決一些其他被認為比顯示速度更重要的問題?
最近有一些努力使新的終端仿真器渲染速度更快(例如,通過將字型預渲染到影片記憶體中的 OpenGL 紋理中)。但這些努力似乎只是在計算網格後加速將字元單元網格渲染到螢幕點陣圖上。
似乎還有其他事情正在發生,即使在非常快的電腦上,這些東西也會從根本上變慢。想一想:如果終端仿真器在將任何內容渲染到螢幕點陣圖之前處理所有 ANSI 程式碼以獲得字元單元格網格,那麼字元網格到點陣圖的渲染常式有多慢並不重要 - 應該有沒有閃爍(至少不是那種明顯對應於硬體終端上的游標移動的閃爍,這是我們經常看到的)。即使終端模擬器花了整整一秒鐘在螢幕上繪製任何給定的字元單元格網格,我們也只會得到一秒鐘的不活動,而不是一秒鐘的閃爍。
一個類似的問題是 Unix
clear
和reset
命令的執行速度非常慢(從 GUI 使用者的角度來看,它們不會做任何比重繪點陣圖更複雜的事情)。也許出於相關原因。
我很想听到更多詳細資訊如何準確觸發如此明顯的閃爍,因為我在使用我的系統時沒有註意到。
在我的系統上,VTE(GNOME 終端背後的引擎)可以處理大約 10 MB/s 的傳入數據。其他模擬器的性能也與此相差不遠,可能在兩個方向上的 3 或 5 倍之內。這對於無閃爍更新來說應該綽綽有餘。
請記住,全屏終端可能包含數万個字元單元。UTF-8 字元由多個字節組成。切換到不同的屬性(顏色、粗體等)需要轉義序列,可以從 3-4 字節輕鬆到 10-20 字節(尤其是 256 色和真彩色擴展)。因此,真正複雜的佈局可能需要 100 kB 甚至更大的流量。這肯定不能一步通過 tty 線。我什至不確定某些應用程序(或螢幕繪圖庫)是否願意在一個步驟中緩衝整個輸出。也許他們只是使用 printf() 並讓 stdio 在每 8 kB 左右刷新一次。這可能是他們有點慢的另一個原因。
我不太熟悉核心的調度行為,例如它是否需要在兩個程序以及使用者/核心模式之間來回切換,或者它們是否可以在多執行緒 CPU 上同時執行。我真的希望它們可以同時在多核 CPU 上執行,現在大多數 CPU 都是這樣。
故事中沒有刻意的節流。但是,當模擬器決定是繼續讀取數據還是更新螢幕時,可能會有猜測。例如,如果終端模擬器處理輸入的速度快於應用程序發出的速度,它會在處理第一個塊後看到它停止,因此可能會合理地決定更新其 UI。
游標可能是最突出的閃爍,因為隨著內容的更新,游標會沿著螢幕移動。它不能停留在同一個地方。如果模擬器在接收輸入數據時只更新一次螢幕,並且游標最終保持在同一位置,則這種閃爍很可能會變得可見。
如果終端仿真器和內部執行的應用程序都支持,您可能會對這個原子更新提案(此處討論)感興趣,該提案將主要解決此問題。
您可能還對為什麼由於鍵盤重複率和顯示器刷新率之間的干擾,鍵盤的滾動體驗必然會變得不穩定感興趣,這本身並沒有閃爍,但會導致不愉快的體驗。