渲染

RenderObject樹

RenderObject基礎類

爲了便於理解,引入書中代碼段:

<html>
  <head></head>
  <body>
    <div>test</div>
    <canvas id="webgl" width="50" height="60"></canvas>
    <a href="http://test"></a>
    <img></img>
    <input type="button"></input>
    <select></select>
    <table width = "100" height="50">
      <tr>
        <td>a1</td>
      </tr>
    </table>
    <script type="text/javascript">
      var canvas = document.getElementById("webgl");
      var gl = canvas.getContext("experimental-webgl");
      if (!gl) {
        alert("No webgl context available");
        return;
      }
    </script>
  </body>
</html>

上述代碼經過WebKit解釋之後會生成DOM樹,在DOM樹構建完成之後會爲DOM樹節點構建RenderObject樹,在DOM樹中,有些元素是不可見的,例如表示文件頭的“meta”節點,在最終的顯示結果中,用戶看不到它的存在,這樣的節點稱爲“非可視化節點”,上述代碼中的“head”,“script”就是這樣的節點,另外的節點是用來展示網頁內容的,如上述例子中的”body”,”div”,”span”,”canvas”,”img”等,這些節點可以顯示一塊區域,如文字,圖片,2D圖形等,這樣的節點被稱爲“可視節點”,對於可視節點,webkit會爲其建立相應的RenderObject對象,一個RenderObject對象保存了爲繪製DOM節點所需要的各種信息,例如樣式佈局信息,這些RenderObject對象會構成RenderObject樹,RenderObject樹是基於DOM樹建立起來的一棵新樹,是爲了佈局計算和渲染等機制而構建的一種新的內部表示,RenderObject和DOM樹不是一一對應的關係,滿足一下三個規則的DOM樹節點會創建一個RenderObject對象:

  • DOM樹的document節點
  • DOM樹中的可視化節點,例如html,body,div等
  • WebKit建立匿名的RenderObject節點,該節點不對應DOM樹中的任何節點,而是WebKit處理上的需要,典型的例子爲匿名的RenderBlock節點

WebKit在創建DOM樹時也會創建RenderObject對象,如果DOM樹被動態的加入新的節點,WebKit也可能會創建相應的RenderObject對象,下面是RenderObject對象被創建時所涉及到的主要類:
這裏寫圖片描述
每個元素會遞歸調用“attach”函數,該函數檢查Element對象是否需要創建RenderObject,如果需要,會使用NodeRenderingContext類來根據DOM節點的類型來創建對應的RenderObject節點。RenderObject樹中的節點有很多類型,其中RenderObject類包含了RenderObject的主要虛函數,大致包含一下幾塊:

  • 爲了遍歷和修改RenderObject樹而涉及到的衆多函數,遍歷操作函數如parent(),firstChild(),nextSibling(),previousSibling()等,修改操作函數如addChild(),removeChild()等
  • 用來計算佈局和獲取佈局相關信息的函數,例如Layout(),style(),enchosingBox()等
  • 用來判斷RenderObject對象屬於哪種類型的子類,這裏面有各式各樣的類似”IsASubClass”的函數,這些函數可以知道它們的類型以作相應的轉換
  • 跟RenderObject對象所在的RenderLayer對象相關的操作
  • 座標和繪圖相關的操作,WebKit使用這些操作讓RenderObject對象 將內容繪製在傳入的繪製結果對象中,例如paint(),repaint()等

RenderObject基類和它的主要子類:
這裏寫圖片描述
對於匿名RenderObject對象由於它沒有對應的DOM樹中節點,WebKit統一使用Document節點來對應匿名對象。

RenderObject樹

