轉譯 NG 的主要資料結構和角色

Chris Harrelson
Chris Harrelson
Daniel Cheng
Daniel Cheng
Philip Rogers
Philip Rogers
Koji Ishi
Koji Ishi
Ian Kilpatrick
Ian Kilpatrick
Kyle Charbonneau
Kyle Charbonneau

本系列文章先前的文章概略說明 RenderingNG 架構的目標、主要屬性高階元件。現在來深入瞭解關鍵資料結構,也就是輸入和輸出至轉譯管道的資料結構。

這些資料結構如下:

  • 影格樹狀結構:由本機和遠端節點組成,代表轉譯程序和哪一個 Blink 轉譯器的網路文件。
  • 不可變動的片段樹狀結構,代表版面配置限制演算法的輸出內容及輸入內容。
  • 屬性樹狀結構,代表網頁文件的轉換、剪輯、效果和捲動階層,用於整個管道。
  • 顯示清單和繪製區塊是光柵和分層演算法的輸入內容。
  • 「合成器框架」會封裝使用 GPU 繪圖的表面、算繪介面和 GPU 紋理圖塊。

在瀏覽這些資料結構之前,我想顯示下列範例從上一份文章為基礎建構的簡易範例。我們將在本文中數次使用這個範例,說明資料結構的應用方式。

<html>
  <div style="overflow: hidden; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
      id="one" src="foo.com/etc"></iframe>
  </div>
  <iframe style="top:200px;
    transform: scale(1.1) translateX(200px)"
    id="two" src="bar.com"></iframe>
</html>

相框樹

Chrome 有時可能會選擇在與上層頁框不同的轉譯程序中算繪跨來源頁框。

在簡介的範例中,共有三個影格:

上層頁框 foo.com,內含兩個 iframe。

啟用網站隔離功能後,Chromium 會使用兩個轉譯程序轉譯這個網頁。 每個轉譯程序都有個別的網頁頁框樹狀結構表示法:

兩個頁框樹狀結構,分別代表兩個轉譯程序。

在其他程序中算繪的影格會顯示為遠端影格。遠端影格會包含做為轉譯中預留位置所需的最低資訊,例如尺寸。否則遠端框架不包含轉譯實際內容所需的任何資訊。

而本機頁框則代表一個影格,會經過前文所述的標準轉譯管道。本機框架包含將該影格資料 (例如 DOM 樹狀結構和樣式資料) 轉換為可顯示的內容所需的所有資訊。

轉譯管道會根據本機影格樹狀結構的精細程度運作。假設有一個較複雜的範例,以 foo.com 做為主頁框:

<iframe src="bar.com"></iframe>

然後顯示以下 bar.com 子頁框:

<iframe src="foo.com/etc"></iframe>

雖然現在只有兩個轉譯器,但現在有兩個本機影格樹狀結構片段,其中兩個是 foo.com 的轉譯程序,另一個是 bar.com 的轉譯程序:

呈現兩個轉譯作業與三個影格樹狀結構的表示法。

為了為網頁產生一個合成框架,Viz 同時從三個本機影格樹狀結構的根框架中要求合成框架,然後「匯總」這些影格。(另請參閱本文後續介紹的合成器頁框一節)。

foo.com 主頁框和 foo.com/other-page 子頁框屬於同一頁框樹狀結構的一部分,並在同一程序中轉譯。不過,這兩個頁框屬於不同的本機頁框樹狀結構片段,因此擁有獨立的文件生命週期。因此,您無法在同一次更新中為兩項更新產生一個合成影格。 算繪程序的資訊不足,無法將為 foo.com/other-page 產生的合成器影格直接合成至 foo.com 主影格的合成器影格。舉例來說,非程序 bar.com 上層頁框可能會透過 CSS 轉換 iframe,或將 iframe 的部分與 DOM 中的其他元素遮蔽,進而影響 foo.com/other-url iframe 的顯示。

視覺房源更新瀑布

裝置縮放比例係數和可視區域大小等視覺屬性會影響算繪的輸出內容,而且必須在本機頁框樹狀結構之間同步處理。每個本機頁框樹狀結構的根層級都有相關聯的小工具物件。視覺屬性更新會先傳送到主頁框的小工具,然後再從上到下推進其餘的小工具。舉例來說,當可視區域大小變更時:

