Accelerated Rendering in Chrome

The Layer Model

HTML5 Rocks

はじめに

For most web developers the fundamental model of a web page is the DOM. Rendering is the often obscure process of turning this representation of a page into a picture on the screen. Modern browsers have changed the way rendering works in recent years to take advantage of graphics cards: this is often vaguely referred to as “hardware acceleration”. When talking about a normal web page (i.e. not Canvas2D or WebGL), what does that term really mean? This article explains the basic model that underpins hardware accelerated rendering of web content in Chrome.

ほとんどのWebデベロッパーにとって、Webページの基本的なモデルはDOMです。このページの表現をスクリーン上の画像に変換するための、通常不明瞭なプロセスをレンダリングと呼びます。モダンブラウザはここ何年かの間、グラフィックスカードを利用するために、レンダリングの方式を変更してきました。このような方式は漠然と「ハードウェア・アクセラレーション」と称されていますが、通常のWebページ (つまり、Canvas2DやWebGLを使用していないベージ) において、この用語はどのような意味を持つのでしょうか? この記事では、Chromeにおけるハードウェア・アクセラレーションを利用したレンダリングに関して、それを支える基本的なモデルについて説明します。

大きな警告

We’re talking about WebKit here, and more specifically we’re talking about the Chromium port of WebKit. This article covers implementation details of Chrome, not web platform features. The web platform and standards don’t codify this level of implementation detail, so there are no guarantees anything in this article will apply to other browsers, but knowledge of internals can nevertheless be useful for advanced debugging and performance tuning.

我々はここでWebKitについて話します。より厳密に言うと、WebKitの Chromiumポートについて話します。当記事はChromeの実装の詳細を題材にしており、Web プラットフォームについて論じている訳ではありません。Web プラットフォームとWeb標準は、ここで書かれている程までには実装の詳細について明文化されていません。よって、当記事で扱う事柄は、他のブラウザでは当てはまらないかもしれません。しかしながら内部構造を知ることで、デバッグやパフォーマンスチューニングに活用することができるでしょう。

Also, note that this entire article is discussing a core piece of Chrome’s rendering architecture that’s changing very fast. This article attempts to cover only stuff that’s unlikely to change, but no guarantees that it’ll all still apply in six months.

また、ここで論じられるChromeのコアモジュールのレンダリング・アーキテクチャは、それ自身ものすごい速さで変化しています。記事を書くに当たって、なるべく変化の少ない部分を扱うように心がけましたが、それでも6ヶ月後に当記事に書かれているすべてのことが適用可能である保証はありません。

It’s important to understand that Chrome has had two different rendering paths for a while now: the hardware-accelerated path and the older software path. As of this writing all pages go down the hardware accelerated path on Windows, ChromeOS, and Chrome for Android. On Mac and Linux only pages that need compositing for some of their content go down the accelerated path (see below for more on what would require compositing), but soon all pages will go down the accelerated path there, too.

Chromeでは、現在のところ、二つの異なるレンダリング・パスがあります。ハードウェアパスと(古い)ソフトウェアパスです。執筆時点では、Windows版 Chrome, ChromeOS, そしてChrome for Androidにおいては、すべてのページはハードウェアパスで処理されます。MacとLinuxはハードウェア合成が必要なコンテンツを含んだページのみがハードウェアパスで処理されます。(ハードウェア合成が必要なコンテンツについては後述します。) しかしながら、MacとLinuxにおいても、いずれ近いうちにすべてのページがハードウェアパスで処理されるようになるでしょう。

Lastly, we’re peeking under the hood of the rendering engine and looking at features of it that have a big impact on performance. When trying to improve the performance of your own site it can be helpful to understand the layer model, but it’s also easy to shoot yourself in the foot: layers are useful constructs, but creating lots of them can introduce overhead throughout the graphics stack. Consider yourself forewarned!

最後に、我々はレンダリング・エンジンの内部をのぞいて、パフォーマンスに大きく影響する機能に関して見ていきます。ご自身のサイトのパフォーマンスを改善するに当たって、このレイヤーモデルに関して知ることは助けになる一方、自ら墓穴を掘ることにもなりかねません。レイヤーモデルは便利な構造であると同時に、たくさん作りすぎるとグラフィックス全体のオーバーヘッドとなる可能性があります。使用前に十分に考慮してください。

