【BeesAndroid系列】聊一聊Chromium的渲染機制

聊一聊Chromium的渲染機制

關於BeesAndroid項目
javascript

BeesAndroid項目提供了一系列的工具、理論分析與方法論,旨在下降Android系統源碼的閱讀門檻,讓讀者更好的理解Android系統的設計與實現。第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄css


今天咱們來聊一聊Chromium的渲染機制,這也是渲染機制系列的第二篇,最近大半年的工做都和H5容器有關,於是花了點時間學習了下Chromium項目,這裏着重去分析一下它的渲染機制。

從開發者的角度,當咱們去看一個H5容器的時候,和它一塊兒工做的有如下角色:
html

  • 軟件
    • Chromium:WebView、Content、Blink、V八、Net、Base等
    • Android OS:View/Window、Activity、WindowManager、ActivityManager、Surface/Texture、SurfaceFlinger。
    • Graphics:OpenGL ES、Skia、Vulkan
    • Binders
    • Linux Kernel
  • 硬件
    • 顯示屏
    • CPU
    • GPU


以下所示:

前端

the_life_of_pixel_webview_01.svg


固然實際的構造複雜的多,用戶在點擊屏幕打開H5頁面時,通常會經歷如下幾個階段。

  1. 觸摸反饋:首先是觸摸,這個觸摸事件是怎麼傳遞到Android裏面的App的。
  2. 容器建立:當觸摸事件傳遞到App中,Android是怎麼啓動WebView容器來加載URL的。這裏會涉及Chromium內核的啓動等相關知識。
  3. 頁面加載:WebView啓動之後,是怎樣向服務端發送主文檔請求的,又是怎麼接收主文檔響應的。
  4. 頁面渲染:WebView接收到主文檔以後,是怎麼樣將它解析成頁面的,這個是最爲關鍵也是最爲複雜的一環。

能夠看到,頁面在渲染以前還有須要工做須要處理,容器的啓動也是個耗時的操做,爲何會特意聊聊容器啓動呢,由於這個也是H5頁面體驗的重要組成部分,由於是Native的關係,前端同窗可能會關注不到。並且容器導航階段是重要的預加載時機,咱們能夠在這裏作不少事情,例如:java

  1. 接口預加載
  2. HTML文檔預加載
  3. 資源預加載
  4. 導航的時候建立一個JS Engine,能夠提早執行JS邏輯,把導航預加載這個能力開放給前端


言歸正傳,咱們接着來聊聊渲染機制。
node

Rendering Architecture


瀏覽器的渲染過程就是把網頁經過渲染管道渲染成一個個像素點,最終輸出到屏幕上。這裏面就涉及3個角色
android

  • 輸入端:網頁,Chromium將其抽象成Content。
  • 渲染管線:主要是Blink負責DOM解析、樣式佈局、繪製等操做,將網頁內容轉換爲繪製指令。
  • 輸出端:主要負責把繪製指令轉換爲像素,顯示在屏幕上。


什麼是輸入端(Content)?
git

咱們在Chromium這個項目裏會頻繁的看到Content這個概念,那麼Content究竟是什麼呢。Content是渲染網頁內容的區域,在Java層對應AwContent,底層有WebContents表示,以下所示:github

image.png

content在代碼由content::WebContents來描述,它在獨立的Render進程由Blink建立。具體說來Content對應着前端開發中涉及的HTML、CSS、JS、image等,以下所示:web

image.png


什麼是渲染管線(Rendering Pipeline)?

渲染管線能夠理解爲對渲染流程的拆解,向工廠流水線同樣,上一個車間生成的半成品送到下一個車間繼續裝配。拆解渲染流程有助於把渲染流程簡單化,提升渲染效率。

