[譯] 瀏覽器幀原理剖析

瀏覽器幀原理剖析

開發者經常問我關於像素工做流程的某些部分,何時、爲何、發生了什麼。因此我感受值得提供一些參考,有關於將像素顯示在屏幕上的過程裏發生了什麼。前端

警告:文本是 Blink(譯註:Chrome 使用的排版引擎,是 webkit 的分支)和 Chrome 的視角。主線程的大部分任務以某種方式被全部第三方(vendors)任務「共享」,好比佈局和樣式計算結果,可是總的架構可能不是這樣。android

一圖勝千言

這是真的,讓咱們先看一張圖:ios

The process of getting pixels to screen.

將像素放到屏幕上的完整過程。git

下載圖片github

進程

這張小圖上放了太多內容,因此讓咱們詳細看些定義。將上圖與這些定義結合起來可能會有幫助。web

讓咱們從進程開始看:canvas

  • 渲染進程。包裹標籤頁的容器。包含了多個線程,這些線程一塊兒負責了頁面顯示到屏幕上的各個方面。這些線程有合成線程(Compositor)圖塊柵格化線程(Tile Worker),和主線程
  • GPU 進程。這是一個單一的進程,爲全部標籤頁和瀏覽器周邊進程服務。當幀被提交時,GPU 進程會將分爲圖塊的位圖和其餘數據(好比四邊形頂點和矩陣)上傳到 GPU 中,真正將像素顯示到屏幕上。GPU 進程只有一個的線程,叫 GPU 線程,其實是它作了這些工做。

渲染進程中的線程

如今看一下渲染進程中的線程。後端

  • 合成線程(Compositor Thread)。這是最早被告知垂直同步事件(vsync event,操做系統告知瀏覽器刷新一幀圖像的信號)的線程。它接收全部的輸入事件。若是可能,合成線程會避免進入主線程,本身嘗試將輸入的事件(好比滾動)轉換爲屏幕的移動。它會更新圖層的位置,並經由 GPU 線程直接向 GPU 提交幀來完成這個操做。若是輸入事件須要進行處理,或者有其餘的顯示工做,它將沒法直接完成該過程,這就須要主線程了。
  • 主線程。在這裏瀏覽器執行咱們熟知和喜歡的那些任務:JavaScript,樣式,佈局和繪製。(這一點之後會變化,有了 Houdini,咱們能夠在合成線程中運行一些代碼)主線程榮獲「最容易致使 jank 獎」,很大程度上是由於它要作的事情太多了這個事實。(譯註:jank 指頁面內容抖動卡頓,因爲頁面內容的更新頻率跟不上屏幕刷新頻率致使)
  • 合成圖塊柵格化線程(Compositor Tile Worker)。由合成線程派生的一個或多個線程,用於處理柵格化任務。咱們稍後再討論。

在許多方面,你都應該把合成線程看作「老大」。雖然這個線程不運行 JavaScript,不進行佈局、繪製內容或者其餘任務,可是它全權負責啓動主線程工做,並將幀運送到屏幕上。若是合成線程不用等待輸入事件的處理,就能夠在等待主線程完成工做時把幀發送出去。瀏覽器

你也能夠想象 Service WorkerWeb Worker 存在於渲染進程中,雖然我把他們排除在外了,由於他們把事情弄得很複雜。架構

運做過程

The main thread in all its glory.

主線程風貌全覽。下載圖片

讓咱們從垂直同步信號到像素,逐步分析這個過程,而後討論一下在徹底版本中事件是怎麼工做的。記住這一點:瀏覽器並不須要執行全部步驟,具體狀況取決於哪些步驟是必需的。例如,若是沒有新的 HTML 要解析,那麼解析 HTML 的步驟就不會觸發。事實上,一般提高性能的最佳方法,只是簡單地移除流程中部分步驟被觸發的須要!

