https://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome
このコードはスリムペイントに置き換わる予定。将来的に大きな変化になり、クラス名なども変わるかもしれない(例えばRenderObjectからLayoutObject、RenderLayerからPaintLayer)。
要約
ハードウェア加速を使ったコンポジットについて記述する。
導入:なぜハードウェアコンポジットか
伝統的にブラウザはCPUによってレンダリングしてきた。GPUの登場によって小さなデバイスでさえこれをつかって最適化することを考えるようになった。GPUをつかってウェブページコンテントのコンポジットを行うと劇的に速度が上がる。
この利点は次の3点に求められる。
- GPUでページレイアウトのコンポジットを行うとCPUよりも効率が良い(速度の面でも電力の面でも)。これは多くのピクセルを伴ったオペレーションに由来する。
- GPU上のコンテントには負荷のかかるリードバッグが必要ない。(ビデオや2DキャンパスやWebGL)。
- CPUとGPUの並行実行によりパイプラインを使って同時に動かせる。
最後に注意:Chromeのグラフィックスタックは長年の進化により複雑に構成されているがここでは最先端の部分だけを取り上げるしすべてのプラットフォームで同じ構成ではない。何が有効かどうかを調べるにはGPUアーキテクチャのロードマップを見ること。現実の開発で使われていないコードは最小限で説明する。
パート1:Blinkレンダリングの基礎
Blinkレンダリングエンジンのコードは膨大で複雑でときにはドキュメントもない。ChromeでGPUがどのように動いているかを知るには、Blinkコードをブロックに分けて理解することが大切。
ノードとDOMツリー
Blinkではウェブページの内容は内部的にはDOMツリーと呼ばれるノードオブジェクトのツリーで構成されている。個々のHTMLエレメントはノードと対応する。DOMツリーのトップレベルのノードは常にドキュメントノードである。
ノードからRenderObjectsへ
DOMツリーのノードはそれのヴィジュアル表現であるRenderObjectと対応している。RenderObjectはレンダーツリーと呼ばれるパラレルなツリー構造内で保持さる。RenderObjectはその内容をどのように描画すればよいかを知っており、GraphicsContextの描画命令を呼ぶことで実現する。GraphicsContextはピクセルをビットマップに描画し、それがいずれは画面に表示される。ChromeではGraphicsContextはSkiaをラップしている(Skiaは描画ライブラリ)。
伝統的にはほとんどのGraphicsContext呼び出しはSkCanvasかSkPlatformCanvas呼び出しへとつながっていた。つまり即座にビットマップに描画された(古いモデルについての詳細はこのドキュメントを参照)。しかしペイント作業をメインスレッドから外すには(以下で詳細を述べる)、これらのコマンドはSkPictureに記録されるようになった。SkPictureはシリアル化できるデータ構造でコマンドを保持し後になってから実行できる。これはディスプレイリストと同じである。
RenderObjectからRenderLayersへ
個々のRenderObjectは直接的にか間接的にかRenderLayerと結びついている。
同じ座標空間(例えばCSS transformに影響される範囲)を共有するRenderObjectsはふつうは同じRenderLayerに属している。HTMLの中で重なってるエレメントや半透明なエレメントなどを正しく表示するためにRenderLayersが存在する。特定のRenderObjectのために新しくRenderLayerがつくられる多くの条件があり、それらはRenderBoxModelObject::requiresLayer()とその派生クラスで定義されている。RenderLayerがつくられる主なケースは以下:
- ページのルートオブジェクトである
- CSS positionプロパティが設定されている(相対、絶対またはトランスフォーム)
- 透明である
- オーバーフローをもっている(アルファマスクまたはリフレクション)
- CSS filterをもっている
- 3D(WebDL)または加速2Dをもった<canvas>エレメントと関係している
- <video>エレメントと関係している
注意すべきはRenderObjectとRenderLayerは一対一対応ではないということ。一対一対応しているものもあれば、一対多の場合もある。後者の場合はRenderObjectは祖先に対応したRenderLayerと対応している。
RenderLayerも木構造をとっている。ルートノードのRenderLayerはページのルートエレメントとに視覚的に含まれる子レイヤーが対応している。RenderLayerの子ノードは2つのソート方法によって昇順に保持されている、negZOrderListとposZOrderListである。これはZ軸で上か下かを表している。
RenderLayerからGraphicsLayerへ
コンポジターを利用するにはいくつかのRenderLayerはバックサーフェスを保持しなければならない。バックサーフェスをもつレイヤーはコンポジットレイヤーと呼ばれる。それぞれのRenderLayerは自分のGraphicsLayerをもつか(コンポジットレイヤーの場合)直近の祖先のGraphicsLayerを使う。この関係はRenderObjectとRenderLayerとの関係に似ている。
それぞれのGraphicsLayerはGraphicsContextをもちこれが関連するRenderLayerをもちここにペイントされる。コンポジターは最終的には一連のコンポジットパスのGraphicsContextの出力ビットマップを結合し最終イメージを作成する。
理論的にはすべてのRenderLayerは自分のバックサーフェスに書き込めるが、メモリー(特にVRAM)の無駄になることから、実際の実装ではRenderLayerが自分のコンポジットレイヤーを取得するには以下の条件が必要になっている(CompositingReasons.hを参照)。
- レイヤーは3Dまたはperspective transform CSSプロパティをもつ
- 加速動画デコードをつかう<video>エレメントで使用されるレイヤー
- 3Dコンテキストまたは加速2Dコンテキストの<canvas>エレメントで使用されるレイヤー
- コンポジットプラグインのために使用されるレイヤー
- 加速CSSフィルターを使うレイヤー
- コンポジットレイヤーを孫に持つレイヤー
- より低いZインデクスでコンポジットレイヤーをもつ兄弟レイヤをもつレイヤー(言い換えればレイヤーがコンポジットレイヤーの上に重なりその上にレンダーしなければならないもの)
レイヤー押し込み
例外のないルールは存在しない。上で述べたようにGraphicsLayerはメモリーや他のリソース面でコストがかかる(例えばメインパスの GraphicsLayerツリーのサイズに比例してCPU時間がかかる)。多くの追加的レイヤーが自分のバックサーフェスで既存のレイヤーに上書きすることもコストがかかる。
本質的にコンポジットが必要な理由を「直接的」理由という(例えば3Dトランスフォームを持つレイヤー)。「レイヤーの爆発的増加」を防ぐためにBlinkは直接的コンポジットに重なるRenderLayerを1つのバックストアに「押しつぶす」。これによりレイヤーの爆発的増加を防げる。より詳細な説明はこのプレゼンから、RenderLayerとコンポジットレイヤーのコードの詳細はこのプレゼンを参照。どちらも2014年1月時点の情報だが同じ年に大きく変化している。