渲染時動態的,內容發生變化時,就會觸發渲染,更新像素點,和Android的繪製系統同樣,觸發繪製也是由invalidate機制觸發的,觸發渲染後,執行整個渲染管線是很是昂貴的,於是Blink也在想法設法減小沒必要要的渲染動做,提升渲染效率。

  • 觸發的條件以下所示:
    • scrolling
    • zooming
    • animations
    • incremental loading
    • javascript
  • 各個流程的觸發方法以下:
    • Style:Node::SetNeedsStyleRecalc()
    • Layout:LayoutObject::SetNeedsLayout()
    • Paint:PaintInvalidator::InvalidatePaint()
    • RasterInvalidator::Generate()


渲染管道把網頁轉換爲繪製指令後,它並不能直接把繪製指令變成像素點(光柵化)顯示在屏幕上(Window),這個時候就須要藉助操做系統本身的能力(底層的圖形庫),在圖形界面這一塊大部分平臺都遵循OpenGL標準化的API。例如Windows上的DirectX,Android上的Vulcan。以下圖所示:

image.png


經過上面的描述,咱們瞭解了Conntent從哪裏來,要到哪裏去。總的來講就是把HTML、CSS、JS等轉換爲正確的OpenGL指令,而後渲染到屏幕上,與用戶交互。

在瞭解了渲染的基本要素之後,咱們來看看具體的渲染流程是怎樣執行的,以下所示:

image.png

Structure

**
咱們先來講結構

從上到下,分層來講:

  • Blink:運行在Render進程的Render線程,它是Chromium的Blink渲染引擎,主要負責HTML/CSS的解析、jS的解釋執行(V8)、DOM操做、排版、圖層樹的構建更新等任務。
  • Layer Compositor:運行在Render進程的Compositor線程,它負責接收Blink生成的Main Frame,負責圖層樹的管理、圖層的滾動、旋轉等矩陣變化,圖層的分塊、光柵化、紋理上傳等任務。
  • Display Compositor:運行在Browser進程的UI線程,它負責接收Layer Compositor生成的Compositor Frame,輸出最終的OpenGL繪製指令,將網頁內容經過GL貼圖操做繪製到目標窗口上。


這裏面還提到了每一個層級向上輸出的產物幀,幀(Frame)描述了渲染流水線下級模塊向上級模塊輸出的繪製內容相關數據的封裝。

  • Main Frame:包含了對網頁內容的描述,主要以繪圖指令的形式,或者理解爲某個時間點對整個網頁的一個矢量圖快照。
  • Compositor Frame:Layer Compositor接收Blink生成的Main Frame,並轉換成內部的合成器結構。它會被髮往Browser,並最終到達Compositor Frame,它主要由兩部分構成:
    • Resource:它是對Texture的封裝,Layer Compositor爲每一個圖層分塊,而後爲每一個分塊分配Resource,而後安排光柵化任務。
    • Draw Quad:它表明了繪製指令(矩形繪製指令,指定了座標、大小、變換矩陣等屬性),Layer Compositor接收到Browser的繪製請求時,它會爲當前可見區域每一個圖層的每一個分塊生成一個Draw Quad繪製指令。
  • GL Frame:Display Compositor將Compositor Frame的每一個Draw Quad繪製指令轉換成一個GL多邊形繪製指令,使用對應的Resource封裝的Texture對目標窗口進行貼圖。這個GL繪圖指令的集合就構成了一個GL Frame,最終由GPU執行這些GL指令完成網頁在窗口可見區域的繪製。


整個渲染水流水線的調度基於請求和狀態機響應,調度的中樞運行在Browser UI線程,它按照顯示器的VSync信號向Layer Compositor發出輸出下一幀的請求,而Layer Compositor根據自身的狀態機的狀態決定是否須要Blink輸出下一幀。而Layer Compositor和Display Compositor是生成者和消費者的關係,Display Compositor持有一個Compositor Frame隊列不斷的進行取出和繪製,輸出的頻率取決於 Compositor Frame的輸入幀率和自身GL Frame的繪製頻率。

Flow


