QML Scene Graph が master に

この記事は Qt Blog の "QML Scene Graph in Master” を翻訳したものです。
執筆: Gunnar Sletta 2011年5月31日

5月の終わりに qtdeclarative-staging.git#master に QML Scene Graph を取り込みました。さぁ、モジュール化された Qt 5 リポジトリ からソースコードを取得して、ハックをはじめましょう。

この基本的な使用方法は qmlscene を実行することです。もしくは、QSGView を使用して qml ファイルを実行することも可能です。import する際の名前が QtQuick 2.0 にアップグレードされていますので、qml ファイルも書き換えてください。qml ファイルを書き換えていない場合には、qmlsceneQtQuick 1.0 のファイルを 2.0 としてロードしようとするでしょう。QDeclarativeItem ベースのプラグインはロードされないでしょう。

QML Scene Graph の紹介のために以下の動画を作成しました。

この動画で使用しているソースコードは こちらから 入手可能です。このデモでは QML プレゼンテーションシステム を使用しています。

コメントで聞かれるよりも前に、なぜ既存のシステムを使わないのかという質問に答えましょう。我々は軽量でコンパクトなモジュールで QML を動作させたいと考えました。シーングラフのコア部分は(クラスのドキュメントも含めて)1万行以下のコードで、我々のユースケース向けに作られています。それ以外は Qt と QML を繋ぐ部分です。書かれたコードは全てのプラットフォームで共通です。既存の技術の上にはこのようなスリムなものを実現することはできませんでした。

免責: この記事の内容を最終的な仕様として受け取らないでください。今現在の状態を説明しているだけで、将来変更される可能性があります。

基本機能

QML Scene Graph は Qt と QML を最適に動かすための描画システムのコア技術の変更であり、新しい機能はそんなに多くありません。少し前に同僚と一緒に現状の描画システムに関する いくつかの記事 を書き、その中でいくつかの問題点にも言及しました。一番最初の シーングラフの記事 では、私たちがどのようにそれらの問題に取り組んで行くのかを説明しました。

QML のチームは現在新しいパーティクルのシステムなどの QML の機能拡張をしていますが、彼らには準備が整い次第それらについての記事を公開するように言うつもりです。

  • Kim は既に ShaderEffectItem についての「QGraphicsView での QML ShaderEffectItem」という記事を書きました。ShaderEffectItem の基本的な考えは、門を解放し可能性をさらに広げることです。
  • テキストの描画の新しい方法を用意しています。ただ1つのテクスチャにスケーラブルなグリフを取得する "距離フィールド" に基づいた方法を既にデフォルトで使用するようにしました。この方法は浮動小数点の位置をサポートし、GPU の処理能力によってはサブピクセルのアンチエイリアスを使用することが可能です。これにより QML の Text エレメントをより高速に、より綺麗に、より柔軟に描画することができます。さらに、もう1つのフォントの描画機能があり、これは(QPainter でこれまで描画してきた)ネイティブのフォントの描画に近いものになっています。しかし、これは現在は有効になっていません。
  • Qt のアニメーションフレームワークの内部を変更し、vsync シグナルと同期してアニメーションができるようにしました。このコンセプトは "Velvet and the QML Scene Graph" の記事で既に紹介しています。

パブリック API とバックエンド API

我々は、API を2つの異なるタイプに分けました。アプリケーション開発者によって使われるパブリック API と、システムインテグレーターが使用するバックエンド API です。パブリック API には QML を描画するために必要な全てのクラスが含まれます。新しいプリミティブや独自シェーダー等を導入するための API の他、低レベルの API を使用する便利な API をいくつか提供します。ドキュメントに記載されている全てのファイルはパブリック API になります。ここで、そのドキュメントへのリンクを貼ろうと思ったのですが、モジュール化されたリポジトリではまだドキュメントの自動生成が行われていないため、後回しにします。

バックエンド API にはレンダラやテクスチャなどの実装が含まれます。これによりこのシステムのいくつかの部分を、ハードウェア毎に必要に応じて最適化ができるようになります。バックエンド API はプライベートヘッダの中にあります。その中のいくつかはパブリック API になるかもしれませんが、そのために必要な API の確定にはまだ時期尚早だと思っています。

描画のモデル