以上文字說明的程序圖表。

這項程序不會立即執行,因此複製的視覺屬性也會包含同步權杖。Viz 合成器會使用這個同步權杖,等待所有本機影格樹狀結構片段,以目前的同步權杖提交合成影格。這個程序可避免將合成器畫面與不同的視覺屬性混合。

不可變更的片段樹狀結構

不可變動的片段樹狀結構是轉譯管道版面配置階段的輸出內容。這代表頁面上所有元素的位置和大小 (未套用轉換)。

表示每個樹狀結構中的片段,一個片段被標示為需要版面配置。

每個片段都代表 DOM 元素的一部分。一般來說,每個元素只會有一個片段,但如果在列印時分成不同頁面,則在多欄結構定義中則可分割更多資料欄。

在版面配置後,每個片段都變得不可變動,也永遠不會變更。重要的是,我們也設有幾項額外限制。我們不會採取以下做法:

  • 允許樹狀結構中所有「向上」參照。(子項不能有指向父項的指標)。
  • 「bubble」資料

這些限制可讓我們重複使用片段,做為後續版面配置。如果沒有這些限制,我們通常必須重新產生整個樹狀結構,而高昂費用。

多數版面配置通常是漸進式更新,例如網頁應用程式為了回應使用者點擊元素而更新一小部分 UI 的部分。在理想情況下,版面配置只應與螢幕上實際變更的比例成正比。我們可以重複使用先前樹狀結構的更多部分來達成這個目標。這表示 (通常) 我們只需要重新建構樹狀結構的輪子。

日後,這項不可變更的設計可讓我們進行有趣的操作,例如視需要在不同執行緒邊界傳遞不可變動的片段樹狀結構 (在不同的執行緒上執行後續階段)、為順暢的版面配置動畫產生多個樹狀結構,或執行平行推測版面配置。我們也能藉此掌握多執行緒版面配置本身的潛力。

內嵌片段項目

內嵌內容 (主要樣式文字) 使用稍有不同的表示法。我們不是有方塊和指標的樹狀結構,而是在代表樹狀結構的平面清單中表示內嵌內容。主要優點在於內嵌清單表示法的快速處理速度較快,適合用於檢查或查詢內嵌資料結構,以及有效記憶體。這對網頁轉譯效能來說非常重要,因為轉譯文字非常複雜,而且如果沒有高度最佳化,很容易成為管道中最緩慢的部分。

在此提出一個有趣的過往說明,這與 Internet Explorer 先前表示其 DOM 的方式非常類似,因為原本的建構方式與文字編輯器類似。

系統會根據內嵌版面配置子樹狀結構的深度優先搜尋,為每個內嵌格式內容建立平面清單。清單中的每個項目都是元組 (物件、子系數)。以下列 DOM 為例:

<div style="width: 0;">
  <span style="color: blue; position: relative;">Hi</span> <b>there</b>.
</div>

(請注意,width 屬性已設為 0,這樣行會在「Hi」和「the」之間包裝)。在此情況下,內嵌格式設定內容以樹狀結構表示時,如下所示:

{
  "Line box": {
    "Box <span>": {
      "Text": "Hi"
    }
  },
  "Line box": {
    "Box <b>": {
      "Text": "There"
    }
  },
  {
    "Text": "."
  }
}

平面清單如下所示:

  • (線條方塊,2)
  • (方塊 <span>, 1)
  • (訊息「嗨,0)
  • (線條方塊,3)
  • (方塊 <b>, 1)
  • (文字「there」, 0)
  • (文字「.」, 0)

這種資料結構有很多消費者:無障礙 API,以及 getClientRectscontentedit 等幾何 API。兩者的需求不盡相同。 這些元件可透過便利的遊標存取平面資料結構。