咱們再來講流程

  1. Parse/DOM:將Content解析成DOM樹,它是後面各個渲染流程的基礎。
  2. Style:解析並應用樣式表。
  3. Layout:佈局。
  4. Compositing update:將整個頁面按照必定規則,分紅獨立的圖層,便於隔離更新。
  5. prepaint:構建屬性樹,使得能夠單獨操做某個節點(變換、裁剪、特效、滾動),不至於影響它的子節點。
  6. paint:paint這個單詞名詞有油漆、顏料的含義。動詞有用顏料畫等含義。這裏我以爲使用它的名詞含義比較貼切,Paint操做會將佈局樹(Layout Tree)中的節點(Layout Object)轉換成繪製指令(例如繪製矩形、繪製字體、繪製顏色,這有點像繪製API的調用)的過程。而後把這些操做封裝在Dsipaly Item中,因此這些Display Item就像是油漆,它尚未真正的開始粉刷(繪製Draw)。
  7. Commit:commit會把paint階段的數據拷貝的合成器線程。
  8. Tiling:raster接收到paint階段的繪製指令以後,會先對圖層進行分塊。圖塊是柵格化(Raster)的基本工做單位。
  9. Raster:柵格化。
  10. Activate:柵格化是個異步的過程,於是圖層樹(Layer Tree)被分爲了Pending Tree(負責接收Commit提交的Layer進行柵格化操做)和Activate Tree(從這裏取出柵格化的Layer進行Draw操做),從Pending Tree拷貝Layer到Activate Tree的過程就叫作Activate。
  11. Draw:這裏要和上面的Paint區分開來了,圖塊被柵格化之後,合成器線程會爲每一個圖塊生成draw quads(quads有四邊形之意,它表明了在屏幕特定位置繪製圖塊的指令,包含屬性樹裏面的變換、特效等信息),這些draw quads被封裝到Compositor Frame中輸出給GPU,Draw操做就是生成draw quads的過程。
  12. Display:生成了Compositor Frame之後,Viz會調用GL指令把draw quads最終輸出到屏幕上。


咱們來分別看具體的流程。

Rendering Pipeline

注:Rendering Pipeline裏的圖片來自於Chromium工程師的ppt Life of a Pixel的截圖。

Blink

01 Parse


相關文檔


相關源碼


當咱們從服務器上下載了一份HTML文檔,第一步就是解析,HTML解析器接收標籤和文本流(HTML是純文本格式)把HTML文檔解析成DOM樹。DOM(Document Object Model)即文檔對象模型,DOM及時頁面的內部表示,也爲JavaScript暴露了API接口(V8 DOM API),可讓JavaScript程序改變文檔的結構、樣式和內容。

它是一個樹狀結構,咱們在後續的渲染流程中還會看到不少樹形結構(例如佈局樹、屬性樹等)由於它們都是基於DOM樹的結構(HTML的結構)而來的。

image.png

注:HTML文檔中可能包含多棵DOM樹,由於HTML支持自定義元素,這種樹一般被稱爲Shadow Tree。


解析HTML生成DOM樹流程以下:

  1. HTMLDocumentParser負責解析HTML中的token,生成對象模型。
  2. HTMLTreeBuilder負責生成一棵完整的DOM樹,同一個HTML文檔能夠包含多個DOM樹,Custom Element元素具備一棵shadow tree。在shadow tree slot中傳入的節點會被FlatTreeTraversal向下遍歷時找到。


DOM樹(DOM Tree)做爲後續繪製流程的基礎, 還會基於它生產各類類型的樹,具體說來,主要會經歷以下轉換:

對象轉換

  • DOM Tree -> Render Tree -> Layer Tree
  • DOM node -> RenderObject -> RenderLayer

DOM Tree(節點是DOM node)

當加載一個HTML時,會對他進行解析,生成一棵DOM樹。DOM樹上的每個節點都對應這網頁裏面的每個元素,網頁能夠經過JavaScript操做這棵DOM樹。

image.png

How Webkit Works

Render Tree(節點是RenderObject)

可是DOM樹自己並不能直接用於排版和渲染,所以內核會生成Render Tree,它是DOM Tree和CSS相結合的產物,二者的節點幾乎是一一對應的。Render Tree是排版引擎和渲染引擎之間的橋樑。