DOMからスクリーンへ

レイヤーとは

Once a page is loaded and parsed, it's represented in the browser as a structure many web developers are familiar with: DOM. When rendering a page, however, the browser has a series of intermediate representations that aren’t directly exposed to developers. The most important of these structures is a layer.

Webページは、ひとたびロードされ、パースされると、多くのWebデベロッパーにおなじみの構造、つまりDOMに変換されます。しかしながら、ページをレンダリングするに当たって、ブラウザはデベロッパーが直接のぞくことのできないいくつかの中間形式を持ちます。これらのうち最も重要なものはレイヤーです。

In Chrome there are actually several different types of layers: RenderLayers, which are responsible for subtrees of the DOM, and GraphicsLayers, which are responsible for subtrees of RenderLayers. The latter is most interesting to us here, because GraphicsLayers are what get uploaded to the GPU as textures. I’ll just say “layer” from here on out to mean GraphicsLayer.

Chromeにおいては、実際のところ複数の異なるタイプのレイヤーが存在します。RenderレイヤーはDOMのサブツリーに関して責任を持ちます。また、GraphicsレイヤーはRenderレイヤーのサブツリーに関して責任を持ちます。後者は我々にとってより興味深いものとなります。なぜならGraphicsレイヤーはテクスチャとしてGPUにアップロードされるからです。以後、単に「レイヤー」と行った場合はこのGraphicsレイヤーを指します。

Quick aside on GPU terminology: what’s a texture? Think of it as a bitmap image that’s moved from main memory (i.e. RAM) to video memory (i.e. VRAM, on your GPU). Once it’s on the GPU, you can map it to a mesh geometry -- in video games or CAD programs, this technique is used to give skeletal 3D models “skin.” Chrome uses textures to get chunks of web page content onto the GPU. Textures can be cheaply mapped to different positions and transformations by applying them to a really simple rectangular mesh. This is how 3D CSS works, and it’s also great for fast scrolling -- but more on both of these later.

少し話は逸れますが、GPUの用語でテクスチャとは何でしょう?メインメモリ(つまりRAM)からビデオメモリ(GPUに搭載されたVRAM)に転送されるビットマップのようなものだと思ってください。ひとたびGPUに転送されると、テクスチャを3Dメッシュにマッピングできます。-- ビデオゲームやCADのプログラムでは、3Dの骨格モデルに「スキン」を与えるためにこの技術が利用されています。ChromeはWebページのコンテンツの大量のデータをGPUで扱うためにテクスチャを使用します。テクスチャは単純な矩形のメッシュに貼付けることで、容易にリポジショニングやトランスフォーメーションができます。これにより3D CSSや高速スクロールが可能になります。これらについては後述します。

Let’s look at a couple examples to illustrate the layer concept.

では、レイヤーの概念を説明するために、2つの例を見てみましょう。

A very useful tool when studying layers in Chrome is the “show composited layer borders” flag in the settings (i.e. little cog icon) in Dev Tools, under the “rendering” heading. It very simply highlights where layers are on-screen. Let’s turn it on. These screenshots and examples are all taken from the latest Chrome Canary, Chrome 27 at the time of this writing.

Chrome のレイヤーを学ぶには、まず DevTools の設定(小さな歯車のアイコン) から “rendering” 配下の “show composited layer borders” フラグを有効にしてください。これにより、レイヤーがハイライトされるので、スクリーン上のどこに位置するか分かるようになります。以下のスクリーンショットと例はすべて最新のChrome Canary (執筆時点ではChrome 27) で実行されたものです。

Figure 1: A single-layer page. (open stand-alone)

図1: シングルレイヤーのページ (別ウィンドウで表示)

<html>
  <body>
    <div>I am a strange root.</div>
  </body>
</html>
Screenshot of composited layer render borders around the page's base layer
Screenshot of composited layer render borders around the page's base layer

このページはレイヤーを1つだけ持ちます。青い線はタイルを表します。タイルは、大きなレイヤーを扱う際に、Chromeが分割してGPUにアップロードするための単位だと思ってください。タイルはここではあまり重要ではありません。