遊標具有 MoveToNextMoveToNextLineCursorForChildren 等 API。這個遊標表示法對於文字內容具有非常強大的功能,原因如下:

  • 逐一處理優先順序優先的搜尋順序非常快速。這種行為經常使用,因為與插入點類似。由於深度優先搜尋是扁平的清單,因此陣列偏移搜尋只是遞增陣列偏移值,提供快速的疊代和記憶體位置。
  • 此方法可提供廣泛的搜尋功能,例如繪製線條和內嵌方塊的背景時就必須使用。
  • 掌握子系的數量後,會快速地移至下一個同層級 (只要以該數字為單位增加陣列位移即可)。

物業樹

DOM 是元素的樹狀結構 (加上文字節點),CSS 可以將各種樣式套用至元素。

這些效果大多以四個變種形式呈現:

  • Layout:版面配置限制演算法的輸入內容。
  • 繪製:如何繪製及光柵元素 (而非其子系)。
  • 視覺呈現:套用至 DOM 子樹狀結構的光柵/繪圖效果,例如轉換、篩選器和裁剪。
  • 捲動:軸對齊和圓角裁剪及捲動內含子樹狀結構的圓角。

資源樹狀結構是資料結構,說明視覺效果和捲動效果如何套用至 DOM 元素。這些 API 可讓您根據版面配置大小和位置,找出特定 DOM 元素,例如相對於畫面的位置。然後,應使用哪種 GPU 運算順序來套用視覺和捲動效果?

網路上的視覺和捲動效果極為複雜,因此,最重要的屬性樹狀結構最重要的就是,將複雜程度轉換成單一的資料結構,精準呈現其結構和意義,同時消除 DOM 和 CSS 的複雜性。這樣一來,我們就能更準確地實作組合和捲動的演算法。請特別注意以下幾點:

  • 並將容易出錯的幾何圖形和其他計算集中在同一位置。
  • 建構及更新屬性樹狀結構的複雜性會分離為一個轉譯管道階段。
  • 相較於完整 DOM 狀態,將屬性樹狀結構傳送至不同的執行緒和程序會簡單許多,因此可在許多用途中使用。
  • 使用的情況越多,我們就能從以頂層為基礎的幾何快取中受益,因為他們可以重複使用彼此的快取。

轉譯 NG 會使用屬性樹狀結構來達成許多目的,包括:

  • 正在將繪製與繪製作業區隔開來,並與主執行緒進行合成。
  • 決定最佳組合 / 繪製策略。
  • 測量 IntersectionObserver 幾何圖形。
  • 避免處理畫面外元素和 GPU 紋理圖塊的工作。
  • 繪製和光柵範圍更有效率且效率更佳。
  • 使用 Core Web Vitals 評估版面配置位移最大內容繪製

每份網頁文件都有四個不同的屬性樹狀結構:變形、裁剪、效果和捲動(*)。 轉換樹狀結構代表 CSS 轉換及捲動。(捲動轉換以 2D 轉換矩陣表示)。 片段樹狀結構代表溢位片段。效果樹狀結構代表所有其他視覺效果:不透明度、濾鏡、遮罩、混合模式,以及剪輯路徑等其他類型的剪輯片段。捲動樹狀結構代表有關捲動的相關資訊,例如如何同時捲動鏈結;需要在合成器執行緒上執行捲動。屬性樹狀結構中的每個節點都代表由 DOM 元素套用的捲動或視覺效果。如果發生這種情況,表示同一個元素在每個樹狀結構中可能會有多個屬性樹狀結構節點。

每個樹狀結構的拓撲就像是 DOM 的稀疏表示法。舉例來說,假設有三個 DOM 元素含有溢位片段,則會有三個裁剪樹狀結構節點,而裁剪樹狀結構的結構將遵循溢位片段之間的內含區塊關係。樹狀圖之間也有連結。這些連結會指出節點的相對 DOM 階層與應用程式順序。舉例來說,如果某產品的 DOM 元素轉換在另一個含有篩選器的 DOM 元素下方,當然轉換時也會先套用,然後再套用篩選器。

每個 DOM 元素都有一個「屬性樹狀結構狀態」,也就是以 4 個元組 (轉換、短片、效果、捲動) 表示,也就是對該元素生效的最接近的祖系剪輯、轉換和效果樹狀結構。這樣的做法非常方便,因為我們能得知套用至該元素的短片、轉換和效果清單,以及套用的順序。這可讓我們瞭解資料在畫面上的位置和繪製方式。