一樣值得注意的是,上圖中 RecalcStyles 和 Layout 下方指向 requestAnimationFrame 的紅色箭頭。在代碼中剛好觸發這兩個狀況是徹底可能的。這種狀況叫作強制同步佈局(或強制同步樣式,Forced Synchronous Layout 和 Forced Synchronous Styles),一般於性能不利。

  1. 開始新的一幀。垂直同步信號觸發,開始渲染新的一幀圖像。

  2. 輸入事件的處理。從合成線程將輸入的數據,傳遞到主線程的事件處理函數。全部的事件處理函數(touchmovescrollclick)都應該最早觸發,每幀觸發一次,但也不必定這樣;調度程序會盡力嘗試,可是是否真的每幀觸發因操做系統而異。從用戶交互事件,到事件被交付主線程,兩者之間也存在延遲。

  3. requestAnimationFrame。這是更新屏幕顯示內容的理想位置,由於如今有全新的輸入數據,又很是接近即將到來的垂直同步信號。其餘的可視化任務,好比樣式計算,由於是在本次任務以後,因此如今是變動元素的理想位置。若是你改變了 —— 好比說 100 個類的樣式,這不會引發 100 次樣式計算;它們會在稍後被批量處理。惟一須要注意的是,不要查詢進行計算才能獲得的樣式或者佈局屬性(好比 el.style.backgroundImageel.style.offsetWidth)。若是你這樣作了,會致使從新計算樣式,或者佈局,或者兩者都發生,進一步致使強制同步佈局,乃至佈局顛簸

  4. 解析 HTML(Parse HTML)。處理新添加的 HTML,建立 DOM 元素。在頁面加載過程當中,或者進行 appendChild 操做後,你可能看到更多的此過程發生。

  5. 從新計算樣式(Recalc Styles)。爲新添加或變動的內容計算樣式。可能要計算整個 DOM 樹,也可能縮小範圍,取決於具體更改了什麼。例如,更改 body 的類名影響可能很大,可是值得注意的是瀏覽器已經足夠智能了,能夠自動限制從新計算樣式的範圍。

  6. 佈局(Layout)。計算每一個可見元素的幾何信息(每一個元素的位置和大小)。通常做用於整個文檔,計算成本一般和 DOM 元素的大小成比例。

  7. 更新圖層樹(Update Layer Tree)。這一步建立層疊上下文,爲元素的深度進行排序。

  8. Paint。過程分爲兩步:第一步,對全部新加入的元素,或進行改變顯示狀態的元素,記錄 draw 調用(這裏填充矩形,那裏寫點字);第二步是柵格化(Rasterization,見後文),在這一步實際執行了 draw 的調用,並進行紋理填充。Paint 過程記錄 draw 調用,通常比柵格化要快,可是兩部分一般被統稱爲「painting」。

  9. 合成(Composite):圖層和圖塊信息計算完成後,被傳回合成線程進行處理。這將包括 will-change、重疊元素和硬件加速的 canvas 等。

  10. 柵格化規劃(Raster Scheduled)柵格化(Rasterize):在 Paint 任務中記錄的 draw 調用如今執行。過程是在合成圖塊柵格化線程(Compositor Tile Workers)中進行,線程的數量取決於平臺和設備性能。例如,在 Android 設備上,一般有一個線程,而在桌面設備上有時有 4 個。柵格化根據圖層來完成,每層都被分紅塊。

  11. 幀結束:各個層的全部的塊都被柵格化成位圖後,新的塊和輸入數據(可能在事件處理程序中被更改過)被提交給 GPU 線程。

  12. 發送幀:最後,但一樣很重要的是,圖塊被 GPU 線程上傳到 GPU。GPU 使用四邊形和矩陣(全部經常使用的 GL 數據類型)將圖塊 draw 在屏幕上。

福利時間

  • requestIdleCallback:若是在幀結束時,主線程還有點時間,requestIdleCallback 可能會被觸發。這是作些非必要工做的好機會,好比標記分析數據。若是你不熟悉 requestIdleCallbackGoogle Developers 上的入門知識能幫到你。

兩種圖層

在工做流程中深度的排序有兩種版本。

首先是層疊上下文,好比有 2 個絕對定位的重疊的 div。更新圖層樹(Update Layer Tree) 是流程的一部分,保證 z-index 和相似的屬性受到重視。

而後是合成圖層,在上述流程較後的位置,多用於繪製元素。可使用空 transform 技巧(譯註:指使用 translateZ(0,0) 強制開啓硬件加速),或者 will-change: transform 將一個元素提高爲合成圖層,這樣就能輕鬆地使用 transform 動畫(有利於動畫效果!)。可是若是存在重疊元素,瀏覽器也可能須要建立額外的合成圖層,來保持由 z-index 或者其餘屬性指定的深度順序。有趣!

擴展閱讀

實質上,上面概述的過程都是在 CPU 中完成的。只有最後一部分,圖塊被上傳和移動的過程,是在 GPU 中完成的。

然而,在 Android 上,像素流在柵格化時有所不一樣:GPU 用得更多一些。在 GPU 着色器上用 GL 命令執行 draw 調用,而不是在合成圖塊柵格化線程中進行柵格化。

這就是所謂的 GPU 柵格化,是一種下降繪製(paint)成本的方法。在 Chrome DevTools 中啓用 FPS Meter(FPS 計數),你能夠查看頁面是否使用了 GPU 柵格化。

The FPS meter indicating GPU Rasterization is in use.

FPS 計數面板顯示了正在使用 GPU 柵格化。

其餘資源

若是你但願深刻研究,還有不少的資料,好比如何避免在主線程工做,或者瀏覽器渲染更深刻的運做機理。但願這些資料能幫到你:

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索