基本的にシーングラフとは定義済みのノードで構成される1つのツリーです。描画はそのツリーのジオメトリノードを配置することで行われます。ジオメトリノードとは描画する頂点やメッシュのジオメトリと、そのジオメトリをどう処理するかを定義するマテリアルで構成されます。マテリアルは基本的には QGLShaderProgram にロジックをいくつか追加したものになります。

ノードを描画する際には、レンダラがツリーを取得し描画します。パフォーマンスの改善のため、レンダラは描画が正しいことを保証した上でそのツリーを自由に解釈することができます。デフォルトのレンダラでは非透過のジオメトリと半透明のジオメトリを別々に持ちます。非透過のジオメトリが先に描画され、ステートの変更が最小限になるようにマテリアルの順番が計算されます。半透明のジオメトリは色バッファと深度バッファの両方に描画されます。アイテムの深度はグラフの中の元の順序によって決定されます。半透明のジオメトリを描画する際には深度のチェックを有効にし、非透過のジオメトリが半透明ジオメトリより手前に描画される場合には GPU は透過ピクセルに対して何もしないようにしています。デフォルトのレンダラには非透過のジオメトリを厳密に手前から奥に向かって描画するスイッチ(現在は -opaque-front-to-back オプションを qmlscene に指定することで有効になります)があります。バックエンド API を使用して、別のアルゴリズムでツリーを描画するようなカスタムレンダラを実装することもできます。

シーングラフは1つの OpenGL コンテキストで描画を行いますが、それを生成したり所有したりはしません。様々なコンテキストが使用可能で、QSGView の場合には QGLWidget の派生クラス由来のコンテキストを使用しています。今年の夏以降に予定されている master でのグラフィックスタックの差し替え後は、OpenGL コンテキストはウィンドウ由来のものになるでしょう。

スレッドを使用する

QML Scene Graph はスレッドとは無関係なため、OpenGL コンテキストが関連づけられていればどんなスレッドでも動作可能です。しかし、シーングラフがコンテキスト/スレッドへ設定された後にはスレッドを移動することはできません。初めは、QML のアニメーションと全ての OpenGL の呼び出しを描画専用のスレッドで行おうと思っていましたが、QML を動かす仕組み的にそれは実現できないということが分かりました。QML のアニメーションは QML のプロパティに作用する機能で、このプロパティは QObject で C++ で実装されています。もし描画スレッドでアニメーションを実行し C++ のコードを呼び出すとなると、同期地獄に陥るでしょう。このため、別のアプローチを取ることにしました。

OpenGL のコンテキストと全てのシーングラフ関連のコードはドキュメントに明示的に記載されていない限りは描画スレッド(Render Thread)で動作します。アニメーションは GUI スレッド(GUI Thread)で動作しますが、描画スレッドから送られたイベントによって操作されます。1つのフレームの描画が始まる前に GUI スレッドを一瞬止めて、QML ツリーとその変更をシーングラフにコピーします(Synchronize Graph)。シーングラフはその時点での QML ツリーのスナップショットを表します。ユーザー API で見ると、これは QSGItem::updatePaintNode() の中で起こります。これを図で示すと以下のようになります。

このモデルの利点は、現在のフレームの描画を描画スレッドの中で実行している間にも、次のフレームのためのアニメーションの計算や GUI スレッド上での JavaScript のバインディングの評価ができることです(Advanced Animations Frame)。アニメーションを先に進め、JavaScript の評価をするのは一般的に描画(Render Frame)よりも時間がかかりますが、この処理は描画スレッドが次の vsync シグナルまでの間にブロックしている間(Swap and wait for vsync)にも実行されることになります。シングルコアの CPU であっても、描画スレッドが vsync シグナルを待っているアイドル状態でもアニメーションを先に進めることができます。これは一般的に swapBuffers() で行われます。

QPainter との共存

QML Scene Graph 自体は QPainter を使用しませんが、使用する方法はいくつかあります。真っ先に思い浮かぶ方法は QSGPaintedItem クラスを使用することです。このクラスはユーザーの要求に応じて QImage か FBO(FrameBuffer Object) に対して QPainter をオープンし、upadte() により描画のリクエストがあった際に仮想関数 paint() を呼び出します。このクラスが QDeclarativeItem がシーングラフで動作するようになった際の移植の第一候補になります。この2つのクラスの API は同じですが、内部の動作は少し異なります。paint() 関数はデフォルトでは "sync" フェイズの間に呼び出され、この間 GUI スレッドはスレッドに関わる問題を回避するためにブロックされます。しかし、GUI スレッドとは分離した描画スレッド上で実行するように切り替えることも可能です。