範例

(資料來源)

<html>
  <div style="overflow: scroll; width: 100px; height: 100px;">
    <iframe style="filter: blur(3px);
      transform: rotateZ(1deg);
      width: 100px; height: 300px"
  id="one" srcdoc="iframe one"></iframe>
  </div>
  <iframe style="top:200px;
      transform: scale(1.1) translateX(200px)" id=two
      srcdoc="iframe two"></iframe>
</html>

針對上述範例 (與簡介中的範例略有不同),以下是產生的屬性樹狀結構的主要元素:

屬性樹狀結構中各項元素的範例。

顯示清單和繪製區塊

顯示項目包含低階繪圖指令 (請參閱這個頁面),該指令可使用 Skia 光柵化。顯示項目通常很簡單,只要幾個繪圖指令 (例如繪製邊框或背景) 即可。油漆樹步道會反覆查看版面配置樹狀結構和相關片段,然後依 CSS 繪製順序產生顯示項目清單。

例如:

藍色方塊,有綠色矩形內部的「Hello World」字詞。

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="blue" style="width:100px;
  height:100px; background:blue;
  position:absolute;
  top:0; left:0; z-index:-1;">
</div>

這個 HTML 和 CSS 會產生以下顯示清單,其中每個儲存格都是一個顯示項目:

檢視畫面的背景 #blue背景 #green背景 #green 內嵌文字
drawRect 大小為 800x600,顏色為白色。 drawRect 大小為 100x100,位置為 0,0,藍色。 drawRect 大小為 80x18,位置為 8、8,顏色為綠色。 drawTextBlob,位置為 8,8,文字「Hello World」。

顯示項目清單順序為從前排序。在上述範例中,綠色 div 位於 DOM 順序的藍色 div 之前,但 CSS 繪製順序要求在 (步驟 3) 綠色 div 之前繪製負的 Z-index 藍色 div (步驟 4.1)。顯示項目大致符合 CSS 繪製順序規格的不可分割步驟。單一 DOM 元素可能會產生多個顯示項目,例如 #green 如何含有背景的顯示項目,以及另一個內嵌文字的顯示項目。如要呈現 CSS 繪製順序規範的完整複雜度,就必須採用這個精細程度,例如以負邊界建立交錯:

綠色長方形,有部分重疊的灰色方塊和「Hello world」文字。

<div id="green" style="background:green; width:80px;">
    Hello world
</div>
<div id="gray" style="width:35px; height:20px;
  background:gray;margin-top:-10px;"></div>

這會產生以下顯示清單,其中每個儲存格都是一個顯示項目:

檢視畫面的背景 #green背景 #gray背景 #green 內嵌文字
drawRect 大小為 800x600,顏色為白色。 drawRect 大小為 80x18,位置為 8、8,顏色為綠色。 drawRect 的大小為 35x20,位置為 8,16,顏色為灰色。 drawTextBlob,位置為 8,8,文字「Hello World」。

系統會儲存顯示項目清單,並於日後更新時重複使用。如果版面配置物件在繪製樹狀結構走動期間未變更,其顯示項目就會從先前的清單複製。額外最佳化作業依賴 CSS 繪製順序規格的屬性:以不可分割的形式堆疊結構定義。如果堆疊結構定義內沒有任何版面配置物件變更,油漆樹狀結構會略過堆疊結構定義,並複製上一個清單的整個顯示項目序列。

目前的屬性樹狀結構狀態在繪製樹狀結構走動期間會保留下來,顯示項目清單會歸入具有相同屬性樹狀結構狀態的顯示項目「區塊」。如以下範例所示:

粉紅色方形箱與傾斜的橘色方塊。

<div id="scroll" style="background:pink; width:100px;
   height:100px; overflow:scroll;
   position:absolute; top:0; left:0;">
    Hello world
    <div id="orange" style="width:75px; height:200px;
      background:orange; transform:rotateZ(25deg);">
        I'm falling
    </div>
</div>

這會產生以下顯示清單,其中每個儲存格都是一個顯示項目:

檢視畫面的背景 #scroll背景 #scroll 內嵌文字 #orange背景 #orange 內嵌文字
drawRect 大小為 800x600,顏色為白色。 位置為 0,0 且顏色為 100x100 的 drawRect,顏色為粉紅色。 drawTextBlob 位置為 0,0,文字「Hello World」。 drawRect 大小為 75x200,位置為 0.0,顏色為橘色。 drawTextBlob 位置為 0,0,文字「I'm Down」。

接著轉換屬性樹狀結構和顏料區塊就會簡化 (為求精簡):

上表的圖片,區塊 1 的前兩個儲存格,第三個區塊 2,最後兩個儲存格位於區塊 3。

已排序的繪製區塊清單 (顯示項目群組和屬性樹狀結構狀態) 是轉譯管道步驟分層的輸入內容。完整的繪製區塊清單可合併為單一合成層,並將光柵化,但這需要花費大量的光柵化。系統可以為每個繪製區塊分別建立合成層,並將光柵化,以免重新光柵化,但這麼做會快速耗盡 GPU 記憶體。分層步驟必須在 GPU 記憶體之間做出取捨,並在情況發生變化時降低成本。一般而言,建議的做法是預設合併區塊,不要合併具有在合成器執行緒上會變更屬性樹狀結構狀態的繪製區塊,例如合成器執行緒捲動或合成器執行緒轉換動畫。

上述範例在理想情況下應產生兩個複合圖層:

  • 800x600 的合成圖層,內含繪圖指令:
    1. 尺寸為 800x600 且顏色為白色的 drawRect
    2. 位置為 0.0 且顏色為 100 x 100 的 drawRect,顏色為粉紅色
  • 一個 144 x 224 的複合圖層,內含繪圖指令:
    1. 位置為 0,0 且文字「Hello World」的 drawTextBlob
    2. 翻譯 0,18
    3. rotateZ(25deg)
    4. 位置為 0.0 且顏色為 75x200 的 drawRect,顏色為橘色
    5. drawTextBlob 位置為 0,0,文字「I'm Down」

如果使用者捲動 #scroll,系統會移動第二個複合式圖層,但不需要光柵化。

如同此處的前一章節屬性樹狀結構,這裡有六個繪製區塊。以及 (轉換、裁剪、效果、捲動) 屬性樹狀結構狀態,如下所示:

  • 文件背景:文件捲動、文件剪輯、根目錄、文件捲動。
  • div 的水平、垂直和捲動角落 (三個獨立的繪製區塊):文件捲動、文件剪輯、#one 模糊處理、文件捲動。
  • iframe #one#one 旋轉、溢位捲動片段、#one 模糊、div 捲動。
  • iframe #two#two 縮放、文件裁剪、根、文件捲動。

合成框架:表面、算繪介面和 GPU 紋理圖塊

上一篇文章所述 (實際範例請見此處),瀏覽器和轉譯程序會管理內容光柵化,然後將合成影格提交至 Viz 程序,以便在螢幕上呈現。合成影格是指轉譯 NG 的方式,代表如何拼接光柵化內容,並使用 GPU 有效率地繪圖。

資訊方塊

理論上,算繪程序或瀏覽器程序合成器可將像素光柵化為單一紋理 (即轉譯器可視區域的完整大小),並將該紋理提交至 Viz。如要顯示,顯示合成器只需將該紋理中的像素複製到影格緩衝區中的適當位置 (例如螢幕)。但是,如果該合成器想要更新一個像素,就必須重新光柵整個可視區域,然後將新的紋理提交至 Viz。

而是將可視區域分割為圖塊。獨立的 GPU 紋理圖塊,每個圖塊都具有部分可視區域的光柵化像素。接著,轉譯器就能更新個別圖塊,甚至只變更現有圖塊在螢幕上的位置。舉例來說,捲動網站時,現有資訊方塊的位置會向上移動,而且有時只有新的資訊方塊需要以光柵化的方式,顯示前往頁面下方的內容。

四個圖塊。

上圖顯示的是晴天的圖片,當中包含四個圖塊。 捲動發生時,開始顯示第五個資訊方塊。其中一個圖塊只有一種顏色 (天藍色),而頂端有一個影片和 iframe。這會導向下一個主題。

Quads 和 Surface