RenderObject對象構成了一棵樹,該RenderObject樹的創建主要由NodeRenderingContext類來負責,基本思路是WebKit檢查該DOM節點是否需要創建RenderObject對象,如果需要,WebKit建立或者獲取一個創建RenderObject對象的NodeRenderingContext對象,NodeRenderingContext對象會分析需要創建的RenderObject對象的父親節點、兄弟節點等,設置這些信息後完成插入樹的操作,RenderObject樹和DOM樹之間的區別以上述的代碼爲例做說明,具體如下:
這裏寫圖片描述
圖中虛線表示兩種樹節點的對應關係,其中HTMLDocument節點對應RenderView節點,RenderView節點是RenderObject樹的根節點,另外,webkit沒有爲HTMLHeadElement節點(非可視化節點)創建RenderObject子類對象。

網頁層次和RenderObject樹

層次和RenderLayer對象

RenderLayer樹是基於RenderObject樹建立起來的一棵新樹,並且RenderLayer節點和RenderObject節點不是一一對應的關係,當滿足以下關係時RenderObject節點需要建立新的RenderLayer節點:

  • DOM樹的Document節點對應的RenderView節點
  • DOM樹中的Document的子女節點,也就是HTML節點對應的RenderBlock節點
  • 顯示的指定CSS位置的RenderObject節點
  • 有透明效果的RenderObject節點
  • 節點有溢出,alpha,或者反射等效果的RenderObject節點
  • 使用Canvas2D和3D(WebGL)技術的RenderObject節點
  • Video節點對應的RenderObject節點

每個RenderLayer節點包含的RenderObject節點其實是一棵RenderObject子樹,理想情況下每個RenderLayer對象都會有一個後端類,該後端類用來存儲該RenderLayer對象繪製的結果,WebKit創建RenderObject樹之後,WebKit也會創建RenderLayer樹,某些RenderLayer節點有可能在執行JavaScript代碼時或者更新頁面的樣式被創建,同RenderObject類不同,RenderLayer沒有子類,它表示的是一個層次,沒有子層次的說法。

構建RenderLayer樹

根據前面創建RenderLayer的條件來判斷一個RenderObject是否需要創建一個新的RenderLayer對象,並設置RenderLayer對象的父親和兄弟關係即可,根據示例代碼將RenderObject樹根據規則創建對應的RenderLayer樹,如下:
這裏寫圖片描述

渲染方式

繪圖上下文

在WebKit中繪圖操作被定義了一個抽象層,被稱爲繪圖上下文,所有的繪圖操作都是在該上下文中來進行的,繪圖上下文分成兩種類型,第一種用來繪製2D圖形的上下文,稱爲2D繪圖上下文(GraphicsContext),第二種稱爲繪製3D圖形上下文(GraphicsContext3D),這兩種上下文都是抽象基類,只提供接口,具體繪製由不同的移植去實現。2D繪圖上下文的具體作用是提供基本繪圖單元的繪製接口以及設置繪圖的樣式,繪製接口包括點、線、圖片、多邊形、文字等,繪圖樣式包括顏色、線寬、字號大小、漸變等,RenderObject對象知道自己需要畫什麼樣的點,什麼樣的圖片,因此RenderObject對象調用繪圖上下文的這些基本操作繪製顯示結果。在現有的網頁中,由於HTML5標準引入了很多新的技術,所以統一網頁中可能既需要2D繪圖上下文,也需要3D繪圖上下文,對於2D繪圖上下文來說,其平臺相關的實現既可以使用CPU來完成2D相關的操作,也可以使用3D圖形如Opengl來完成2D相關的操作,對於3D繪圖上下文來說,因爲性能問題WebKit通常使用3D圖形接口(Opengl,Direct3D等)來實現。

渲染方式

