讓我們來看看主要資料結構,也就是轉譯管道的輸入和輸出內容。
這些資料結構如下:
- 影格樹狀結構由本機和遠端節點組成,這些節點代表哪些網頁文件位於哪個轉譯程序和哪個 Blink 轉譯器中。
- 不可變動的片段樹狀結構代表版面配置限制條件演算法的輸出內容 (以及輸入內容)。
- 「屬性樹狀結構」代表網頁文件的轉換、片段、效果和捲動階層。這些憑證會用於整個管道中。
- 顯示清單和繪圖區塊是轉譯和分層演算法的輸入內容。
- 合成器影格會封裝用於使用 GPU 繪製的介面、轉譯介面和 GPU 紋理圖塊。
在逐步完成這些資料結構之前,下列範例是以架構審查為基礎。本文件會使用這個範例,說明如何將資料結構套用至該範例。
<!-- Example code -->
<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 有時會選擇在與父項框架不同的轉譯程序中,轉譯跨來源框架。
在範例程式碼中,總共有三個影格:
在網站隔離模式下,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
父項影格可能會影響 foo.com/other-url
iframe 的顯示方式,方法是使用 CSS 轉換 iframe,或在 DOM 中使用其他元素遮蔽 iframe 的部分。
視覺屬性更新瀑布
裝置縮放比例和可視區大小等視覺屬性會影響算繪輸出結果,且必須在本機影格樹狀結構片段之間同步。每個本機框架樹狀結構片段的根都有一個相關聯的小工具物件。視覺屬性更新會移至主要頁框的小工具,再由上至下延伸至其餘小工具。
舉例來說,當可視區域大小變更時:
這項程序不會立即執行,因此複製的視覺屬性也會包含同步權杖。Viz 影像合成器會使用這個同步符記,等待所有本機影格樹狀圖片段使用目前的同步符記提交影像合成器影格。這個程序可避免混合具有不同視覺屬性的合成器影格。
不可變更的片段樹狀結構
不可變更的片段樹狀結構是轉譯管道版面配置階段的輸出內容。它代表網頁上所有元素的位置和大小 (未套用轉換)。
每個片段都代表一部分的 DOM 元素。通常每個元素只有一個片段,但如果在列印時分散到不同頁面,或是在多欄內容中分散到不同欄,則可能會有更多片段。
版面配置完成後,每個區段都會變成不可變更,且不會再變更。重要的是,我們也設有一些額外的限制。我們不會:
- 允許樹狀結構中的任何「上」參照。(子項無法擁有指向父項的指標)。
- 將資料「泡泡」向下傳遞至樹狀結構 (子項只會讀取子項的資訊,而不會讀取父項的資訊)。
這些限制可讓我們在後續版面配置中重複使用片段。少了這些限制,我們就得經常重新產生整個樹狀結構,這種方式所費不貲。
大多數版面配置通常是漸進式更新,例如網頁應用程式會在使用者按一下元素時,更新 UI 的一小部分。理想情況下,版面配置只應根據螢幕上實際變更的內容執行作業。我們可以盡可能重複利用前一個樹狀結構的多個部分,達到這個目的。 也就是說,我們通常只需要重新建構樹狀結構的脊椎。
日後,這項不可變動的設計可讓我們執行有趣的操作,例如在需要時跨執行緒邊界傳遞不可變動的片段樹狀結構 (以便在不同執行緒上執行後續階段)、產生多個樹狀結構以便流暢執行版面配置動畫,或執行並行推測版面配置。這也讓我們有機會使用多執行緒版面配置。
內嵌片段項目
內嵌內容 (大多為樣式文字) 採用的呈現方式略有不同。我們會在代表樹狀結構的平面清單中,呈現內嵌內容,而非使用樹狀結構中的方塊和指標。主要優點是,內嵌式平面清單表示法速度快,可用於檢查或查詢內嵌式資料結構,且記憶體效率高。這對網頁轉譯效能而言非常重要,因為文字轉譯作業相當複雜,除非經過高度最佳化,否則很容易成為管道中最慢的部分。
系統會依照其內嵌版面配置子樹的深度優先搜尋順序,為每個內嵌格式設定內容建立平面清單。清單中的每個項目都是 (物件、後代數量) 的元組。舉例來說,請考慮以下 DOM:
<div style="width: 0;">
<span style="color: blue; position: relative;">Hi</span> <b>there</b>.
</div>
width
屬性設為 0,讓該行在「Hi」和「there」之間換行。
當這種情況的內嵌格式設定內容以樹狀結構表示時,如下所示:
{
"Line box": {
"Box <span>": {
"Text": "Hi"
}
},
"Line box": {
"Box <b>": {
"Text": "There"
}
},
{
"Text": "."
}
}
扁平清單如下所示:
- (線條方塊, 2)
- (Box <span>, 1)
- (文字「Hi」,0)
- (線條方塊,3)
- (Box <b>, 1)
- (傳送文字「the」,0)
- (文字「.」, 0)
這個資料結構有許多使用者,包括無障礙 API 和幾何 API,例如 getClientRects
和 contenteditable
。每個國家/地區的需求都不同。這些元件可透過便利遊標存取平面資料結構。
游標有 MoveToNext
、MoveToNextLine
、CursorForChildren
等 API。這種遊標表示法非常適合用於文字內容,原因如下:
- 依深度優先搜尋順序疊代處理速度非常快。這種方法與插入點動作類似,因此經常使用。由於深度清單是平面清單,因此深度優先搜尋只會增加陣列偏移,可以提供快速的疊代和記憶體本地性。
- 它提供橫向搜尋功能,例如在繪製線條和內嵌方塊的背景時,就需要使用這項功能。
- 只要知道子項數量,就能快速移至下一個同胞節點 (只要將陣列偏移量增加該數量即可)。
物業樹狀結構
DOM 是元素 (加上文字節點) 的樹狀結構,CSS 可為元素套用各種樣式。
這會以四種方式顯示:
- 版面配置:輸入至版面配置限制演算法。
- Paint:如何繪製及轉成點陣圖元素 (但不包括其子項)。
- 視覺:套用至 DOM 子樹的算繪/繪圖效果,例如轉換、濾鏡和裁剪。
- 捲動:軸對齊和圓角裁剪,以及包含子樹狀圖的捲動。
屬性樹狀結構是資料結構,可說明視覺效果和捲動效果如何套用至 DOM 元素。這些元素可用來回答以下問題:在給定的 DOM 元素版面配置大小和位置下,該元素相對於螢幕的位置為何?以及:如要套用視覺效果和捲動效果,應使用哪一系列 GPU 作業?
網路上的視覺效果和捲動效果非常複雜。因此,屬性樹狀結構最重要的功能,就是將複雜性轉換為單一資料結構,以精確呈現其結構和含義,同時移除 DOM 和 CSS 的其他複雜性。這讓我們更有信心地實作合成和捲動演算法。請特別注意以下幾點:
- 可將可能容易出錯的幾何圖形和其他計算集中於單一位置。
- 建構及更新資源樹狀結構的複雜度會分離至一個轉譯管道階段。
- 相較於完整的 DOM 狀態,將屬性樹狀結構傳送至不同執行緒和程序會更簡單、更快速,因此可用於多種用途。
- 用途越多,我們就能從頂層建立的幾何圖形快取中獲得越多優勢,因為這些用途可以重複使用彼此的快取。
RenderingNG 會將屬性樹狀結構用於許多用途,包括:
- 將顏料與顏料分開,並從主要執行緒進行合成。
- 決定最佳合成 / 繪製策略。
- 測量 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 繪製順序對版面配置樹狀結構和相關片段進行疊代,以產生顯示項目清單。
例如:
<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 繪圖順序要求負值 z-index 藍色 div 繪圖順序在綠色 div (步驟 4.1) 之前 (步驟 3)。顯示項目大致對應於 CSS 繪圖順序規格中的原子步驟。單一 DOM 元素可能會產生多個顯示項目,例如 #green 有背景的顯示項目,以及內嵌文字的另一個顯示項目。如要呈現 CSS 繪製順序規格的完整複雜度,例如由負邊界產生的交錯,這種精細程度就很重要:
<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,顏色為白色。 |
drawRect 大小為 100x100,位於 0,0 位置,顏色為粉紅色。 |
drawTextBlob 的位移為 0,0,文字為「Hello world」。 |
drawRect 大小為 75x200,位於 0,0 位置,顏色為橘色。 |
drawTextBlob ,位置為 0,0,文字為「I'm falling」。 |
轉換屬性樹狀結構和繪圖區塊會變成以下內容 (為了簡潔起見,我們簡化了內容):
排序的繪圖區塊清單 (即一組顯示項目和屬性樹狀狀態) 是轉譯管道層次化步驟的輸入內容。整個繪圖區塊清單可合併為單一疊合圖層,並一起進行算繪,但這會導致每次使用者捲動時都需要進行耗時的算繪作業。系統可能會為每個繪製區塊建立複合層並個別光柵化,以免重新光柵化,但會快速耗盡 GPU 記憶體。分層步驟需要在 GPU 記憶體之間取得平衡,並降低情況發生變化時的費用。一般來說,建議您預設合併區塊,並且不要合併具有屬性樹狀圖狀態的繪圖區塊,因為這些屬性樹狀圖狀態會在合成器執行緒中變更,例如合成器執行緒捲動或合成器執行緒轉換動畫。
上述範例在理想情況下應產生兩個複合層:
- 包含繪圖指令的 800x600 複合圖層:
drawRect
,大小為 800x600,顏色為白色drawRect
大小為 100x100,位於 0,0 位置,顏色為粉紅色
- 144x224 複合圖層,其中包含繪圖指令:
drawTextBlob
的位移為 0,0,文字為「Hello world」- 翻譯 0,18
rotateZ(25deg)
drawRect
大小為 75x200,位於 0,0 位置,顏色為橘色drawTextBlob
位置為 0,0,文字為「I'm falling」
如果使用者捲動 #scroll
,系統就會移動第二個合成圖層,但不需要光柵化。
舉例來說,在前一個屬性樹狀結構的範例中,有六個繪圖區塊。以下是這些屬性 (轉換、剪輯、效果、捲動) 的樹狀狀態:
- 文件背景:文件捲動、文件剪輯、根目錄、文件捲動。
- div 的水平、垂直和捲動角落 (三個不同的繪製區塊):文件捲動、文件片段、
#one
模糊、文件捲動 - iframe
#one
:#one
旋轉、溢位捲動短片、#one
模糊、div 捲動。 - Iframe
#two
:#two
縮放、文件剪輯、根目錄、文件捲動。
合成器頁框:表面、算繪表面和 GPU 紋理圖塊
瀏覽器和轉譯程序會管理內容的掃描作業,然後將合成器影格提交至 Viz 程序,以便在螢幕上呈現。合成器影格代表如何將經過掃描處理的內容拼接在一起,並使用 GPU 有效繪製。
資訊方塊
理論上,轉譯程序或瀏覽器程序合成器可將像素光柵化為單一紋理,成為轉譯器可視區域的完整大小,然後將該紋理提交至 Viz。如要顯示紋理,顯示合成器只需從該單一紋理將像素複製到影格緩衝區中的適當位置 (例如螢幕)。不過,如果該合成器即使只想更新單一像素,也需重新柵格整個可視區域,並將新的紋理提交至 Viz。
而是將可視區域劃分為多個圖塊。每個動態磚都會使用獨立的 GPU 紋理動態磚,以掃描處理的像素作為檢視區的一部分。接著,轉譯器可以更新個別資訊方塊,甚至只需變更現有圖塊的畫面上的位置。舉例來說,捲動網站時,現有圖塊的位置會往上移,但有時只有新圖塊需要將光柵化,才能讓內容顯示在頁面更下方的位置。
四區和表面
GPU 紋理圖塊是一種特殊的 quad,這是紋理類別的另一個別名。四邊形會識別輸入紋理,並指出如何轉換紋理並套用視覺效果。舉例來說,一般內容方塊會具有轉換,可指出方塊格線中的 x、y 位置。
這些經過算繪的圖塊會包裝在算繪傳遞中,這是四邊形的清單。算繪通道不含任何像素資訊,而是提供有關繪製每個四邊形的位置和方式的指令,以產生所需的像素輸出內容。每個 GPU 紋理圖塊都有一個繪圖四邊形。顯示合成器只需依序檢查四邊形清單,並以指定的視覺效果繪製每個四邊形,以便產生算繪階段所需的像素輸出內容。您可以在 GPU 上有效地合成算繪通道的繪圖四邊形,因為允許的視覺效果經過精心挑選,可直接對應至 GPU 功能。
除了經過算繪的圖塊之外,還有其他類型的繪圖四邊形。舉例來說,有些單色繪製序列完全不受紋理支援,或有些非圖塊紋理 (例如影片或畫布) 的紋理繪製四邊形。
合成器影格也可能嵌入其他合成器影格。舉例來說,瀏覽器合成器會產生含有瀏覽器 UI 的合成器影格,以及用於嵌入轉譯合成器內容的空白矩形。另一個例子是網站隔離的 iframe。這項嵌入作業是透過途徑完成。
合成器提交合成器影格時,會附帶一個稱為「表面 ID」的 ID,讓其他合成器影格透過參照嵌入。Viz 會儲存以特定途徑 ID 提交的最新合成器影格。其他合成器影格稍後可透過途徑繪圖四邊形參照該影格,因此 Viz 就會知道要繪製哪些內容。(請注意,表面繪圖四邊形只包含表面 ID,而非紋理)。
中間算繪傳遞
某些視覺效果 (例如許多濾鏡或進階混合模式) 需要將兩個或更多四邊形繪製至中介紋理。接著,系統會將中間材質繪製至 GPU 上的目的地緩衝區 (或其他中間材質),同時套用視覺效果。為此,合成器影格實際上包含轉譯階段清單。系統一律會產生根轉譯作業,這個作業會在最後繪製,且目的地會對應至影格緩衝區,但可能還有其他作業。
由於多個算繪通道是可能的,因此才有「算繪通道」這個名稱。每個 pass 都必須在 GPU 上以多個「pass」依序執行,而單一 pass 則可在單一大量並行 GPU 運算中完成。
匯總
系統會將多個合成器影格提交至 Viz,而且需要一併繪製在螢幕上。匯總階段會將這些圖層轉換為單一匯總合成器影格,藉此完成匯總作業。匯總會按指定的合成器影格取代表面繪製四邊形。您也能趁此機會改善不必要的中繼紋理或畫面外的內容。舉例來說,在許多情況下,網站獨立 iframe 的合成器影格不需要自己的中繼紋理,而且可以透過適當的繪製四邊形直接繪製到影格緩衝區。匯總階段會找出這類最佳化項目,並根據個別轉譯合成器無法存取的全球知識套用這些最佳化項目。
範例
以下是代表本篇文章開頭範例的轉譯器影格。
foo.com/index.html
介面:id=0- 算繪通道 0:繪製至輸出。
- 算繪通道繪製四邊形:以 3 像素模糊效果繪製,並裁剪至算繪通道 0。
- 算繪通道 1:
- 為
#one
iframe 的方塊內容繪製四邊形,並為每個四邊形指定 x 和 y 位置。
- 為
- 算繪通道 1:
- 表面繪製四分之一:以 ID 2 繪製,以縮放和平移轉換功能繪製。
- 算繪通道繪製四邊形:以 3 像素模糊效果繪製,並裁剪至算繪通道 0。
- 算繪通道 0:繪製至輸出。
- 瀏覽器 UI 途徑:ID=1
- 算繪通道 0:繪製至輸出。
- 繪製瀏覽器使用者介面的四邊形 (同時並排)
- 算繪通道 0:繪製至輸出。
bar.com/index.html
surface: ID=2- 算繪傳遞 0:繪製至輸出內容。
- 針對
#two
iframe 的內容繪製序列,每個物件都有 x 和 y 位置。
- 針對
- 算繪傳遞 0:繪製至輸出內容。
插圖:Una Kravets。