GPU 紋理圖塊是特殊類型的「四分之一」,這只是某種紋理類別的精美名稱。四方可識別輸入紋理,並指示如何轉換及套用視覺效果。例如,一般內容圖塊的轉換表示其在圖塊格線中的 x, y 位置。

GPU 紋理圖塊。

這些光柵化圖塊會包裝在「算繪傳遞」中,也就是部分區塊清單。算繪通道不含任何像素資訊,而是提供指示,說明如何繪製各個四分位數以產生所需的像素輸出內容。每個 GPU 紋理圖塊都有繪圖四分之一。顯示合成器只需要疊代 Quad 清單,使用指定的視覺效果繪製每個清單,即可為算繪通道產生所需的像素輸出內容。您可以在 GPU 上有效為算繪通道組合繪製區塊,因為系統會仔細選擇所允許的視覺效果,做為直接對應至 GPU 功能的視覺效果。

除了光柵化圖塊之外,還有其他類型的繪製指標。例如,單色繪製燈號完全沒有受到紋理支援,如果是影片或畫布等非圖塊紋理的紋理繪製導塊

合成器影格也可嵌入另一個合成器影格。舉例來說,瀏覽器合成器會產生具有瀏覽器 UI 的合成器影格,以及將嵌入轉譯合成器內容的空白矩形。另一個範例是網站隔離 iframe。這項嵌入作業是透過介面完成。

合成器提交合成影格時,會附上一個稱為「介面 ID」的 ID,可讓其他合成器影格透過參照嵌入。使用特定介面 ID 提交的最新合成器影格會由 Viz 儲存,而另一個合成器影格之後可透過介面繪製四方來參照,因此 Viz 會知道要繪製什麼。(請注意,表面繪製指標僅包含表面 ID,而不會包含紋理)。

中繼算繪票證

某些視覺效果 (例如許多濾鏡或進階混合模式) 需要將兩個以上的四分塊繪製為中繼紋理。接著,中繼紋理會繪製在 GPU 目的地緩衝區 (或其他中繼紋理) 中,同時套用視覺效果。為此,合成器影格實際上包含轉譯票證清單。我們一律會有一個根轉譯傳遞,也就是最後繪製,且目的地與影格緩衝區相對應,或許會有更多。

多個算繪通道可能會產生「算繪票證」的名稱。每個票證都必須在 GPU 上依序執行,以多次「傳遞」的方式執行,而單次傳遞可在單一大量平行 GPU 運算中完成。

匯總

多個合成器影格已提交至 Viz,且需要一起繪製在螢幕畫面上。這項作業會在匯總階段完成,可轉換為單一匯總合成器影格。匯總功能會依據指定的合成器影格取代表面繪製填充值。還有機會最佳化不必要的中繼紋理或畫面外的內容。舉例來說,在許多情況下,網站隔離 iframe 的合成框架不需要專屬的中繼紋理,而且可以透過適當的繪製量廣告直接繪製到影格緩衝區。匯總階段會找出這類最佳化作業,並根據個別轉譯合成器無法存取的全域知識來套用。

範例

以下是實際合成影格,代表本文開頭的範例。

  • foo.com/index.html 介面:id=0
    • 「Render Pass 0」:繪製並輸出。
      • 算繪通道繪製四方:以 3px 的模糊效果和裁剪為算繪通過 0 繪製。
        • 算繪票證 1:
          • 繪製 #one iframe 的圖塊內容 Quads,每個方塊內容各有 x 和 y 位置。
      • Surface draw quad:ID 2,使用縮放和平移轉換繪製。
  • 瀏覽器 UI 介面:ID=1
    • 「Render Pass 0」:繪製並輸出。
      • 為瀏覽器 UI 繪製部分 (同時並排)
  • bar.com/index.html 介面:ID=2
    • 「Render Pass 0」:繪製並輸出。
      • 繪製 #two iframe 內容的部分,每個內容的 x 和 y 位置。

結論

感謝您的閱讀!以及前兩篇貼文,這就是「轉譯 NG」總覽。接下來,我們將深入探討轉譯管道中許多子元件面臨的挑戰與技術,以及從頭到尾的情況。我們很快就會推出這些功能!

插圖:Una Kravets。