Ghostty Devlog 002

Mitchell Hashimoto

Ghostty 開發日誌 002

哈囉!歡迎來到 Ghostty 的第二篇官方開發日誌 👻!

如果你錯過了第一篇開發日誌,或想更了解 Ghostty 是什麼,請參考本網站上的 Ghostty 介紹頁面


非技術部分:公開演講、社群經營

我們馬上就會深入探討技術細節,不過我想先分享一些非技術面的近況,因為有幾個令人非常興奮的消息!

首先,我在 Handmade Boston 首次公開談論 Ghostty。你可以在這裡觀看 Twitch 錄影(從 2:45:30 開始)。這場演講比較像是圍繞終端機整體的討論,並沒有太多專門介紹 Ghostty 的內容,不過如果你想聽聽我對終端機的理念,以及我為何開始並持續投入 Ghostty 的開發,歡迎去看看。

接著,有幾位社群成員已經開始著手建立 Ghostty 網站、公開的 Discord(即使你不在 Beta 測試名單內也能加入),以及加入 Beta 測試的等候名單。雖然目前還在早期階段,但這些都將是專案邁向正式發布過程中的重要環節。在那之前,也很期待能與你們見面、聊聊!

至於 Beta 測試的參與資格……也許在下一篇開發日誌中會有更多消息可以分享。我們正在努力中。之所以說「我們」,是因為 Ghostty 社群正在成長,有一些社群成員正協助我準備迎接越來越多的人加入。真有趣!

我也經常被問到 Ghostty 的發布計畫:是否會免費、是否會開源等等?答案是:是的,都是!Ghostty 將會是免費且開源的(兼具自由如言論自由、免費如免費啤酒的兩種意義)。我們尚未決定採用哪種授權條款,不過我已開放讓 Beta 測試成員提供建議。目前我們傾向於 GPLv3,但還沒有做出最終決定。