在完成DOM構建之後,WebKit所要做的事情就是構建渲染的內部表示並使用圖形庫將這些模型繪製出來,關於網頁的渲染方式,目前主要有兩種,第一種是軟件渲染,第二種是硬件加速渲染。每個RenderLayer對象可以被想象成圖像中的一個層,各個層一同構成一個圖像,其中軟件繪圖採用CPU,硬件加速繪圖採用GPU來完成,並且在理想情況下每個層都有個繪製的存儲區域,這個存儲區域用來保存繪圖的結果,最後將這些層的內容合併到同一個圖像中,稱之爲合成,使用了合成技術的渲染稱爲合成話渲染。在RenderObject和RenderLayer樹之後,WebKit的機制將內部模型轉換爲可視化結果分爲兩個階段:每層的內容繪製及之後將這些繪圖的結果合成一個圖像。對於軟件渲染,WebKit需要使用CPU來繪製每層的內容,軟件渲染機制不需要合成階段,因爲在軟件渲染的過程中通常渲染結果就是一個位圖(Bitmap),繪製每一層的時候都使用該位圖,區別在於繪製的位置可能不一樣,每一層都是按照從後到前的順序,下圖是網頁渲染的三種方式:
這裏寫圖片描述
第一種方式使用軟件渲染,在網頁渲染過程中使用一個位圖,也就是一塊CPU使用的內存空間,後面兩種方式使用了合成化的渲染技術,也就是使用GPU硬件加速合成這些網頁層,合成的工作由GPU來完成,稱爲硬件加速合成,其中第二種方式有些特殊,其中一些層使用CPU來繪圖,另外一些層使用GPU來做,對於使用CPU來繪圖的層,該層的結果保存在CPU內存中,之後被傳輸到GPU的內存中,主要是爲了後面的合成工作。對於這三種方式各有優缺點和使用場景,對於常見的2D繪圖操作,使用GPU來繪圖不一定比使用CPU繪圖在性能上有優勢,例如文字、線、點等,原因在於CPU使用的緩存機制有效減少了重複繪製的開銷而且不需要GPU並行性,其次,GPU的內存資源相對與CPU的內存資源比較緊張,而且網頁的分層使得GPU的內存使用相對比較多,下面列舉這三種方式的特點:

  • 軟件渲染很普遍,也是遊覽器最早使用的渲染方式,比較節省內存,特別是GPU內存,缺點在於只能處理2D方面的操作,對於沒有複雜繪圖或者多媒體的網頁可採用軟件渲染,但對於HTML5新的技術點軟件渲染顯得無計可施,一是因爲能力不足,包括CSS3D、WebGL等,而是性能不好,例如視頻、Canvas2D等,鑑於此軟件渲染使用的場景越來越少,特別是移動領域。此外軟件渲染相對於硬件加速渲染有另外一個優勢,當網頁中有一塊小型區域需要更新的時候,軟件渲染可能只需要更新一個極小區域,而硬件渲染可能需要重新繪製其中的一層或者多層,然後再合成這些層,因此硬件加速渲染的代價要高些
  • 對於硬件加速的合成化渲染來說,每個層的繪製和所有層的合成均使用GPU硬件來完成對使用3D繪圖的操作來說特別合適,在這種方式下,RenderLayer樹之後,WebKit和Chromium需要建立更多的內部表示,例如GraphicsLayer樹、合成器中的層(如chromium中的CCLayer)等,這會消耗額外的內存空間,但一方面硬件加速機制能夠支持現在所有的HTML5定義的2D或者3D繪圖標準,另一方面如果需要更新某個層的一個區域,由於軟件渲染沒有爲每一層提供後端存儲,因而它需要將和這個區域有重疊部分的所有層次相關區域依次從後向前重新繪製一遍,而硬件渲染只需要重新繪製更新發生的層次
  • 軟件繪製合成化渲染方式結合了前面兩種方式的優點,因爲很多網頁既可能包含一些HTML的基本元素,也可能包含一些HTML5新功能,使用CPU繪製方式來繪製某些層,使用GPU來繪製其他一些層,這是基於性能和內存方面綜合考慮的結果

WebKit軟件渲染技術

