Chrome 的加速轉譯功能

圖層模型

Tom Wiltzius
Tom Wiltzius

簡介

對大部分的網頁開發人員而言,網頁的基本模型是 DOM。轉譯是指將網頁呈現為螢幕上的圖片,經常難以理解的程序。近年來,新世代瀏覽器改變了算繪的運作方式,活用顯示卡功能,這通常是指「硬體加速」。介紹一般網頁 (例如 Canvas2D 或 WebGL),這意味著什麼意思?本文說明 Chrome 中硬體加速轉譯網頁內容的基本模型。

大脂肪警告

我們在這裡會介紹 WebKit,更具體來說,是討論 Chromium 的 WebKit 連接埠。本文將詳細說明 Chrome 的實作詳細資訊,而非網頁平台功能。網路平台和標準並未編寫此層級的實作細節,因此本文內容無法保證適用於其他瀏覽器,但對內部知識的知識可能仍適合用於進階偵錯和效能調整作業。

此外,本文將介紹 Chrome 的轉譯架構核心,這是將大幅變化的轉譯架構核心。本文僅探討不太可能變動的細節,但無法保證這項規定在未來六個月都適用。

請務必瞭解,Chrome 當時有兩段不同的轉譯路徑:硬體加速路徑和舊版的軟體路徑。在此時,所有網頁都會在 Windows、ChromeOS 和 Android 版 Google Chrome 的硬體加速路徑上進行下去。在 Mac 和 Linux 上,只有需要組合部分內容的網頁才會加快加速程序 (請參閱下方內容,進一步瞭解需要合成的項目),但很快也會在所有網頁採用加速路徑。

最後,我們會搶先一窺轉譯引擎背後的運作原理,並探討會對效能帶來重大影響的部分功能。當您嘗試改善自家網站的效能時,瞭解圖層模型會有所幫助,但親自拍攝也很容易:圖層是很實用的結構,但建立大量圖層會使整個圖像堆疊的負載增加。你自己發動了吧!

從 DOM 到畫面

圖層簡介

網頁載入並剖析後,在瀏覽器中就會以許多網頁開發人員熟悉的結構表示:DOM。不過,算繪網頁時,瀏覽器有一系列的中繼表示法,不會直接提供給開發人員。這類結構最重要的部分是圖層。

Chrome 實際上有幾種不同的圖層類型:RenderLayers 負責 DOM 的子樹狀結構,以及負責 RenderLayers 子樹狀結構的 GraphicsLayers。我們最感興趣的是 GraphicsLayers,是上傳至 GPU 做為紋理的內容。我先從這裡說「圖層」,代表 GraphicsLayer。

GPU 術語簡介:什麼是紋理?可以視為點陣圖圖片從主記憶體 (即 RAM) 移至視訊記憶體 (例如 GPU 上的 VRAM)。經過 GPU 處理後,你就可以將其對應至網格幾何圖形。在電玩遊戲或 CAD 程式中,這項技術就是讓骨架 3D 模型「面色」。Chrome 使用紋理將許多網頁內容寫入 GPU。只要將紋理套用至非常簡單的矩形網格,即可以成本低廉的方式對應至不同位置和轉換。3D CSS 的運作原理,也非常適合快速捲動,後續操作則更多。

讓我們來看看幾個例子,說明圖層概念。

在 Chrome 中研究圖層時,在開發人員工具設定中的「算繪」標題下方,使用「顯示複合圖層框線」旗標 (也就是小圖示) 是相當實用的工具。只會醒目顯示畫面上的圖層。讓我們啟用吧!本文所擷取的螢幕截圖和範例,都是從本文撰寫時最新的 Chrome Canary 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 像素。這會導致整個元素重新配置及重新繪製,在本範例中為整個圖層。

如要查看繪製效果,使用開發人員工具中的「顯示繪製矩形」工具,還有開發人員工具設定「算繪」標題底下的工具。開啟這項功能後,請留意使用者點擊按鈕時,動畫元素和按鈕都會閃爍紅光。

「Show paint rects」(顯示繪製矩形) 核取方塊螢幕截圖
「Show paint rects」(顯示繪製矩形) 核取方塊螢幕截圖

繪製事件也會顯示在開發人員工具的時間軸中。眼看清晰的讀者可能會發現其中有兩個繪製事件:一個用於圖層,另一個用於按鈕本身,當其變更為失壓狀態時,就會重新繪製。

開發人員工具時間軸重新繪製圖層的螢幕截圖
開發人員工具時間軸重新繪製圖層的螢幕截圖

請注意,Chrome 不一定需要重新繪製整個圖層,因此會聰明地只重新繪製無效 DOM 的部分。在本例中,我們修改的 DOM 元素是整個圖層的大小。但在許多情況下,圖層會有許多 DOM 元素。

另一個顯而易見的問題,就是導致失效和強制重新繪製的原因。這很難逐一回答,因為有許多極端案例會強制撤銷。最常見的原因是操控 CSS 樣式或造成重新版面配置造成 DOM 毀損。Tony Gentilcore 發表了一篇文章,說明重新調整版面的原因。Stoyan Stefanov 發表的文章會詳細介紹繪畫 (但最後一篇為繪畫,而非這是什麼精美的合成材料)。

如果想瞭解您正在做的事情是否會影響到正在處理的事務,最好的方法就是使用開發人員工具的時間軸和顯示矩形工具,看看自己是否在造景不甚理想後重繪,然後在重新版面配置/重新繪製之前,試著找出在何處解開 DOM。如果畫作不可避免,但時間過長,請參閱 Eberhard Gräther 文章瞭解開發工具的連續繪製模式。

搭配使用:DOM 到 Screen

那麼 Chrome 如何將 DOM 轉換成螢幕圖片?概念上來說,

  1. 擷取 DOM 並將其分割成多個圖層
  2. 將每個圖層獨立繪製成軟體點陣圖
  3. 以紋理的形式上傳到 GPU
  4. 將多個圖層合併成最終畫面圖片。

以上所有程序都必須在 Chrome 首次產生網頁頁框時進行。但在之後的影格中,還是可以使用一些快速鍵:

  1. 如果某些 CSS 屬性有所變更,就不需要重新繪製任何內容。Chrome 可以將原本就在 GPU 上的現有圖層,以其他組合屬性 (例如不同位置、不透明度等) 重新合成。
  2. 如果部分的圖層失效,系統會重新繪製並重新上傳。如果內容保持不變,但複合屬性有所變更 (例如翻譯或不透明度變更),Chrome 可保留 GPU 並予以重組,製作新影格。

現在應該很清楚,圖層型合成模型會對算繪效能產生深刻影響。在不需要繪製任何項目的情況下,合成成本相當低廉,因此在嘗試對轉譯效能進行偵錯時,避免重新繪製圖層是不錯的整體目標。熟練的開發人員將查看上方的合併觸發條件清單,以輕鬆強制建立圖層。不過請注意,由於這些程式碼會佔用系統 RAM 和 GPU 上的記憶體 (特別是行動裝置上的記憶體限制),因此請謹慎思考是否建立這些容器。如果這類容器佔用大量資源,邏輯中可能會增加其他負擔,持續追蹤哪些資源可見。如果層數較大,而且與先前相比的高度重疊,許多層反而會增加光柵化的時間,這會導致有時稱為「過度繪製」。因此請善用您的知識!

以上是今天的節目內容請密切關注後續幾篇文章,瞭解如何運用圖層模型的實際影響。

其他資源