跳過導覽
An illustration showing a 3D stopwatch on a yellow background
Sketch 內幕

技術講座:我們如何透過改進渲染效能讓 Mac app 變得更快

了解我們如何提升 Sketch 的渲染效能,以及未來的發展方向

在 Sketch,我們持續致力於提升平台的速度和反應能力。例如,我們最近談到了提升網頁版 app 檢查器的速度。但最明顯的部分是在 Mac app 的畫布上的渲染——以快速且準確的方式將 Sketch 圖層轉換成螢幕上的像素。這個流程是我們非常重視的,並且我們也在不斷改進。

這些變更通常會在多次更新中逐步推出,因此很少有一個版本會大幅提升效能。但如果您回顧幾個先前的更新版本,肯定會注意到差異。

因此,我們今天就要來做這件事——比較 Sketch 的現今版本和幾個月前的版本,看看我們的渲染效能在這段時間的進展。

但首先,讓我們先簡單介紹一下背景。

將圖層轉換為渲染指令

我們的渲染堆疊有很多活動部件。因此,在這篇文章中,我們將重點放在一個渲染子系統——迭代 Sketch 文件樹,並將其轉換為 CoreGraphic 基礎呼叫的部分。基本上,它會將您的圖層樹轉換為「填充此路徑」或「繪製該筆畫」等呼叫。

當我們在 2010 年最初編寫這個子系統時,我們使用的是 Objective-C。現在我們認為是時候退一步,現代化,看看我們可以在不影響渲染品質的情況下改進哪些方面。

從 Objective-C 切換到 Swift

我們做的第一件事是用 Swift 重寫程式碼。這並非一時興起——通常為了重寫而重寫可以正常運作的程式碼是個壞主意。您可能會在此過程中引入一些新的細微錯誤。但這次我們有一些很好的理由進行更改。

用 Swift 重寫程式碼並非一時興起。但這次我們有一些很好的理由進行更改。

首先,Swift 與 CoreGraphics 溝通的介面更加完善,而且我們認為 Swift 更強大的類型系統會派上用場。當然,它也是 Apple 平台的首選語言。最後,重寫程式碼也迫使我們重新審視每個類別和每一行程式碼,這有助於我們進行許多其他小的改進。

簡化複雜的流程

將圖層轉換為渲染指令乍看之下很簡單。當您遇到形狀時,繪製該形狀,也許新增邊框和陰影,然後繼續處理下一個形狀。但實際情況比這更複雜。

在某些情況下,例如使用遮罩時,您必須預先查看接下來的內容,或記住之前的內容。如果是個別圖層,單純地繪製填充和邊框會導致不必要的鋸齒效應。

將圖層轉換為渲染指令乍看之下很簡單。但在某些情況下,您必須預先查看接下來的內容,或記住之前的內容。

雖然還有其他例子,但它們都有一個共同點 — 它們會使原本一系列簡單的指令變得複雜且緩慢。所有這些都意味著我們必須採取更複雜的途徑。

棘手的部分?我們並非總是需要使用這種複雜的途徑 — 有時我們可以採取捷徑,從而獲得更好的效能。真正的挑戰在於知道何時該走哪條路。

改進螢幕外點陣圖

在用 Swift 重寫程式碼後,我們開始研究這些渲染複雜性,並找出如何有效地處理它們。我們懷疑我們可以在這裡獲得很大的效能提升,因為 Mac 應用程式在繪製時資訊不完整 — 因此它做了比實際需要更多的工作。我們對應用程式如何使用螢幕外點陣圖特別感興趣。

Mac 應用程式在渲染過程中大量使用螢幕外點陣圖,尤其是在最初需要將多個繪圖組合在一起時。例如,如果您使用低於 100% 的不透明度值、套用遮罩或使用模糊等效果。