image.png

How Webkit Works

Layer Tree(節點是RenderLayer)

渲染引擎並非直接使用Render Tree進行繪製的,爲了更加方便的處理定位、裁剪、業內滾動等操做,渲染引擎會生成一棵Layer Tree。渲染引擎會爲一些特定的RenderObject生成相應的RenderLayer,不過該RenderObject的子節點沒有相應的RenderLayer,那麼它就從屬於父節點的RenderLayer。渲染引擎會遍歷每個RenderLayer,再遍歷從屬於這個RenderLayer的RenderObject,將每個RenderObject繪製出來。

能夠這麼理解,Layer Tree決定了網頁的繪製順序,從屬於RenderLayer的RenderObject決定了這個Layer的繪製內容。

什麼樣的RenderObject會成爲RenderLayer呢。GPU Accelerated Compositing in Chrome是這樣定義的:

  • It's the root object for the page
  • It has explicit CSS position properties (relative, absolute or a transform)
  • It is transparent
  • Has overflow, an alpha mask or reflection
  • Has a CSS filter
  • Corresponds to element that has a 3D (WebGL) context or an accelerated 2D context
  • Corresponds to a


對上面的流程不瞭解也不要緊,咱們下面會一一解釋。

02 Style


當DOM樹生成之後,就須要爲每一個元素設置一個樣式,有的樣式只是會影響某個節點,有的樣式會影響整個節點下面的整個DOM子樹的渲染(例如,節點的旋轉變換)。

image.png


相關文檔


相關源碼


樣式通常都是樣式渲染器共同做用的結果,它有複雜的優先級語義和渲染過程,過程總體分爲三步:

1 收集、劃分和索引全部樣式表中樣式規則。

image.png


CSSParser首先CSS文件解析成對象模型StyleSheetContents,它裏面包含各類樣式規則(StyleRule),這些樣式規則具備豐富的表現形式。包含選擇器(CSSSelector)和屬性值映射(CSSPropertyValue)在這些樣式規則中,對象以各類方式創建索引,進行更有效的查找。

另外,樣式屬性以聲明的方式進行定義,定義在Chromium裏的css_properties,json5這個json文件裏,這些定義會經過py腳本生成特定的C++類。

2 訪問每一個DOM元素並找到應用在該元素的全部規則。

樣式引擎會遍歷整個DOM樹,計算每一個節點的樣式,計算樣式(ComputeStyle)會完成property到rule的映射,例如字體樣式、邊距、背景色等。這些就是樣式引擎的輸出。

image.png


3 結合這些規則以及其餘信息(樣式引擎由部分默認的樣式)生成最終的計算樣式。

03 Layout


計算並應用了每一個DOM節點的樣式之後,就須要決定每一個DOM節點的擺放位置。DOM節點都是基於盒模型擺放(一個矩形),佈局就是計算這些盒子的座標。

image.png


佈局操做是創建在 CSS盒模型基礎之上的,以下所示:

|-------------------------------------------------|
    |                                                 |
    |                  margin-top                     |
    |                                                 |
    |    |---------------------------------------|    |
    |    |                                       |    |
    |    |             border-top                |    |
    |    |                                       |    |
    |    |    |--------------------------|--|    |    |
    |    |    |                          |  |    |    |
    |    |    |       padding-top        |##|    |    |
    |    |    |                          |##|    |    |
    |    |    |    |----------------|    |##|    |    |
    |    |    |    |                |    |  |    |    |
    | ML | BL | PL |  content box   | PR |SW| BR | MR |
    |    |    |    |                |    |  |    |    |
    |    |    |    |----------------|    |  |    |    |
    |    |    |                          |  |    |    |
    |    |    |      padding-bottom      |  |    |    |
    |    |    |                          |  |    |    |
    |    |    |--------------------------|--|    |    |
    |    |    |     scrollbar height ####|SC|    |    |
    |    |    |-----------------------------|    |    |
    |    |                                       |    |
    |    |           border-bottom               |    |
    |    |                                       |    |
    |    |---------------------------------------|    |
    |                                                 |
    |                margin-bottom                    |
    |                                                 |
    |-------------------------------------------------|
