天天看點

浏覽器渲染流水線解析(四)

前面已經我們已經把非合成器動畫區分為 Blink 觸發,無法由合成器運作的動畫和由 Timer/RAF 驅動的 JS 動畫兩類,因為前者可以認為是後者的一個簡化版本,是以這一章主要讨論 Timer/RAF 驅動的 JS 動畫。

浏覽器渲染流水線解析(四)

從上圖可以看出非合成器動畫的流水線比合成器動畫更長更複雜,并且非合成器動畫的後半段跟合成器動畫是一緻的。

JavaScipt 部分是頁端實作的邏輯,可能包含了計算的部分,和調用浏覽器提供的 API 的部分(修改 DOM 樹,CSS 屬性等),最終改變了網頁的内容;

網頁内容被改變會導緻 Blink 生成新的 MainFrame,MainFrame 包括了重排版,更新圖層樹,和重新記錄發生變更的圖層的内容,生成新的 DisplayList,等等;

Blink 生成新的 MainFrame 後需要向合成器發起 Commit 的請求,合成器在 Commit 過程中根據 MainFrame 生成自身的圖層樹,Blink 在 Commit 的過程中保持阻塞狀态,Commit 完成後再繼續運作;

合成器實際上有兩棵圖層樹,新送出的 MainFrame 生成的是 Pending 樹,用于繪制 Draw 的是 Active 樹,隻有當 Pending 樹目前可見區域部分的分塊全部完成 Rasterize 後,才會進入 Active 步驟,在 Active 的過程中,Pending 樹相對于 Active 樹的變更部分才會被同步到 Active 樹;

Active 後,合成器會向 UI 線程的視窗管理器發起重繪請求,視窗管理器會在下一個 VSync 的時候開始繪制新的一幀,後面的流程就跟合成器動畫是一樣的了;

上述流程的一些關鍵點是:

在合成器動畫中,分塊沒有完成光栅化,出現空白是被允許的,這樣浏覽器可以更好地保證合成器動畫的幀率,但是在非合成器動畫中出現空白是不被允許的,因為新的 MainFrame 常常會帶來大面積的變更,如果允許空白的話可能會出現非常不好的視覺效果。這樣就導緻合成器需要使用兩棵圖層樹來建構一個類似雙緩沖的機制,隻有當 Pending 樹在背景完成可見區域的光栅化時才被允許同步到 Active 樹;

在非合成器動畫過程中,Main Frame N,Main Frame N Active;Compositor Frame N,GL Frame N 這四個 Block 基本上可以認為是可以并發運作的(唯一會阻塞的環節是 Commit,不過 Commit 耗時一般不長),理論上我們要實作 60 幀的非合成器動畫,隻需要保證其中每個 Block 的耗時總和小于 16.7 毫秒即可。當然實際的狀況下,在移動裝置上很難實作這麼多線程完全并發運作,加上過多線程帶來的互相通訊的開銷,使得每個 Block 的最大允許耗時實際上是小于 16.7 毫秒的;

JavaScipt 的耗時是由頁端自己的邏輯決定的,一般超過 10 毫秒就基本上很難實作 60 幀的非合成器動畫了;

MainFrame 的耗時主要取決于網頁 DOM 樹,圖層樹的複雜程度和變化程度,在變更很小,比如隻有幾個元素的内容發生變化,圖層樹不變的情況下,一般耗時都是在 3 ~ 5 毫秒左右,如果變更很大,幾十甚至幾百都是有可能的;

Commit 的耗時主要取決于圖層樹的複雜程度,一般耗時都很短,大概 2 ~ 3 毫秒上下;

Rasterize 的耗時範圍變化極大,取決于網頁内容的複雜程度和新 MainFrame 在目前可見區域内網頁内容發生變化的總面積,另外圖檔解碼也發生在這個階段,而圖檔解碼也是光栅化耗時最多的一個環節,光栅化的耗時從幾毫秒到幾百毫秒都有可能(圖檔在第一次被光栅化時被解碼,一直在可見區域内的圖檔不會被反複重解碼);

Active 跟 Commit 的耗時類似,主要取決于圖層樹的複雜程度,一般耗時很短,大概 2 ~ 3 毫秒上下;

總的來說對非合成器動畫性能影響最大的通常是 JavaScript 和 Rasterize,要實作高性能的非合成器動畫,頁端需要很小心地控制 JavaScript 部分的耗時,并避免在每一幀中引入大面積的網頁内容變化和大幅度的圖層結構變化。另外非合成器動畫的後半段就是合成器動畫,是以對合成器動畫的性能優化要求也同樣适應于非合成器動畫。

另外對于 WebGL 來說,當在 JavaScript 裡面調用 WebGL API 時,這些指令隻是被 Chrome 緩存起來,并不會在 Renderer 線程調用真正的 GL API,是以 WebGL API 在 JavaScript 部分的耗時隻是一個 JS Binding 調用的 Overhead,最終繪制 WebGL 内容的 GPU 耗時實際上是被包含在最後的 GPU 的步驟裡面。但是在移動平台上一個 JS Binding 調用的 Overhead 是相當高的,大概在 0.01 毫秒這個範圍,是以每一幀超過 1000 個 WebGL API 調用的 WebGL 遊戲,性能阻塞的瓶頸有很大機率會出現在 JavaScript 也就是 CPU 上,而不是 GPU。

繼續閱讀