Figure 2: An element in its own layer (open stand-alone)

図2: 自身のレイヤーを持つ要素(別ウィンドウで表示)

<html>
  <body>
    <div style="-webkit-transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
      I am a strange root.
    </div>
  </body>
</html>
Screenshot of rotated layer's render borders
Screenshot of rotated layer's render borders

By putting a 3D CSS property on the <div> that rotates it, we can see what it looks like when an element gets its own layer: note the orange border, which outlines a layer in this view.

自身のレイヤーを持つ要素がどのようなものか見るために、ここでは <div> に3D CSSプロパティを追加してローテーションしています。オレンジ色の境界線に着目してください。これはビュー内でのレイヤーの外枠を示します。

レイヤー生成の指針

What else gets its own layer? Chrome’s heuristics here have evolved over time and continue to, but currently any of the following trigger layer creation:

では、ローテーション以外に自身のレイヤーを持つケースはあるのでしょうか?Chromeは進化の過程にあるので、今後変化する可能性がありますが、現時点では以下の条件のいずれかを満たした場合にレイヤーが作成されます。

  • 3D or perspective transform CSS properties
  • 3D or perspective transform CSS properties
  • <video> elements using accelerated video decoding
  • HWデコーダを使用した<video> 要素
  • <canvas> elements with a 3D (WebGL) context or accelerated 2D context
  • 3D (WebGL) コンテキスト、もしくはHWアクセラレーションを有効にした状態での2Dコンテキストを使用した<canvas> 要素
  • Composited plugins (i.e. Flash)
  • Compositorを利用するプラグイン(例:Flash)
  • Elements with CSS animation for their opacity or using an animated webkit transform
  • 透過色もしくはwebkit-transformを使用したCSSアニメーション
  • Elements with accelerated CSS filters
  • CSS Filterを使用した要素
  • Element has a descendant that has a compositing layer (in other words if the element has a child element that’s in its own layer)
  • Element has a sibling with a lower z-index which has a compositing layer (in other words the it’s rendered on top of a composited layer)

実践:アニメーション

We can move layers around, too, which makes them very useful for animation.

アニメーション

Figure 3: Animated Layers (open stand-alone)

<html>
<head>
    <style type="text/css">
    div {
        -webkit-animation-duration: 5s;
        -webkit-animation-name: slide;
        -webkit-animation-iteration-count: infinite;
        -webkit-animation-direction: alternate;
        width: 200px;
        height: 200px;
        margin: 100px;
        background-color: gray;
    }
    @-webkit-keyframes slide {
        from {
            -webkit-transform: rotate(0deg);
        }
        to {
            -webkit-transform: rotate(120deg);
        }
    }
    </style>
</head>
<body>
    <div>I am a strange root.</div>
</body>
</html>

As mentioned earlier, layers are really useful for moving around static web content. In the basic case, Chrome paints the contents of a layer into a software bitmap before uploading it to the GPU as a texture. If that content doesn’t change in the future, it doesn’t need to be repainted. This is a Good Thing: repainting takes time that can be spent on other stuff, like running JavaScript, and if the paint is long it causes hitches or delays in animations.

先にも述べましたが、レイヤーは静的なWebコンテンツを移動させるのに大変便利です。通常の場合、Chromeはレイヤーの内容をいったんソフトウェア・ビットマップに描画してから、GPUにテクスチャとして転送します。コンテンツがもし将来的に変わらなければ、再度ビットマップを描画する必要はありません。これは良いことです。なぜなら再描画するということは、JavaScriptの実行等、他の処理の時間を奪うからです。描画時間が長引くと、結果的にアニメーションがカクカクしたり、遅れたりします。

See, for instance, this view of Dev Tools timeline: there are no paint operations while this layer is rotating back and forth.

Screenshot of Dev Tools timeline during animation
Screenshot of Dev Tools timeline during animation

Invalid! Repainting

But if the layer’s content changes, it has to be repainted.

Figure 4: Repainting Layers (open stand-alone)