複製代碼


相關文檔


相關源碼


基於DOM Tree會生成Layout Tee,生成每一個節點的佈局信息。佈局的過程就是遍歷整個Layout Tree進行佈局操做。

DOM Tree和Layout Tree也不老是一一對應的,若是咱們再標籤裏設置dispaly:none,它就不會建立一個佈局對象(LayoutObject)。

image.png

04 Compositing Update


在Layout操做完成之後,理論上就能夠開始Paint操做了,可是咱們以前提過,若是直接開始Paint操做,繪製整個界面,代價是很是昂貴的。所以便引入了一個圖層合成加速的概念。

什麼是圖層合成加速(Compositing Layer)?

圖層合成加速基本思想是把整個頁面按照必定規則分紅多個圖層(就像Photoshop的圖層那樣),在渲染時只須要操做必要的圖層,其餘圖層只須要參與合成就好了,以此提升渲染效率。完成這個工做的線程叫Compositor Thread,值得一提的是Compositor Thread還具有處理輸入事件的能力(例如滾動事件),可是若是在JavaScript註冊了事件監聽,它會把輸入事件轉發給主線程處理。

image.png

具體說來是爲某些RenderLayer擁有本身獨立的緩存,它們被稱爲合成圖層(Compositing Layer),內核會被這些RenderLayer建立對應的GraphicsLayer。

  • 擁有本身的GraphicsLayer的RenderLayer在繪製的時候就會繪製在本身的緩存裏面。
  • 沒有本身的GraphicsLayer的RenderLayer會向上查找父節點的GraphicsLayer,直到RootRenderLayer(它老是會有本身的GraphicsLayer)爲止,而後繪製在有GraphicsLayer的父節點的緩存裏。

image.png

這樣就造成了與RenderLayer Tree對應的GraphicsLayer Tree。當Layer的內容發生變化時,只須要更新所屬的GraphicsLayer便可,而單一緩存架構下,就會更新整個圖層,會比較耗時。這樣就提升了渲染的效率。可是過多的GraphicsLayer也會帶來內存的消耗,雖然減小了沒必要要的繪製,但也可能由於內存問題致使總體的渲染性能下賤。於是圖層合成加速追求的是一個動態的平衡。


什麼樣的RenderLayer會被建立GraphicsLayer呢,GPU Accelerated Compositing in Chrome是這樣定義的:

  • Layer has 3D or perspective transform CSS properties
  • Layer is used by
  • Layer is used by a element with a 3D context or accelerated 2D context
  • Layer is used for a composited plugin
  • Layer uses a CSS animation for its opacity or uses an animated webkit transform
  • Layer uses accelerated CSS filters
  • Layer has a descendant that is a compositing layer
  • Layer has a sibling with a lower z-index which has a compositing layer (in other words the layer overlaps a composited layer and should be rendered on top of it)


圖層化的決策是由Blink來負責(將來可能會轉移到Layer Compositor決策),根據DOM樹生成一個圖層樹,並以DisplayList記錄每一個圖層的內容。

瞭解了圖層合成加速的概念之後,咱們再來看看發生在Layout操做以後的Compositing update(合成更新),合成更新就是爲特定的RenderLayer(建立規則咱們已經描述過了)建立GraphicsLayer的過程,以下所示:

image.png

05 Prepaint


什麼是屬性樹?

在描述屬性的層次結構這一塊,以前的方式是使用圖層樹的方式,若是父圖層具備矩陣變換(平移、縮放或者透視)、裁剪或者特效(濾鏡等),須要遞歸的應用到子節點,時間複雜度是O(圖層數),這在極端狀況下會有性能問題。