もう1つの選択肢は FBO や QImage に自分で描画をし、その結果を QSGSimpleTextureNode などに追加することです。

OpenGL との共存

OpenGL と一緒に使うための方法は3つあります。

  • シーングラフでの描画が行われる前に QSGEngine::beforeRendering() シグナルが描画スレッド上で発生します。このシグナルを利用することにより、QML の UI をシーングラフで描画する際の背景に GL のコードを利用した描画ができます。この場合、シーングラフでの描画時にこの GL で書かれた背景をクリアしないようにするための設定が必要で、QSGEngine にいくつかこのプロパティがあります。例えば、3D のゲームのエンジンが背景を描画し、その前面に QML の UI を表示するようなケースが考えられます。
  • シーングラフが描画の完了後でスワップが起こる前には QSGEngine::afterRendering() が同じく描画スレッド上で発生します。このシグナルを利用すると QML の UI より前面に 3D のコンテンツを描画することが可能になります。
  • FBO に描画し、シーングラフ内のテクスチャを合成する。これは QML 内にコンテンツを埋め込むために推奨される方法で、不透明度やクリッピングや変形などの QML の機能が使用可能になります。下記のビデオは QML Scene Graph にオフスクリーンテクスチャを使用して Ogre3D を埋め込んだサンプルです。これを実現する簡単な方法の1つは QSGPaintedItem の派生クラスを作成し、FBO ベースの描画をし、paint() 関数の中で QPainter::beginNativePainting() を実行することです。

デバッグモード

今現在、アニメーションを追跡するためにいくつかの環境変数を用意しています。

  • QML_TRANSLUCENT_MODE=1 を環境変数に設定すると、全てのジオメトリノードが不透明度 0.5 で描画されます。いくつかのマテリアルでは不透明度は無視されるかもしれませんが、そういったものは多くないはずです。この設定は興味のある QML の要素が他のものによって見えなくなっている場合に便利でしょう。
  • QML_FLASH_MODE=1 を環境変数に設定するのは Qt で QT_FLUSH_UPDATE を設定するのと同じです。描画の更新が必要な全ての QML 要素にて1フレームだけその領域がフラッシュされます。この設定はアニメーションを調査する際に便利でしょう。

この2つを組み合わせることによって、現在の状態で裏に隠れて行われているアニメーションを調査することができます。

連絡先一覧

これらの新しいものを使う際には、バグを報告したり、機能不足を指摘したり、改善の提案などをしていただけると助かります。それぞれ正しい場所は以下の通りです。

  • バグや機能: http://bugreports.qt.nokia.com シーングラフの描画 API に関連した話題のために "SceneGraph" 要素を用意しています。
  • IRC: freenode.net の #qt-graphics が一番グラフィック関係の人間が集まっているところです。
  • メール: qt5-feedback メーリングリスト がこの元記事が書かれた数週間前に作られました。
  • QML Scene Graph の議論は Qt Contributors' Summit でも行われます。

数字をいくつか

ここでいくつかの数字をシェアしようと思います。以下の数値は demos/declarative 以下にある photoviewer のデモを QML 1 上で、ラスタ、OpenGL、LLVMpipe を使用した Mesa によるソフトウェアでの描画と、QML 2 上で、OpenGL、Mesa/LLVMpipe で実行した結果になります。Intel Sandy Bridge i7-2600K で、オンボードの Intel HD Graphics 3000 GPU を使用し、Qt 5 の HEAD を Linux 上で XCB のバックエンド(ラスタと OpenGL の描画エンジンでは 4.8 での X11 と同じになります)を使用して実行しました。

上記のグラフでお分かりのように、 QML 1 の描画スタックと比較すると QML Scene Graph では、任意の QML のサンプルで全体的に 2.5 倍ほど高速になっています。もう1つ興味深い部分は、LLVMpipe が我々のソフトウェアのラスタエンジンと同じレンジにあり、実際は少しそれよりも高速だということです。これは LLVMpipe が基本的には全く同じようなことをしようとしているので、驚くことではありませんが。QML 2 のマルチスレッドの LLVMpipe 版は QML 1 を OpenGL で動かした場合よりも高速です。これらの結果が Qt 5 が OpenGL に依存するということに対する懸念を払拭する助けになればと思います。


Blog Topics:

Comments