在沒有需要硬件加速的時候,WebKit使用軟件渲染方式來完成頁面的繪製工作,對於軟件渲染主要關注兩個方面,第一是RenderLayer樹,第二是每個RenderLayer所包含的RenderObject子樹,對於每個RenderObject對象,需要三個階段繪製自己,第一階段繪製該層中所有塊的背景和邊框,第二階段是繪製浮動內容,第三階段是前景(Foreground),包含內容部分,輪廓等部分,內嵌元素的背景、邊框、前景等也是在該階段繪製的,下圖大致描述了RenderLayer層是如何繪製自己和子女的:
這裏寫圖片描述

① 對於當前RenderLayer對象而言,WebKit首先繪製反射層(Reflectionlayer)
② 繪製RenderLayer對象對應的RenderObject節點的背景層(PaintBackground-ForFragments),調用“PaintPhaseBlockBackground”函數繪製該對象的背景層,不包括RenderObject的子女,“Fragments”的含義是可能繪製幾個區域,因爲網頁需要更新的區域可能不是連續的,而是多個小塊,因此WebKit繪製的時候需要更新這些非連續的區域就可
③圖中“paintList”(z座標爲負數的子女層)階段負責繪製Z座標爲負數的子女層,這是一個遞歸的過程,Z座標爲負數的層在當前RenderLayer對象層的後面,因此WebKit先繪製後面的層,然後當前RenderLayer對象層可能覆蓋它們
④“PaintForegroundForFragments()”步奏相對較多,包括四個子階段:第一進入“PaintPhaseChildBlockBackground”階段,WebKit繪製RenderLayer節點對應的RenderObject節點的所有後代節點的背景,如果某個被選中的話,WebKit改爲繪製選中區域的背景;第二進入“PaintPhaseFloat”繪製階段,WebKit繪製浮動的元素;第三進入“PaintPhaseForeground”階段,WebKit繪製RenderObject節點的內容和後代節點的內容;第四進入“PaintPhaseChildOutlilnes”繪製階段,WebKit的目的是繪製所有後代節點的輪廓
⑤進入“PaintOutlineForFragments”步驟,WebKit在該步驟中繪製RenderLayer對象對應的RenderObject節點的輪廓
⑥進入繪製RendeLayer對象的子女步驟,WebKit首先繪製溢出的RenderLayer節點,之後繪製Z座標爲正數的RenderLayer節點
⑦進入該RenderObject節點的濾鏡步驟

WebKit第一次繪製網頁的時候,WebKit繪製的區域等同與可視區域的大小,在這之後,WebKit只是首先計算需要更新的區域,繪製同這些區域有交集的RenderObject節點,也就是說,如果更新區域跟某個RenderLayer節點有交集,WebKit會繼續查找RenderLayer樹中的特定一個或一些節點,而不是繪製整個RenderLayer對應的RenderObject子樹,這與硬件加速不同,硬件加速渲染會繪製整個RenderLayer。WebKit軟件渲染結果的存儲方式在不同的平臺有可能不一樣,但基本上都是CPU內存的一塊區域,多數情況下是一個位圖(Bitmap),下面這個位圖如何處理,如何跟之前的繪製結果合併,如何顯示跟WebKit的不同移植有關。

Chromium多進程軟件渲染技術