所以引入了屬性樹的概念,合成器提供了變換樹、裁剪樹、特效樹等。每一個圖層都由若干節點id,分別對應不一樣屬性樹的矩陣變換節點、裁剪節點和特效節點。這樣的時間複雜度就是O(要變化的節點),以下所示:

image.png


Prepaint的過程就是構建屬性樹的過程,以下所示:

image.png

06 Paint


建立完屬性樹(Prepaint)之後,就開始進入Paint階段了。

相關文檔


相關源碼


Paint操做會將佈局樹(Layout Tree)中的節點(Layout Object)轉換成繪製指令(例如繪製矩形、繪製字體、繪製顏色,這有點像繪製API的調用)的過程。而後把這些操做封裝在Dsipaly Item中,這些Dsipaly Item存放在PaintArtifact中。PaintArtifact就是是Paint階段的輸出。

到目前爲止,咱們創建了能夠重放的繪製操做列表,但沒有執行真正的繪製操做。

注:重放(replay),如今圖形系統大都採用recrod & replay機制,採集繪製指令與執行繪製指令相互分離,提升渲染效率


image.png


在繪製的過程當中,會涉及一個繪製順序的問題,它使用的是stacking order(z-index),而不是DOM order。z-index會決定繪製順序,在沒有z-order指定的狀況下,Paint會按照如下順序進行繪製。

image.png

  • 背景色
  • floats
  • 前景色
  • 輪廓


Paint操做最終會在Layout Tree的基礎上生成一棵Paint Tree。

image.png


Layer Compositor

07 Commit


Paint階段完成之後,進入Commit階段。該階段會更新圖層和屬性樹的副本到合成器線程,以匹配提交的主線程狀態。說的通俗點,就是把主線程裏Paint階段的數據(layers and properties)拷貝到合成器線程,供合成器線程使用。

image.png

08 Tiling


可是合成器線程接收到數據後,並不會當即開始合成,而是進行圖層分塊,這裏又涉及一個分塊渲染的技術。

什麼是分塊渲染?

分塊渲染(Tile Rendering)就是把網頁的緩存分爲一格一格的小塊,一般爲256x256或者512x512,而後分塊進行渲染。

分塊渲染主要基於兩個方面的考慮:

  • GPU合成一般是使用OpenGL ES貼圖實現的,這時候的緩存實際就是紋理(GL Texture),不少GPU對紋理的大小是有限制的,好比長寬必須是2的冪次方,最大不能超過2048或者4096等。沒法支持任意大小的緩存。
  • 分塊緩存,方便瀏覽器使用統一的緩衝池來管理緩存。緩衝池的小塊緩存由全部WebView共用,打開網頁的時候向緩衝池申請小塊緩存,關閉網頁是這些緩存被回收。


圖塊(tiling)是柵格化工做的基本單位。 柵格化會根據圖塊與可見視口的距離安排優先順序進行柵格化。離得近的會被優先柵格化,離得遠的會降級柵格化的優先級。這些圖塊拼接在一塊兒,就造成了一個圖層,以下所示:

image.png

09 Raster


圖層分塊完成之後,接着就會進行柵格化(Raster)。

什麼是光柵化(柵格化)?

光柵化(Raterization),又稱柵格化,它用於執行繪圖指令生成像素的顏色值,光柵化策略分爲兩種:

  • 同步光柵化:光柵化和合成在同一線程,或者經過線程同步的方式來保證光珊化和合成
    • 直接光柵化:直接將全部可見圖層的eDisplayList中的可見區域的繪圖指令進行執行,在目標Surface的像素緩衝區上生成像素的顏色值。固然若是是徹底的直接光柵化,就不涉及圖層合併了,也就不須要後面的合成了。
    • 間接光柵化:容許爲指定圖層分配額外的緩衝區,該圖層的光柵化會先寫入自身的像素緩衝區,渲染引擎再將這些圖層的像素緩衝區(Android裏能夠調用View.setLayerType容許應用爲View分配像素緩衝區)經過合成輸出大歐姆表Surface的像素緩衝區。Android和Flutter主要使用直接光柵化的測量,同時也支持間接光柵化。
  • 異步分塊光柵化