當應用程式使用螢幕外點陣圖時,它必須為其分配記憶體、繪製到其中、將生成的點陣圖繪製到其最終目的地,然後自行清理。您可以想像,與直接將輸出繪製到目的地相比,這些步驟在處理這些像素時會產生輕微的成本。

螢幕外點陣圖的作用

讓我們舉一個通常會使用螢幕外點陣圖的具體例子 — 在多個圖層之間共用不透明度

A screenshot showing two shapes on the Canvas of a Sketch file. The first has two squares intersecting, and each square is at 50% opacity — the area where the two squares intersect is darker. The second has a similar layout with the squares intersecting, but the image is handled as a single shape and color is uniform across the whole thing.

如果您對 Sketch 很有經驗,您就會知道如何實現上面兩個例子之間的差異。第一個是兩個正方形,每個正方形的不透明度都設定為 50%。第二個是一個包含兩個正方形的群組,該群組的不透明度設定為 50%。

現在讓我們將它們轉換為繪圖原語。在第一個例子中,我們繪製了兩個半透明的正方形,一個接一個 — 這使得第一個渲染可以透過第二個渲染看到。在第二個例子中,兩個正方形首先被不透明地繪製到一個螢幕外點陣圖中,然後作為一個圖像組合在一起,整個結果都具有不透明度。

但是,如果在此例子中,群組只包含一個正方形,那麼螢幕外點陣圖路線將毫無意義。結果將沒有什麼不同,但如果我們以這種天真的方式處理它,效能將會受到影響。因此,為了獲得最佳效能,我們必須提前預測,確保在採用螢幕外路線之前,群組中有多個圖層。

建模複雜性

這個例子很簡單 — 但真正的挑戰是擁有足夠的資訊來事先弄清楚我們是否真的需要一個螢幕外點陣圖,或者直接路線是否可行。

我們決定投入一些時間來構建一個數據結構來捕捉所有這些細節。構建一個額外的樹成本很高,但我們認為如果它能幫助我們優化渲染 — 例如通過跳過不必要的螢幕外點陣圖,那麼成本是值得的。

它開始時只是小幅度的增長。在 Sketch 72 中,我們將新的樹用於我們正在渲染的單個圖層。在 73 中,我們將樹擴展到整個群組。在 74 中,我們為文檔中需要單次渲染的整個部分構建了樹。每一步都解鎖了進一步的優化 — 到 Sketch 75 推出時,我們已經有足夠的資訊來盡可能地跳過昂貴的路徑。

回到上面的例子 — 我們知道設計師有很多正當的理由將單個圖層放入半透明的群組中,而不是直接對形狀應用不透明度。例如,在符號中,您可以將符號來源設為黑色字形,然後為實例指定特定的色調。現在,程式碼已經足夠聰明,可以查看符號內部並檢測我們是否可以直接使用正確的填充來渲染形狀,並完全繞過螢幕外點陣圖。

顯示結果

雖然每個離屏點陣圖本身佔用資源不多,但它們累積起來的速度很快。因此,這些優化確實能帶來改變,正如下圖所示。

A graph showing how the time taken to process documents has dropped over time as we have implemented rendering improvements.

這張圖表只顯示了將文件圖形處理為基本指令所需的時間。除此之外,還有其他處理程序在進行,所以我們不能斷言「整體效能提升高達 3 倍」。但在許多文件中,差異是顯而易見的。

這張圖表也顯示了另一個重點:現有的文件種類繁多,設計師會以不同的方式在這些文件中堆疊效果。沒有萬能的解決方案,但某些文件的效能下降幅度確實令人印象深刻。

我們的工作還沒結束。在未來的更新中,您將看到進一步的改進,讓 Sketch 的運作和使用體驗更加快速。請密切關注未來的貼文。

您可能也會喜歡

免費試用 Sketch

無論您是 Sketch 的新手,還是回來看看有什麼新功能,我們都能讓您在幾分鐘內設定好並準備好進行最佳創作。

免費開始使用
免費開始使用