この記事は Qt Blog の "QtWebKit Accelerated Compositing Report" を翻訳したものです。
執筆: No'am Rosenthal, 2011年11月10日
昨年の私のブログ記事以降も、QtWebKit の合成のアクセラレーションに関する様々な対応をしてきました。その件に関する問い合わせがいくつかあったため、この記事を書くことにしました。興味を持ってくれる人がいることを祈っています :)
今回の記事は若干内容が高度なため、合成のアクセラレーションについての基礎知識がない方は、Ariya のブログ記事 (もしくは こちら、これも Ariya によるものです)を先に読むことをお勧めします。QtWebKit 内部のグラフィックスアクセラレーションに興味がある方は先に進んでください。
まずはじめに、TextureMapper が最近の trunk ではデフォルトで有効になっています。これは QtWebKit が CSS のアニメーションと変形のみに最適化された、独自の小さなシーングラフを持っていることを意味します。TextureMapper はパブリックな API を持っていないため、CSS シェーダーのプロポーザル や preserves-3d のような特別な 3D 変形のための CSS などの対応が必要になった場合でも、簡単に変更や最適化を進めることができるようになっています。
次に、標準のウェブコンテンツにタイル式のバッキングストアを用いるために多くの時間を割きました。マルチプロセスモデルの WebKit2 では CPU による描画はバックグラウンドのプロセスで行われ、その結果は共有メモリを通して UI プロセスに渡されテクスチャにアップロードされます。このため、この作業は以前にも増して重要になります。ウェブプロセスが文字やパスの描画やその他の時間のかかる作業をしている間でも UI プロセスは固まらなくなります。クロスプロセスのタイル式のバッキングストアの改善により、ユーザーのスクロールの軌跡を解析して、次にスクリーンに表示されるエリアのタイルを描画してしまうようなユーザーエクスペリエンスの最適化を行うことができました。
クロスプロセスの環境に対応した合成のアクセラレーションの作業はとても面白い挑戦でした。プラットフォームごとに様々な方法で解決しました。まず始めにアップルの場合は、合成のアクセラレーションはクロスプロセスのアーキテクチャである CoreAnimation の上で動作しています。Chromium では我々がサポートしようと思っているモバイルプラットフォームにはそぐわない、独立した「GPU プロセス」を持っています。ここで解決すべき問題は、実際の合成をどこで行うかということでした。WebKit はレイヤーのツリーとそれぞれのレイヤーの画像のアップデート、それからそれぞれのレイヤーのアニメーションや描画情報を提供してくれます。これらはすべてウェブプロセスで行われます。我々のブラウザや QML の Web ビューでは、ピンチ操作によるズームの変更など、UI プロセスでも GPU を広範囲に渡って使用しています。ウェブプロセスでのウェブページの GPU 処理とユーザー操作による UI プロセスでの GPU 処理をどのように統合すべきでしょうか?
これには2つのオプションがあります:
我々が初めに取った方法は、自然な1の方でした。しかし、モバイルプラットフォーム上では、コンテキストの切り替えが多発したり、同期が困難だったり、フレームレートの低さを克服できないなどの問題がありました。このため、2の方法に方向転換しました。レイヤーのツリーやアニメーションの情報を UI プロセスに渡すため、シリアライズに関する大量のつまらないコードが必要でしたが、それに見合う価値がありました。レイヤーのツリーのセットアップやアニメーションの開始の為に若干のオーバーヘッドが生じますが、一旦それが済んでしまった後は、実際のフレームの合成はすべて UI プロセスで行われ、ユーザーに起因するズームやスクロールなどのジェスチャーも含めて OpenGL を用いた合成は1つの場所で行われます。
これを WebKit2 で行った際、合成のアクセラレーションのパスとタイル式のバッキングストアのパスが違うことが原因で起こる問題がいくつかありました。タイル式のバッキングストアはスクリーン上で「合成処理の行われない」コンテンツにのみ使用し、合成済みのレイヤーをその上に描画していました。これはインターネット上の大半の合成処理のないコンテンツに対してはとても大きな最適化になりますが、いくつかの厄介なバグが生じました。これらのバグは、大きなコンテンツの塊を持ち一般的に合成が必要なローテーションのアニメーションなどのエフェクトを使用している場合に起こりました。ズームの変更時のコンテンツのスケールで描画をくっきりさせるなどの技をタイル式のバッキングストアのコードの一部で行うような工夫をしましたが、合成のアクセラレーションの流れでは一度も実行されませんでした。このため、合成のアクセラレーションが有効かどうかによってくっきりするコンテンツとぼんやりするコンテンツのような差が生じてしまいました。もちろん、これは怪しい動作で、アニメーションの実行をウェブプロセスと UI プロセスの両方で (例えばあるエレメントには "left" のトランジションが、別のエレメントには "-webkit-transform" のトランジションがあるような場合) 行う場合に悲しいことになるのは言うまでもありません。
現在作業をしている trunk のバージョンでは、これに対応するためにさらに1つ上のステップを取りました。合成済みのレイヤーを合成のないのレイヤーの「上に」被せるのではなく、合成のないコンテンツも z-index の小さな1つのレイヤーとして扱うようにして、すべての合成処理をアクセラレーションの利いたプロセスで実行することにしました。これにより、すべてのレイヤーが同期され、タイルも一緒になり、くっきりとしたコンテンツのスケーリングを最背面のレイヤーかどうかに関わらずすべてのレイヤーに適用することが可能になりました。
これは何度も聞かれる質問なので、ここで回答しようと思います。QGraphicsView で常に直面する問題は、それが非常に多くの目的で使われていて、様々なユースケースに対応する必要があるため、結局ある1つの目的のための完璧な最適化ができないという点だと思います。名前のとおり、Qt Quick Scenegraph は QML アプリケーションを高速に実行するためにボトムアップで作られたもので、その目的には非常に適しています。CSS 3D/アニメーションのアクセラレーションの場合は、速度以外にも WebKit の CSS のテストをクリアするための複雑なルールが数多く存在します。我々のケースでは、パブリックな API は CSS3 であり、(QGraphcisView や Qt Quick Scenegraph のような)その他のパブリックな API の導入は不要な複雑性が増すだけです。Qt Quick Scenegraph には元々の目的を完全に満たし、CSS の描画のための追加のコードを提供することの2つを望みますが、これは必ずしも両立しません。どちらにしろ、真剣に考えた末での難しい判断でした。この判断は状況の変化次第では再度改めるかもしれません。
様々な問題が山積していて、すでに何人かが解決のためのハッキングをはじめています。以下の私の「TODO」リストが現在の状況を把握するのに役に立つかもしれません。
もし合成の可能性を知りたかったり、現在の実装で何ができて何ができていないかを知りたい場合には LayoutTests/compositing をまず見てみるのがいいでしょう。