上面說到,在Paint階段會生成DisplayItem列表,它們是對繪製指令的封裝。光柵化(Raster)或者柵格化的過程就是把這些繪圖指令變成位圖(像素點,每一個像素點都帶有本身的顏色)。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/279116/1593585931104-95b9507b-bbdc-43d9-8eab-5a0c430bb6c2.png#align=left&display=inline&height=265&margin=%5Bobject%20Object%5D&name=image.png&originHeight=549&originWidth=1242&size=300772&status=done&style=none&width=600)

光柵化的過程還包括圖片解碼。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/279116/1594026056074-d76001e0-9120-4b19-ac5c-f11593d6514e.png#align=left&display=inline&height=271&margin=%5Bobject%20Object%5D&name=image.png&originHeight=576&originWidth=1276&size=358615&status=done&style=none&width=600)

過去GPU只是做爲一個內存(GPU Memory),這些內存被GL紋理(OpenGL中的標識符)所引用。咱們會將柵格化的像素點放到主內存中,而後上傳到GPU,以減少內存壓力。

如今GPU已經能夠運行產生像素點的着色器,能夠在GPU上進行柵格化,這種模式成爲加速柵格(硬件加速)。無論是硬件柵格化仍是軟件柵格化,本質上都是生成了某種內存中像素的位圖,這個時候尚未顯示到屏幕上。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/279116/1593585957507-654a5009-fc3c-47eb-bb35-b46cf91d1c47.png#align=left&display=inline&height=291&margin=%5Bobject%20Object%5D&name=image.png&originHeight=549&originWidth=1132&size=256218&status=done&style=none&width=600)

GPU柵格化並非直接調用GPU,而是經過Skia圖形庫(Google維護的2D圖形庫,在Android、Flutter、Chromium都有使用)發出的OpenGL調用。以下所示:

Skia提供了某種抽象層,屏蔽了底層硬件、路徑、貝塞爾曲線等複雜的概念,當須要柵格化顯示項(Display Item)時,會先去調用SkCanvas上面的方法,它是Skia的調用入口。SkCanvas提供了Skia內部更多的抽象,在硬件加速時,它會構建另外一個繪圖操做緩衝區,而後對其進行刷新,在柵格化任務結束時,經過flush操做,咱們得到了真正的GL指令。GL指令運行在GPU Process。