<html>
<head>
  <style type="text/css">
  div {
      -webkit-animation-duration: 5s;
      -webkit-animation-name: slide;
      -webkit-animation-iteration-count: infinite;
      -webkit-animation-direction: alternate;
      width: 200px;
      height: 200px;
      margin: 100px;
      background-color: gray;
  }
  @-webkit-keyframes slide {
      from {
          -webkit-transform: rotate(0deg);
      }
      to {
          -webkit-transform: rotate(120deg);
      }
  }
  </style>
</head>
<body>
  <div id="foo">I am a strange root.</div>
  <input id="paint" type="button" onclick="" value=”repaint”>
  <script>
    var w = 200;
    document.getElementById('paint').onclick = function() {
      document.getElementById('foo').style.width = (w++) + 'px';
    }
  </script>
</body>
</html>

Every time the input element is clicked the rotating element gets 1px wider. This causes relayout and repainting of the entire element, which in this case is an entire layer.

A good way to see what’s getting painted is with the “show paint rects” tool in Dev Tools, also under the “Rendering” heading of Dev Tools’ settings. After turning it on, notice that the animated element and the button both flash red when the button is clicked.

Screenshot of show paint rects checkbox
Screenshot of show paint rects checkbox

The paint events show up in the Dev Tools timeline, too. Sharp-eyed readers might notice there are two paint events there: one for the layer, and one for the button itself, which gets repainted when it changes to/from its depressed state.

Screenshot of Dev Tools Timeline repainting a layer
Screenshot of Dev Tools Timeline repainting a layer

Note that Chrome doesn’t always need to repaint the entire layer, it tries to be smart about only repainting the part of the DOM that’s been invalidated. In this case, the DOM element that we modified is the size of the entire layer. But in many other cases, there will be lots of DOM elements in a layer.

An obvious next question is what causes an invalidation and forces a repaint. This is tricky to answer exhaustively because there are a lot of edge cases that can force invalidations. The most common cause is dirtying the DOM by manipulating CSS styles or causing relayout. Tony Gentilcore has a great blog post on what causes relayout, and Stoyan Stefanov has an article that covers painting in more detail (but it ends with just painting, not this fancy compositing stuff).

The best way to figure out if its affecting something you’re working on is to use the Dev Tools Timeline and Show Paint Rects tools to see if you’re repainting when you wish you weren’t, then try to identify where you dirtied the DOM right before that relayout/repaint. If painting is inevitable but seems to be taking unreasonably long, check out Eberhard Gräther’s article on continuous painting mode in Dev Tools.

Putting it together: DOM to Screen

So how does Chrome turn the DOM into a screen image? Conceptually, it:

  1. Takes the DOM and splits it up into layers
  2. Paints each of these layers independently into software bitmaps
  3. Uploads them to the GPU as textures
  4. Composites the various layers together into the final screen image.

That all needs to happen the first time Chrome produces a frame of a web page. But then it can take some shortcuts for future frames:

  • If certain CSS properties change, it isn’t necessary to repaint anything. Chrome can just recomposite the existing layers that are already sitting on the GPU as textures, but with different compositing properties (e.g. in different positions, with different opacities, etc).
  • If part of a layer gets invalidated, it gets repainted and re-uploaded. If its content remains the same but its composited attributes change (e.g. it gets translated or its opacity changes), Chrome can leave it on the GPU and recomposite to make a new frame.

As should now be clear, the layer-based compositing model has deep implications for rendering performance. Compositing is comparably cheap when nothing needs to be painted, so avoiding repaints of layers is a good overall goal when trying to debug rendering perf. Savvy developers will take a look at the list of compositing triggers above and realize that its possible to easily force the creation of layers. But beware just blindly creating them, as they’re not free: they take up memory in system RAM and on the GPU (particularly limited on mobile devices) and having lots of them can introduce other overhead in the logic keeps track of which are visible. Many layers can also actually increase time spent rasterizing if they layers are large and overlap a lot where they didn’t previously, leading to what’s sometimes referred to as “overdraw”. So use your knowledge wisely!

That’s all for now. Stay tuned for a couple more articles on practical implications of the layer model.

Additional Resources

  1. Scrolling Performance
  2. Profiling Long Paint Times with DevTools' Continuous Painting Mode
  3. http://jankfree.com
  4. http://aerotwist.com/blog/on-translate3d-and-layer-creation-hacks/

Comments

0