macOS 上的非原生全螢幕(#215)

這項功能由 Thorsten Ball(托斯騰·鮑爾) 貢獻。

在其他 macOS 終端機模擬器中,有一項常被稱為「non-native fullscreen(非原生全螢幕)」的功能。用視覺展示最容易理解。以下是原生或傳統的 macOS 全螢幕

接著則是全新的非原生全螢幕

這兩種方式各有優缺點,因此在 Ghostty 中這是一個可設定的選項。特別是,非原生全螢幕的速度非常快、允許其他程式浮動顯示在最上層,而且不會建立新的「桌面」。不過,在全螢幕模式下你無法使用分頁(只能使用分割窗格),直到我們寫出自訂的分頁列控制器為止(徵求協助!)。

結果實作起來相當棘手。表面上看起來非常簡單:只要透過程式修改一些視窗屬性就行了。如果你上網搜尋,讓這種行為發生的程式碼範例大概就十幾行而已。但 Ghostty 的 PR 卻是 +802/-239。怎麼會這樣?

如果你有關注 Ghostty,我曾很自豪地談過 Ghostty 是以 Zig 撰寫,但 macOS 的圖形介面則使用 SwiftUI。Ghostty(在圖形介面部分)曾是 100% 的 SwiftUI:Ghostty.app 的主要進入點是一個 SwiftUI 的 App 物件。問題在於,非原生全螢幕需要繼承 NSWindow,而使用 SwiftUI 時你根本做不到(或者說,目前還沒有人公開找出做法)。

因此,為了讓非原生全螢幕能夠運作,我們必須移除 SwiftUI 的應用程式與視窗生命週期管理,並改用老牌的 AppKit 自行重寫。請注意:我們仍在視圖上使用 SwiftUI,只是不再用它來管理視窗/應用程式的生命週期。這包括了啟動、多視窗建立、分頁列、選單列等等。

如果只是為了非原生全螢幕,這樣做大概不值得。但我們本來就因為 SwiftUI 而有一些潛在的錯誤或缺少的功能,這次的改動為我們提供了修復這些問題的途徑,所以我們認為值得這麼做。稍後我會再分享更多相關的改進,但先說一點:托斯騰·鮑爾得以刪除了名為 CursedMenuManager.swift 的檔案,這就很能說明這次改動背後更廣泛的意義。

所以我們現在有非原生全螢幕了!太棒了!但我們也因此擁有了一個更穩健的框架來建構新的 macOS 功能,所以這表面上看起來的成果,實際上帶來了更大的收穫。謝謝托斯騰·鮑爾!

喔對了,+802/-239 當中有 97% 是 Swift。我知道有很多人非常喜歡 Swift,但我個人還是更想用 Zig 來工作。而且 Xcode 並不好用。托斯騰·鮑爾也這麼認為。所以就這點而言,這項功能開發起來也不算太愉快,但我們之所以這麼做,是因為我們希望原生的 macOS 體驗能夠非常出色。


Linux 字型模糊與方塊圖示殘影(#178、#204)

大約 6 個月前,一位最早期的 Ghostty 測試者回報說他在 Linux 上看到字型有些微模糊以及圖像殘影:

請注意,上方的螢幕截圖有兩個問題。第一,字型是模糊的。但第二,在 NixOS 標誌中的方塊圖示下方不應該出現空白行。請參考下方我在沒有出現這些問題的機器上所擷取的畫面。

大量使用 Linux,卻完全沒有看到任何模糊的情況。我在多台硬體裝置上安裝了多個發行版,都無法重現這個問題。我甚至取得了回報者的完整作業系統設定並原封不動地執行,還是無法重現。我曾提到我懷疑這是 DPI 的問題,但當時就沒有再深入追究。在接下來的幾個月裡,我也曾幾次重新檢視這個問題,卻始終沒有弄清楚原因。

但後來我們找到了原因。具體來說,是托斯騰·鮑爾把它解開了。各位朋友,圍坐在營火旁吧,因為這是一個關於浮點數運算與浮點數/整數轉換其隱蔽本質的、非常重要的軟體工程課題。

在現代軟體中處理 DPI 是件麻煩事。數十年來,顯示器的 DPI 只有少數幾種可能,所有內容都是以無縮放(「1x」)的方式渲染。如今,顯示器的 DPI 差異極大,而且解析度非常高,因此使用者介面通常會套用縮放來渲染(「2x」)。更複雜的是,分數縮放(「1.25x」)現在也非常普遍。

因此,可攜式軟體通常會以為單位來要求尺寸設定(字型、內距等)。一般是 1/72 英吋。DPI 是每英吋點數。GPU 渲染則是以像素為單位。要將點轉換為像素,我們需要做一些計算:pixels = (points * 72) / dpi

事實證明,我在幾個月前(較早的那張截圖)就有這樣的直覺,也審查了我所有基於 DPI 的計算,並認為它們都是正確的。而且,它們幾乎真的是正確的。我對字型大小與 GPU(著色器)參數所做的所有計算都是正確的:我在必要時都正確處理了捨入、精確度與整數轉換。

但還有一個使用點為單位的功能被我漏掉沒有審查:視窗內距。而視窗內距的計算是錯的。這個錯誤的結果會導致內距偏差零或一個像素(不會更多)。只要偏差一個像素,就會產生模糊。我的天啊。

讓我們來看看錯在哪裡。以下是先前的內距計算(僅列出寬度,高度的計算方式相同,只是改用 y):

const padding_x: f32 = (config.@"window-padding-x"* x_dpi) / 72;
const screen_width: u32 = self.width -| @as(u32, @intFromFloat(padding_x * 2));

你看出問題了嗎?讓我給你看解法,或許你就會明白了:

const padding_x: u32 = @intFromFloat(@floor(config.@"window-padding-x"* x_dpi / 72));
const screen_width: u32 = self.width -| (padding_x * 2);

問題的發生順序如下:

  1. 內距是以浮點數計算的。如果你的 DPI 無法被 72 整除,就會得到帶有小數的內距。舉例來說,如果你的 DPI 是 125 且內距設定為 2,最終的像素值會是 3.333……

  2. 螢幕寬度的計算在將浮點數轉為整數時沒有明確指定捨入方式。這導致結果被無條件捨去,因此承接前面的例子,你會得到 width - 3。請注意,0.333…… 的內距現在遺失了。

  3. 螢幕寬度與內距都會被送到 GPU 進行渲染。GPU 以浮點數運作,因此它會忠實地採用 3.333…… 這樣的帶小數內距值。

  4. 當然,這必須對應到實際上並非小數的像素,而 GPU 會無條件進位,因為它不想遺失你的任何資料。但現在渲染的寬度就比螢幕寬了 1 個像素(0.3 被進位為 1),因此它會套用縮小操作。

  5. 縮小會引入 anti-aliasing(反鋸齒),而反鋸齒依定義就會模糊邊緣。因此,字型就變模糊了。

改為將內距無條件捨去並全程使用整數型別後,我們就不會再遇到這種情況,得以避免反鋸齒,並獲得像素級完美的渲染。😅 以下是超近距離放大的修正前(左)與修正後(右)對比:

這最終促使我們針對程式庫中所有使用 @intFromFloat 內建函式的地方進行全面審查,而我們也確實又找到一個因捨入錯誤而導致輕微殘影的案例。真是個艱難的教訓。

理論上,這不只影響 Linux 使用者。實務上,Linux 的硬體更多樣,測試者也更常遇到 DPI 與內距無法整除的情況。但只要 macOS 使用者將內距設定調整到特定的數值,也有可能看到同樣的現象。

最初的「修正」只是加上 @floor 到內距計算中的兩行變更。在如上所述徹底理解問題的發生順序後,我們決定正確的解法是將所有尺寸相關的資料結構改為使用整數而非浮點數,並且只在傳給 GPU 時才將整數轉為浮點數,藉此確保只使用非小數的數值。螢幕尺寸不是小數、內距不是小數、網格尺寸也不是小數等等。所以這也是關於使用正確資料型別的一課。托斯騰·鮑爾在一篇關於真正理解錯誤之價值的電子報文章中分享了這次經驗。


結語

Ghostty 開發日誌 002 到此結束。這篇開發日誌根本就是托斯騰·鮑爾的主場!我感到非常開心與感謝!雖然 Beta 測試團隊目前仍只有數十人,但我非常期待看到 Ghostty 社群持續成長。

如果你想掌握最新動態,請在 Twitter 或 Mastodon 上追蹤我(連結在頁尾)。這個部落格也有提供 RSS 訂閱

Boo. 👻

原文由 Mitchell Hashimoto 發布

本文章由 muse-spark-1.2-contributor 進行翻譯