圖層模型
簡介
對大多數的網頁開發人員來說,網頁的基本模型是 DOM。「轉譯」是指將圖片的呈現方式轉成螢幕上的圖片,通常會造成混淆。近年來,新式瀏覽器改變了轉譯方式,以充分利用顯示卡的優勢,通常稱為「硬體加速」。談到一般網頁 (例如 Canvas2D 或 WebGL 時),這個詞彙到底是什麼意思?本文將介紹在 Chrome 中硬體加速轉譯網路內容的基本模型。
大型、脂肪洞穴
這裡就是 WebKit,具體來說,也就是我們說的是 WebKit 的 Chromium 通訊埠。本文說明 Chrome 的實作細節,而非網頁平台功能。網路平台和標準不會編製此層級的實作細節,因此本文不保證其他所有瀏覽器都適用,但掌握內部知識絕對有助於進階偵錯和效能調整。
此外,請注意,本文章所討論的是 Chrome 轉譯架構的核心功能,而且這個架構正快速改變。本文僅涵蓋不可能變動的內容,但不保證會在六個月後仍然適用。
請特別注意,Chrome 目前有兩種不同的轉譯路徑:硬體加速路徑和舊版軟體路徑。截至本文撰寫時間為止,所有頁面都縮減了 Windows、ChromeOS 和 Android 版 Google Chrome 的硬體加速路徑。在 Mac 和 Linux 中,只有需要合成部分內容的網頁會加快加速路徑 (如要進一步瞭解需要合成的項目),但很快也會所有網頁都會加快加速路徑。
最後,我們會深入介紹轉譯引擎的運作原理,看看這個引擎中對效能有顯著影響的功能。當您嘗試改善自家網站的效能時,瞭解圖層模型會很有幫助,但您也可以很容易上手拍攝:圖層雖然很實用,但建立大量圖層會對圖形堆疊造成負擔。請考慮自己,以免除來!
從 DOM 到螢幕
隆重推出「圖層」
網頁載入及剖析完畢後,就會在瀏覽器中呈現出許多網頁開發人員熟悉的結構:DOM。然而,瀏覽器在轉譯網頁時,會使用一系列的中繼表示法,但開發人員並不會直接看到這些內容。其中最重要的就是圖層。
Chrome 實際上有幾種不同類型的圖層:RenderLayer (負責 DOM 子樹狀結構) 和負責 RenderLayers 子樹狀結構的 GraphicsLayers。後者對我們來說最有趣,因為 GraphicsLayers 就是以紋理形式上傳到 GPU 的元件。現在就讓「圖層」改為代表 GraphicsLayer
但簡單說明 GPU 的術語:什麼是紋理?可以視為點陣圖圖像,要從主記憶體 (即 RAM) 移至視訊記憶體 (也就是 GPU 上的 VRAM)。模型在 GPU 上後,就能對應到網格幾何圖形。在電玩遊戲或 CAD 程式中,這項技術可用來讓 3D 骨架 3D 模型「膚色」。Chrome 會使用紋理將部分網頁內容片段擷取到 GPU 上。只要將紋理套用至非常簡單的矩形網格,就能以低廉的價格將紋理對應到不同位置和變形。這就是 3D CSS 的運作方式。它也非常適合快速捲動,但會同時討論更多應用方式。
以下提供幾個範例,說明圖層的概念。
研究 Chrome 圖層時,開發人員工具中的「算繪」標題下方有「顯示複合圖層框線」旗標 (例如小齒輪圖示) 是 Chrome 的實用工具。只會醒目顯示圖層在畫面中的位置。我們來啟用吧!以下螢幕截圖和範例均擷取自最新 Chrome Canary (本文撰寫時採用的 Chrome 27) 版本。
圖 1:單一圖層頁面
<!doctype html>
<html>
<body>
<div>I am a strange root.</div>
</body>
</html>
這個頁面只有一個圖層。藍色的方格代表圖塊,您可以視為圖層的子單元,讓 Chrome 一次將大型圖層的部分內容上傳至 GPU。在這裡,這些原則並不重要。
圖 2:本身的圖層中的元素
<!doctype html>
<html>
<body>
<div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
I am a strange root.
</div>
</body>
</html>
將 3D CSS 屬性放到 <div>
來旋轉該屬性,就可以看到元素獲得專屬圖層後的樣子:請注意橘色邊框,也就是負責在這個檢視畫面中的圖層。
圖層建立條件
還有什麼方法會有自己的層?Chrome 的啟發式技術已隨著時間演進,但目前嘗試建立下列任一觸發事件圖層:
- 3D 或透視可以轉換 CSS 屬性
- 使用加速影片解碼的
<video>
個元素 - 具有 3D (WebGL) 情境或加速 2D 情境的
<canvas>
元素 - 複合外掛程式 (例如 Flash)
- 不透明度的 CSS 動畫元素或使用動畫轉換效果
- 採用加速 CSS 篩選器的元素
- 元素具有具有合成層的子系 (也就是說,如果元素有一個位於自身圖層的子元素)
- 元素的同層 Z-index 值較低,且具有合成層 (也就是會在複合式圖層上方算繪該圖層)
實用影響:動畫
也可以移動圖層,因此非常適合用於動畫。
圖 3:動畫圖層
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div>I am a strange root.</div>
</body>
</html>
如前所述,在移動靜態網路內容時,圖層非常實用。在基本案例中,Chrome 會先將圖層內容繪製到軟體點陣圖中,再將其上傳至 GPU 做為紋理。如果相關內容在日後不會有任何改變,則無須重新繪製。這是件好事:重新繪製需要花費時間在其他工作上 (例如執行 JavaScript),而且如果繪製時間過長,會導致動畫卡頓或延遲。
舉例來說,下方的開發人員工具時間軸檢視畫面:當這個圖層來迴旋轉時,系統不會執行繪製作業。
無效!重新粉刷中
不過,如果圖層的內容變更,就必須重新繪製。
圖 4:重新繪製圖層
<!doctype html>
<html>
<head>
<style>
div {
animation-duration: 5s;
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
width: 200px;
height: 200px;
margin: 100px;
background-color: gray;
}
@keyframes slide {
from {
transform: rotate(0deg);
}
to {
transform: rotate(120deg);
}
}
</style>
</head>
<body>
<div id="foo">I am a strange root.</div>
<input id="paint" type="button" value="repaint">
<script>
var w = 200;
document.getElementById('paint').onclick = function() {
document.getElementById('foo').style.width = (w++) + 'px';
}
</script>
</body>
</html>
每次按下輸入元素時,旋轉元素就會變寬 1 像素。這會導致系統重新配置整個元素,並且重新繪製整個元素,在本例中為整個圖層。
如要瞭解油漆效果,最好的辦法就是使用開發人員工具中的「顯示顏料矩形」工具,也可以在開發人員工具設定的「轉譯」標題下方。開啟後,請注意動畫元素和按鈕會在按下按鈕時閃爍紅色。
開發人員工具時間軸中也會顯示繪製事件。斜眼閱讀的讀者可能會注意到當中有兩個顏料事件:一個用於圖層,另一個則代表按鈕本身,這會在按鈕變更/離開死減狀態時重新繪製。
請注意,Chrome 不一定需要重新繪製整個圖層,而是會設法只重新繪製已失效的 DOM 部分。在本例中,我們修改的 DOM 元素是整個圖層的大小。但在許多其他情況下,圖層中會有許多 DOM 元素。
還有一個很明顯的問題,是導致無效並強迫重新繪製的原因。以上情況很難回答,因為目前有許多極端情況可能會強制撤銷無效。最常見的原因是操控 CSS 樣式或造成重新版面配置來破壞 DOM。Tony Gentilcore 提供精彩的網誌文章,說明重組的因素。Stoyan Stefanov 有一篇文章可深入介紹繪畫 (但最後只提供繪畫,而這部分文章的重點並非如此繁複)。
如果想瞭解這個程式碼是否會影響正在處理的工作,最好的方法就是使用開發人員工具的時間軸和「顯示繪製輪廓」工具,看看您是否在不想修復的情況下重新繪製 DOM,然後在重新配置/重新繪製之前,嘗試找出您移除 DOM 的位置。如果繪畫無可避免,但花費的時間似乎太久,請查看 Eberhard Gräther 的文章,瞭解開發人員工具的持續繪製模式。
放在一起: DOM to Screen
Chrome 是如何把 DOM 變成畫面圖片?概念上:
- 取用 DOM 並分割成多個圖層
- 分別在軟體點陣圖中逐一繪製這些圖層
- 以紋理的形式將圖片上傳至 GPU
- 將各種圖層組合成最終螢幕圖片。
這一切都需要在 Chrome 首次產生網頁頁框時執行。但也可以為日後的影格建立一些捷徑:
- 如果某些 CSS 屬性有變動,就不必重新繪製任何項目。Chrome 只能將位於 GPU 上的現有圖層重新合成為紋理,但具有不同的合成屬性 (例如位置不同、不透明度不同的等)。
- 如果圖層的部分內容失效,系統會重新繪製該圖層並重新上傳。如果兩者的內容維持不變,但複合屬性有變 (例如遭到翻譯或不透明度變動),Chrome 可以將其保留在 GPU 上,並重新組成新的影格。
這裡現在應該很清楚,以圖層為基礎的合成模型對轉譯效能有深遠的影響。在不需繪製任何項目的情況下,合成的成本很低,因此在嘗試對轉譯效能進行偵錯時,避免重新繪製圖層是很好的整體目標。聰明的開發人員將查看上方的合成觸發條件清單,瞭解可以輕鬆強制建立圖層。但要注意的是,這些元素並非自由,因此全然會佔用系統:這類記憶體會佔用系統 RAM 和 GPU 上的記憶體 (尤其是行動裝置的限制),因此如有大量工作負載,可能會在邏輯中造成系統追蹤哪些項目出現其他負擔。如果多個圖層大,且與先前其他範圍的重疊部分重疊,甚至會增加光柵化的時間,這有時稱為「過度繪製」。因此請務必善用知識!
本課程到此告一段落。我們還會推出更多文章,說明圖層模型的實際影響,我們日後還會推出更多文章,敬請期待。