![image.png](https://cdn.nlark.com/yuque/0/2020/png/279116/1593586064954-8bed11e0-a743-415e-8e20-54fcb68ccc43.png#align=left&display=inline&height=268&margin=%5Bobject%20Object%5D&name=image.png&originHeight=566&originWidth=1267&size=330761&status=done&style=none&width=600)

Skia和GL指令能夠運行在不一樣進程,也能夠運行在同一個進程這就產生了兩種調用方式。
  1. In Proess Raster
  2. Out of Proess Raster


1 老版本的調用採用這種方式,Skia運行在Renderer Process,負責產生GL指令,GPU有單獨的GPU Process,這種模式下Skia沒法直接進行渲染系統調用,在初始化Skia的時候回給它一個函數指針表(指向了GL API,但不是真正的OpenGL API,而是Chromium提供的代理),函數指針錶轉換爲真正的OpenGL API的過程稱爲命令緩衝區(GpuChannelMsg_FlushCommandBuffers),

單獨的GPU進程有利於隔離GL操做,提高穩定性和安全性,這種模式也稱爲沙箱機制(不安全的操做運行在獨立的進程裏)。

image.png


2 新版本把繪製操做放到了GPU Process,在GPU一側運行Skia,這有助於提高性能。

image.png



接下來就是執行GL指令,GL指令通常是由底層so庫提供,在Windows平臺上OpenGL還會被轉換爲DirectX(Microsoft的圖形API,用於圖形加速)。

image.png

10 Activate


在Commit以後,Draw以前有一個Activate操做。Raster和Draw都發生在合成器線程裏的Layer Tree上,可是咱們知道Raster操做是異步的,有可能須要執行Draw操做的時候,Raster操做還沒完成,這個時候就須要解決這個問題。

它將Layer樹分爲:

  • Pending Tree:負責接收commit,而後將Layer進行Raster操做
  • Active Tree:會從這裏取出柵格化好的Layer進行draw操做。


這個拷貝的過程就稱爲Activate,以下所示:

image.png


事實上Layer Tree主要有四種:

  • 主線程圖層樹:cc::Layer,始終存在。
  • Pending樹:cc::LayerImpl,合成器線程,用於光柵化階段,可選。
  • Active樹:cc::LayerImpl,合成器線程,用於繪製階段,始終存在。
  • Recycle樹:cc::LayerImpl,合成器線程,與Pending樹不會同時存在。


主線程的圖層樹由LayerTreeHost擁有,每一個圖層以遞歸的方式擁有其子圖層。Pending樹、Active樹、Recycle樹都是LayerTreeHostImpl擁有的實例。這些樹被定義在cc/trees目錄下。之因此稱之爲樹,是由於早期它們是基於樹結構實現的,目前的實現方式是列表。

11 Draw

當每一個圖塊都被光柵化之後,合成器線程會爲每一個圖塊生成draw quads(在屏幕指定位置繪製圖塊的指令,包含了屬性樹裏面的變換、特效等操做),這些draw quads指令被封裝在CompositorFrame對象中,CompositorFrame對象也是Render Process的輸出產物。它會被提交到GPU Process中。咱們平時提到的60fps輸出幀率裏面的幀指的就是Compositor Frame。

Draw操做就是柵格化的圖塊生成draw quads的過程。

image.png

Display Compositor

12 Display


相關文檔


Draw操做完成之後,就生成了Compositor Frame,它們會被輸出到GPU Process。 它會從多個來源的Render Process接收Compositor Frame。

  • Browser Process也有本身的Compositor來生成Compositor Frame,這些通常是用來繪製Browser UI(導航欄,窗口等)。
  • 每次建立tab或者使用iframe,會建立一個獨立的Render Process。


image.png


Display Compositor運行在Viz Compositor thread,Viz會調用OpenGL指令來渲染Compositor Frame裏面的draw quads,把像素點輸出到屏幕上。

什麼是VIz?

Viz是VIsual的縮寫,它是Chromium總體架構轉向服務化的一個重要組成部分,包含Compositing、GL、Hit Testing、Media、VR/AR等衆多功能。


VIz也是雙緩衝輸出的,它會在後臺緩衝區繪製draw quads,而後執行交換命令最終讓它們顯示在屏幕上。

什麼是雙緩衝機制?

在渲染的過程當中,若是隻對一塊緩衝區進行讀寫,這樣會致使一方面屏幕要等到去讀,而GPU要等待去寫,這樣要形成性能低下。一個很天然的想法是把讀寫分開,分爲:

  • 前臺緩衝區(Front Buffer):屏幕負責從前臺緩衝區讀取幀數據進行輸出顯示。
  • 後臺緩衝區(Back Buffer):GPU負責向後臺緩衝區寫入幀數據。

這兩個緩衝區並不會直接進行數據拷貝(性能問題),而是在後臺緩衝區寫入完成,前臺緩衝區讀出完成,直接進行指針交換,前臺變後臺,後臺變前臺,那麼何時進行交換呢,若是後臺緩存區已經準備好,屏幕尚未處理完前臺緩衝區,這樣就會有問題,顯然這個時候須要等屏幕處理完成。屏幕處理完成之後(掃描完屏幕),設備須要從新回到第一行開始新的刷新,這期間有個間隔(Vertical Blank Interval),這個時機就是進行交互的時機。這個操做也被稱爲垂直同步(VSync)。

到這裏,整個渲染流程就結束了,前端的代碼變成了能夠與用戶交互的像素點。

相關文章
相關標籤/搜索