在Chromium中,需要將渲染結果從Render進程傳遞到Browser進程。
Render進程:WebKit的Chromium移植的接口類是RenderViewImpl,該類包含一個用於表示一個網頁渲染結果的WebViewImpl類,此外RenderViewImpl也繼承於RenderWidget類需要同Browser進程進行通信,RenderWidget類不僅負責調度頁面渲染和頁面更新到實際的WebViewImpl類等操作,而且還負責同Browser進程進行通信,另一個重要的設施是PlatformCanvas類,也就是SkiaCanvas(Skia是一個2D圖形庫),RenderObject樹的實際繪製操作和繪製結果都是由該類來完成,它類似與2D繪圖上下文和後端存儲的結合體。
Browser進程:第一個設施是RenderWidgetHost類,它負責同Render進程進行通信,RenderWidgetHost類的作用是傳遞Browser進程中網頁操作的請求給Render進程的RenderWidget類,並接收來自對方的請求,第二是BackingStore,它是一個後端的存儲空間,它的大小通常就是網頁可視區域的大小,該空間存儲的數據就是頁面的顯示結果,BackingStore類的作用很明顯,第一是保存當前的可視結果,所以Render進程的繪製工作不會影響該網頁結果的顯示,第二WebKit只需要繪製網頁的變動部分,其餘的部分保存在該後端存儲空間,chromium只需要將網頁的變動更新到該後端存儲中即可。
這兩個進程傳遞繪製結果是通過TransportDB類來完成,該類在Linux系統下其實是一個共享內存的實現,對Render進程來說,Skia Canvas把內容繪製到位圖中,該位圖的後端即是共享的CPU內存,當Browser進程接收到Render進程關於繪圖完成的通知消息,Browser進程會把共享內存的內容複製到BackStoring對象中,然後釋放共享內存,Browser進程中的後端存儲最終會被繪製在顯示窗口中,用戶就能看到網頁的結果。多進程軟件渲染的過程大致如下:RenderWidget類接收到更新請求時,Chromium創建一個共享內存區域,然後Chromium創建skia的SkCanvas對象,並且RenderWidget會把實際的繪製工作派發給RenderObject樹,具體來說,WebKit負責遍歷RenderObject樹,每個RenderObject節點根據需要來繪製自己和子女的內容並存儲到目標存儲空間,也就是SkCanvas對象所對應的共享內存的位圖,最後,RenderWidgetHost類把位圖複製到BackStoring對象的相應區域並調用“Paint”函數來把繪製結果繪製到窗口中。
下面列舉兩種會觸發重新繪製網頁中某些區域的請求:

  • 前端請求:該類型的請求從Browser進程發起,可能是遊覽器自身的一些需求,也可能是X窗口系統的請求,一個典型的例子就是用戶因操作網頁引起的變化
  • 後端請求:由於頁面自身的邏輯而發起更新部分區域的請求,例如HTML元素或樣式的改變、動畫等,一個典型的例子是JavaScript代碼每隔50ms便會更新網頁的樣式,這是樣式更新會觸發部分區域的重繪

下圖給出了當有繪製或者更新某個區域的請求時,Chromium和WebKit是如何來處理這些請求的:
這裏寫圖片描述
1. Render進程的消息循環調用處理“界面失效”的回調函數,該函數主要調用RenderWidget::DoDeferredUpdate來完成繪製請求
2. RenderWidget::DoDeferredUpdate函數首先調用Layout函數來觸發檢查是否需要重新計算佈局和更新請求
3. RenderWidget類調用TransportDIB類來創建共享內存,內存大小爲繪製區域的高*寬*4,同時調用skia圖形庫來創建一個SkCanvas對象,SkCanvas對象的繪製目標是一個使用共享內存存儲的位圖
4. 當渲染該頁面的全部或部分時,ScrollView類請求按照從前到後的順序遍歷並繪製所有RenderLayer對象的內容到目標的位圖中,WebKit繪製每個RenderLayer對象通過以下步驟來完成:首先WebKit計算重繪的區域是否和RenderLayer對象有重疊,如果有,WebKit要求繪製該層中的所有RenderObject對象
5. 繪製完成後,Render進程發送UpdateRect消息給Browser進程,Render進程同時返回以完成渲染過程,Browser進程收到消息後首先由BackingStoreManager類來獲取或者創建BackingStoreX對象,BackingStoreX對象的大小與可視區域相同,包含整個網頁的座標信息,它根據UpdateRect的更新區域的位置信息將共享內存的內容繪製到自己對應的存儲區域中

最後Browser進程將UpdateRect的回覆消息發送到Render進程,Render進程知道Browser進程已經使用完該共享內存,可以進行回收利用等操作。

發佈了39 篇原創文章 · 獲贊 11 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章