Qtクイックシーングラフ

Qt Quickのシーングラフ

Qt Quick 2 では、専用のシーングラフを使用し、OpenGL ES、OpenGL、Vulkan、Metal、Direct 3D などのグラフィックス API を介してレンダリングされます。従来の命令型ペイントシステム(QPainter など)ではなく、グラフィックスにシーングラフを使用することは、レンダリングするシーンをフレーム間で保持し、レンダリングするプリミティブの完全なセットをレンダリング開始前に知ることができることを意味します。これにより、状態変化を最小化するバッチレンダリングや、不明瞭なプリミティブの破棄など、さまざまな最適化が可能になります。

たとえば、ユーザーインターフェースに10項目のリストがあり、各項目に背景色、アイコン、テキストがあるとする。従来の描画テクニックを使用すると、30回の描画呼び出しと同程度の状態変更が発生します。一方、シーングラフでは、すべての背景が1回の呼び出しで描画され、次にすべてのアイコンが描画され、次にすべてのテキストが描画されるように、レンダリングするプリミティブを再編成することができます。

シーングラフはQt Quick 2.0と密接に結びついており、スタンドアロンでは使用できません。シーングラフは、QQuickWindow クラスによって管理され、レンダリングされます。カスタムアイテムタイプは、QQuickItem::updatePaintNode() を呼び出すことで、そのグラフィカルプリミティブをシーングラフに追加できます。

シーングラフはItemシーンのグラフィカルな表現であり、すべてのアイテムをレンダリングするのに十分な情報を含む独立した構造体である。一度セットアップされると、アイテムの状態とは無関係に操作し、レンダリングすることができる。多くのプラットフォームでは、GUIスレッドが次のフレームのステートを準備している間、シーングラフは専用のレンダースレッドでレンダリングされます。

注意: このページに記載されている情報の多くは、Qt Quick Scene グラフのビルトイン、デフォルトの動作に特有のものです。software のような別のシーングラフ適応を使用する場合、すべての概念が適用されるとは限りません。異なるシーングラフアダプテーションの詳細については、シーングラフアダプテーションを参照してください。

Qt Quick Scene グラフの構造

シーングラフは、多くの定義済みのノードタイプで構成され、それぞれが専用の目的を果たします。シーングラフと呼んでいますが、より正確な定義はノードツリーです。ツリーはQMLシーン内のQQuickItem タイプから構築され、内部的にはシーンを描画するレンダラーによって処理されます。ノード自体には、アクティブな描画コードや仮想paint() 関数は含まれていません

ノードツリーの大部分は、既存の Qt Quick QML タイプによって内部的に構築されますが、3D モデルを表すサブツリーを含め、ユーザーが独自のコンテンツを持つ完全なサブツリーを追加することも可能です。

ノード

ユーザーにとって最も重要なノードは、QSGGeometryNode です。これは、ジオメトリとマテリアルを定義してカスタムグラフィックを定義するために使用されます。ジオメトリはQSGGeometry を使用して定義され、グラフィカルプリミティブの形状またはメッシュを記述します。線、矩形、多角形、多数の切断された矩形、または複雑な3Dメッシュにすることができます。マテリアルは、この形状のピクセルがどのように塗りつぶされるかを定義します。

ノードは任意の数の子を持つことができ、ジオメトリノードは子順に表示され、子の後ろに親があるようにレンダリングされます。

注: これはレンダラーでの実際のレンダリング順序については何も言いません。視覚的な出力のみが保証されます。

使用可能なノードは次のとおりです:

QSGClipNode

シーングラフのクリッピング機能の実装

QSGGeometryNode

シーングラフ内のすべてのレンダリングコンテンツに使用されます。

QSGNode

シーングラフ内のすべてのノードのベースクラス

QSGOpacityNode

ノードの不透明度を変更するために使用されます。

QSGTransformNode

シーングラフ内のトランスフォームの実装

カスタムノードは、QQuickItem::updatePaintNode ()をサブクラス化し、QQuickItem::ItemHasContents フラグを設定することで、シーングラフに追加されます。

警告 ネイティブ・グラフィックス(OpenGL、Vulkan、Metalなど)の操作とシーングラフとのインタラクションは、主にupdatePaintNode()呼び出しの間、レンダースレッドでのみ行われることが重要です。経験則では、"QSG" 接頭辞を持つクラスのみをQQuickItem::updatePaintNode() 関数内で使用します。

詳細は、シーングラフ - カスタムジオメトリを参照してください。

前処理

ノードには仮想のQSGNode::preprocess() 関数があり、シーングラフがレンダリングされる前に呼び出されます。ノードのサブクラスは、フラグQSGNode::UsePreprocess を設定し、QSGNode::preprocess() 関数をオーバーライドして、ノードの最終準備を行うことができます。例えば、ベジェ曲線を現在のスケールファクターに適した詳細レベルに分割したり、テクスチャの一部を更新したりします。

ノードの所有権

ノードの所有権は、作成者が明示的に行うか、シーングラフがフラグQSGNode::OwnedByParent を設定することで行います。シーングラフの所有権をシーングラフに割り当てることは、シーングラフがGUIスレッドの外に存在する場合にクリーンアップを簡素化するので、多くの場合、好ましいことです。

マテリアル

マテリアルは、QSGGeometryNode のジオメトリの内部がどのように塗りつぶされるかを記述します。マテリアルは、グラフィックスパイプラインの頂点とフラグメントステージ用のグラフィックスシェーダをカプセル化し、実現できることに十分な柔軟性を提供します。

QMLアイテムタイプにカスタムシェーディングを適用したい場合、ShaderEffect タイプを使用してQMLで直接行うことができます。

以下はマテリアルクラスの完全なリストです:

QSGFlatColorMaterial

シーングラフでソリッドカラーのジオメトリをレンダリングする便利な方法です。

QSGMaterial

シェーダプログラムのレンダリング状態のカプセル化

QSGMaterialShader

グラフィックスAPIに依存しないシェーダープログラムを表します。

QSGMaterialType

QSGMaterial と組み合わせて一意の型トークンとして使用されます。

QSGOpaqueTextureMaterial

シーングラフでテクスチャ付きジオメトリをレンダリングする便利な方法

QSGTextureMaterial

シーングラフでテクスチャ付きジオメトリをレンダリングする便利な方法

QSGVertexColorMaterial

シーングラフで頂点ごとに色付けされたジオメトリをレンダリングする便利な方法

便利なノード

シーングラフAPIは低レベルで、利便性よりもパフォーマンスに重点を置いています。カスタムのジオメトリやマテリアルをゼロから書くには、たとえ最も基本的なものであっても、自明ではない量のコードが必要です。そのため、APIにはいくつかの便利なクラスがあり、最も一般的なカスタムノードを簡単に利用できるようになっています。

シーングラフとレンダリング

シーングラフのレンダリングはQQuickWindow クラスの内部で行われ、アクセスするためのパブリック API はありません。しかし、レンダリングパイプラインには、ユーザーがアプリケーションコードを追加できる場所がいくつかあります。これは、カスタムシーングラフコンテンツを追加したり、シーングラフで使用されているグラフィックスAPI(OpenGL、Vulkan、Metalなど)を直接呼び出して任意のレンダリングコマンドを挿入するために使用できます。統合ポイントは、レンダリングループによって定義されます。

シーングラフレンダラの動作の詳細については、Qt Quick Scene Graph Default Renderer を参照してください。

利用可能なレンダリングループは2種類あります:basic threaded basic はシングルスレッドで、 は専用スレッドでシーングラフレンダリングを行います。Qt は、プラットフォームと、場合によっては使用中のグラフィックドライバに基づいて、適切なループを選択しようとします。これが満足のいくものでない場合、またはテスト目的の場合、環境変数 を使用して、指定されたループを強制的に使用することができます。どのレンダリングループが使用されているかを確認するには、 を有効にしてください。threaded QSG_RENDER_LOOP qt.scenegraph.general logging category

スレッドレンダーループ ('threaded')

多くの設定では、シーングラフのレンダリングは専用のレンダースレッドで行われます。これは、マルチコアプロセッサの並列性を高め、ブロッキングスワップバッファ呼び出し待ちなどのストール時間を有効活用するために行われます。これにより、パフォーマンスが大幅に向上しますが、シーングラフとのインタラクションが発生する場所とタイミングに一定の制限が課されます。

以下は、スレッドレンダーループとOpenGLでフレームがどのようにレンダリングされるかの簡単な概要です。この手順は、OpenGLコンテキストの仕様を除けば、他のグラフィックスAPIでも同じです。

  1. QMLシーンに変更が発生し、QQuickItem::update() 。これは例えばアニメーションやユーザー入力の結果です。レンダースレッドにイベントがポストされ、新しいフレームが開始されます。
  2. レンダースレッドは新しいフレームを描画する準備をし、GUIスレッド上でブロックを開始します。
  3. レンダー・スレッドが新しいフレームを準備している間に、GUIスレッドはQQuickItem::updatePolish ()を呼び出し、レンダリングする前にアイテムの最終的なタッチアップを行う。
  4. GUIスレッドはブロックされる。
  5. QQuickWindow::beforeSynchronizing() シグナルが発せられる。アプリケーションはこのシグナルに(Qt::DirectConnection を使って)直接接続し、QQuickItem::updatePaintNode ()を呼び出す前に必要な準備を行うことができます。
  6. QMLの状態をシーングラフに同期させます。これは、QQuickItem::updatePaintNode ()関数を、前のフレームから変更された全てのアイテムに対して呼び出すことで行われます。QML アイテムとシーングラフのノードが相互作用するのはこの時だけです。
  7. GUIスレッドブロックが解放されます。
  8. シーングラフがレンダリングされる:
    1. QQuickWindow::beforeRendering() シグナルが発行されます。アプリケーションはこのシグナルに(Qt::DirectConnection を使って)直接接続し、QMLシーンの下に視覚的にスタックするカスタムグラフィックスAPIコールを使用することができます。
    2. QSGNode::UsePreprocess を指定したアイテムは、QSGNode::preprocess() 関数が呼び出されます。
    3. レンダラーがノードを処理します。
    4. レンダラーは状態を生成し、使用中のグラフィックス API の描画コールを記録します。
    5. QQuickWindow::afterRendering() シグナルが発行されます。アプリケーションはこのシグナルに(Qt::DirectConnection を使用して)直接接続し、QML シーン上に視覚的にスタックされるカスタムグラフィックス API 呼び出しを発行できます。
    6. フレームの準備ができました。バッファがスワップされるか(OpenGL)、現在のコマンドが記録され、コマンドバッファがグラフィックキューに投入されます(Vulkan、Metal)。QQuickWindow::frameSwapped() が出力されます。
  9. レンダリングスレッドがレンダリングしている間、GUIは自由にアニメーションを進めたり、イベントを処理したりできます。

スレッドレンダラは現在、Direct3D 11とopengl32.dll使用時のOpenGLを使用するWindows、Mesa llvmpipeを除くLinux、Metalを使用するmacOS、モバイルプラットフォーム、EGLFSを使用するEmbedded Linux、およびプラットフォームに関係なくVulkanでデフォルトで使用されます。これらはすべて将来のリリースで変更される可能性があります。環境にQSG_RENDER_LOOP=threaded を設定することで、スレッドレンダラーを強制的に使用することは常に可能です。

非スレッドレンダーループ ('basic')

システム標準のopengl32.dllを使用しない場合、OpenGLを使用するWindows、OpenGL、WebAssemblyを使用するmacOS、および一部のドライバを使用するLinuxでは、現在、非スレッドレンダーループがデフォルトで使用されています。後者については、OpenGLドライバとウィンドウ・システムのすべての組み合わせがテストされていないため、これはほとんど予防措置です。

macOSとOpenGLでは、XCode 10 (10.14 SDK)以降でビルドする場合、スレッドレンダーループはサポートされません。Xcode 9(10.13 SDK)でビルドしてレイヤーバッキングをオプトアウトすることができます。Metalではそのような制限はありません。

Webプラットフォームでは、メインスレッド以外のスレッドでWebGLを使用するためのサポートが制限されており、メインスレッドをブロックするためのサポートも制限されているため、スレッド化されたレンダリングループはWebAssemblyではサポートされていません。

スレッド化されていないレンダリングループを使用する場合でも、スレッド化されたレンダラーを使用しているかのようにコードを記述する必要があります。

以下は、非スレッドレンダラーでのフレームレンダリングシーケンスの簡略図です。

アニメーションの駆動

上記の図において、Advance Animations は何を指しているのでしょうか?

デフォルトでは、Qt Quick のアニメーション(NumberAnimation など)は、デフォルトのアニメーション・ドライバによって駆動されます。これはQObject::startTimer() のような基本的なシステムタイマーに依存しています。タイマーは通常16ミリ秒の間隔で動作します。これは完全に正確であることはなく、基盤となるプラットフォームのタイマーの精度にも依存しますが、レンダリングに依存しないという利点があります。ディスプレイのリフレッシュレートや、ディスプレイの垂直同期への同期が有効かどうかに関係なく、均一な結果が得られます。これが、basic レンダー・ループを使ったアニメーションの仕組みです。

レンダリングループの設計(シングルスレッドであれ、マルチスレッドであれ)に依存せず、画面上のスタッターを抑えて、より正確な結果を提供するために、レンダリングループは、独自のカスタムアニメーションドライバをインストールし、タイマーに頼らずに、advancing

これが、threaded レンダー・ループが実装しているものだ。1つはguiスレッド上(NumberAnimation のような通常のアニメーションを駆動するため)、もう1つはレンダースレッド上(レンダースレッドのアニメーション、つまりOpacityAnimatorXAnimator のようなAnimator タイプを駆動するため)です。これらの両方は、フレームの準備中に実行されます。つまり、アニメーションはレンダリングと同期します。これは、基礎となるグラフィックスタックによって、プレゼンテーションがディスプレイの垂直同期にスロットルされるためです。

したがって、上記のthreaded レンダリングループの図では、両方のスレッドに明示的なAdvance animations ステップがあります。レンダースレッドでは、これは些細なことです。スレッドがvsyncにスロットルされているため、16.67ミリ秒が経過したかのように各フレームでアニメーション(Animator タイプの場合)を進めると、システムタイマーに頼るよりも正確な結果が得られます。(vsyncタイミングにスロットルされた場合、60Hzのリフレッシュレートで1000/60 ミリ秒であるため、前のフレームで同じ操作が行われてから、ほぼ同じ時間が経過していると考えるのが妥当です)

guiスレッドとレンダースレッド間のデータの同期が不可欠なため、guiスレッドはレンダースレッドと同じ速度にスロットルされます。

上記の例では 60 フレーム/秒を使用しましたが、Qt Quick は他のリフレッシュレートにも対応しています。レートはQScreen とプラットフォームから取得します。例えば、144 Hzの画面では、間隔は6.94 msです。同時にこれは、vsync ベースのスロットリングが期待通りに機能していない場合、レンダリングループが考えていることが現実と一致していないと、アニメーションのペーシングが正しくなくなるため、問題を引き起こす可能性があります。

注意: Qt 6.5 から、スレッドレンダーループでは、経過時間 (QElapsedTimer) のみに基づいて、別のアニメーションドライバを選択できるようになりました。これを有効にするには、QSG_USE_SIMPLE_ANIMATION_DRIVER 環境変数をゼロ以外の値に設定します。これには、複数のウィンドウがある場合にQTimer にフォールバックするためのインフラが不要であること、vsync ベースのスロットリングが欠落しているか壊れているかを判断するヒューリスティックが不要であること、vsync スロットリングの時間的ドリフトと互換性があること、プライマリ画面のリフレッシュレートに縛られないこと、マルチ画面セットアップでよりよく動作する可能性があること、などの利点があります。また、vsyncベースのスロットリングが壊れているか無効になっていても、レンダースレッドアニメーション(Animator タイプ)を正しく駆動します。一方、このアプローチでは、アニメーションが滑らかでないと感じられるかもしれません。互換性を考慮し、現時点ではオプトイン機能として提供されています。

まとめると、threaded のレンダリングループは、以下の条件が満たされている限り、スタッターの少ないよりスムーズなアニメーションを提供することが期待されます:

  • 画面上に(QQuickWindow のように)ウィンドウが1つだけある。
  • VSyncベースのスロットリングは、underylingグラフィックスとディスプレイスタックで期待通りに動作します。

表示されているウィンドウが1つもない、または複数ある場合はどうなりますか?

レンダリング可能なウィンドウがない場合、例えば、QQuickWindow が最小化されていたり(Windows)、完全に隠されていたり(macOS)すると、フレームを表示することができず、したがって、画面のリフレッシュレートと同期して「動作」するスレッドに依存することができません。この場合、threaded のレンダー・ループは、自動的にシステム・タイマー・ベースのアプローチに切り替わってアニメーションを駆動します。つまり、basic のループが使用するメカニズムに一時的に切り替わります。

画面上に複数のQQuickWindow インスタンスがある場合も同様です。レンダースレッドとの同期によって可能になる、guiスレッド上でアニメーションを進めるための上記モデルは、複数のレンダースレッドと複数の同期ポイントが存在するようになったため、満足のいくものではなくなりました。(なぜなら、guiスレッドがブロックする時間と頻度は、ウィンドウ内のコンテンツ(それらはアニメーションしているのか、それらはどのくらいの頻度で更新されているのか)やグラフィックスタックの動作(wait-for-vsyncで提示される2つ以上のスレッドを正確にどのように処理するのか)を含む多くの要因に依存するようになったからです。)私たちは、安定したクロスプラットフォームな方法で、ウィンドウの表示レート(そもそも、どのウィンドウなのか)にスロットルされることを保証できないため、アニメーションをレンダリングに基づいて進めることはできません。

このアニメーション処理メカニズムの切り替えは、アプリケーションには透過的です。

もしvsyncベースのスロットリングが機能不全に陥っていたり、グローバルに無効になっていたり、アプリケーション自身が無効にしていたらどうでしょう?

threaded 例えば、OpenGL(GLX、EGL、WGL)の場合、スワップ間隔1を要求する、Direct 3Dの場合、間隔1でPresent()を呼び出す、またはVulkanでプレゼンテーションモードFIFO

グラフィックスドライバによっては、Qt の要求を無視して、この設定を上書きしてオフにすることができます。この例として、vsync に関するアプリケーションの設定を上書きできる、グラフィックスドライバのシステム全体のコントロールパネルがあります。また、グラフィックススタックが適切な vsync ベースのスロットリングを提供できないこともあり、これは仮想マシンによっては起こりえます(主に OpenGL や Vulkan のソフトウェアラスタライゼーションベースの実装を使用しているため)。

swap/present操作(または他のグラフィック操作)をブロックしないと、そのようなレンダリングループはアニメーションを速く進めすぎてしまいます。basic のレンダリングループでは、常にシステムタイマーに依存しているため、このような問題は発生しません。threaded では、Qt のバージョンによって動作が異なります:

  • システムが vsync ベースのスロットリングを提供できないことが分かっている場合、Qt 6.4 以前の唯一の選択肢は、アプリケーションを実行する前にQSG_RENDER_LOOP=basic を環境に手動で設定して、basic レンダーループを使用することでした。
  • Qt 6.4 からは、QSG_NO_VSYNC 環境変数を 0 以外の値に設定するか、ウィンドウのQSurfaceFormat::swapInterval() を0 に設定することで、この問題を軽減できます。vsync ベースのブロッキングを無効にするよう明示的に要求することで、その要求が実際にどのような効果を持つかに関係なく、threaded レンダリングループは、アニメーションを駆動するために vsync に依存することは無駄であることを認識し、複数のウィンドウの場合と同様に、システムタイマーを使用するようにフォールバックします。
  • さらに良いことに、Qt 6.4 から scenegraph は、いくつかの簡単なヒューリスティックを使って、フレームが「速すぎる」ことを認識し、必要であれば自動的にシステムタイマーに切り替えます。これは、ほとんどの場合、何もする必要がなく、デフォルトのレンダー・ループがthreaded 、アプリケーションが期待通りにアニメーションを実行することを意味します。これはアプリケーションには透過的ですが、トラブルシューティングや開発目的には、QSG_INFO またはqt.scenegraph.general が有効になっているときに出力される"Window 0x7ffc8489c3d0 is determined to have broken vsync throttling ..." メッセージでログに記録されることを知っておくと便利です。この方法は、最初に評価するためのデータを収集する必要があるため、QQuickWindow を開いたときに、アプリケーションが短時間だけ過度に速いアニメーションを表示する可能性があることを意味します。さらに、vsyncが壊れた可能性のあるすべての状況を捕捉できない可能性があります。

しかし、設計上、スレッド・アニメーション(Animator タイプ)のレンダリングに役立つものはないことを忘れないでください。vsync ベースのブロッキングがない場合、animators は、通常のanimations で回避策が有効になっていても、デフォルトでは正しく進まず、予想よりも速く進みます。これが問題になる場合は、QSG_USE_SIMPLE_ANIMATION_DRIVER を設定して、代替アニメーション・ドライバを使用することを検討してください。

注: GUI(メイン)スレッド上のレンダリングループロジックとイベント処理は、 vsync待機が無効になっていても、必ずしもスロットルされていないわけでは ないことに注意してください: 両方のレンダリングループは、QWindow::requestUpdate() を介してウィンドウの更新をスケジュールします。これは、イベント処理の時間を確保するために、ほとんどのプラットフォームで5ミリ秒のGUIスレッドタイマーに支えられています。macOSなど一部のプラットフォームでは、プラットフォーム固有のAPI(CVDisplayLinkなど)を使用して、新しいフレームを準備する適切な時間について通知を受けており、おそらく何らかの形でディスプレイのvsyncに関連付けられています。これはベンチマークや同様の状況に関連する可能性があります。低レベルのベンチマークを実行しようとするアプリケーションやツールでは、QT_QPA_UPDATE_IDLE_TIME 環境変数を0 に設定すると、GUI スレッドのアイドル時間を短縮できる可能性があります。通常のアプリケーションの使用では、ほとんどの場合、デフォルトで十分です。

注: レンダリングやアニメーションが期待されるペースで実行されない理由を明らかにする手がかりになるかもしれないので、疑わしい場合は、トラブルシューティングのためにqt.scenegraph.generalqt.scenegraph.time.renderloop のログ・カテゴリーを有効にしてください。

QQuickRenderControlによるレンダリングのカスタムコントロール

QQuickRenderControl を使用する場合、レンダリングループを駆動する責任はアプリケーションに移ります。この場合、組み込みのレンダリングループは使用されません。代わりに、適切なタイミングでポリッシュ、同期、レンダリングの各ステップを呼び出すのはアプリケーションに任されます。上に示したものと同様に、スレッドまたは非スレッドの動作を実装することが可能です。

さらに、アプリケーションは、QQuickRenderControl と組み合わせて独自の QAnimationDriver を実装し、インストールすることもできます。これにより、Qt Quick アニメーションの駆動を完全に制御することができます。これは、画面上に表示されないコンテンツにとって特に重要であり、フレームの提示が行われないため、プレゼンテーション・レートとは無関係です。これはオプションで、デフォルトではアニメーションはシステムタイマーに基づいて進みます。

QRhiベースとネイティブ3Dレンダリングによるシーングラフの拡張

シーングラフは、アプリケーションが提供するグラフィックコマンドを統合する3つの方法を提供します:

  • QRhi 、OpenGL、Vulkan、Metal、Direct3Dのいずれかのコマンドを、シーングラフ自身のレンダリングの前後に直接発行します。これは事実上、メインのレンダーパスに一連の描画コールを前置または追加します。追加のレンダーターゲットは使用されません。
  • テクスチャへのレンダリングと、シーングラフ内のテクスチャノードの作成。これは、追加のレンダーパスとレンダーターゲットを伴います。
  • シーングラフ内のQSGRenderNode サブクラスをインスタンス化することによって、シーングラフ自身のレンダリングとインラインで描画コールを発行する。これは、最初のアプローチに似ていますが、カスタム描画コールは、シーングラフのコマンドストリームに効果的に注入されます。

アンダーレイ・オーバーレイモード

QQuickWindow::beforeRendering() およびQQuickWindow::afterRendering() シグナルに接続することで、アプリケーションは、シーングラフがレンダリングしているのと同じコンテキストに、QRhi またはネイティブ 3D API 呼び出しを直接行うことができます。VulkanやMetalのようなAPIを使用すると、アプリケーションは、シーングラフのコマンドバッファのようなネイティブオブジェクトに、QSGRendererInterface を介して問い合わせ、適切と思われるコマンドを記録することができます。シグナル名が示すように、ユーザーはQt Quickシーンの下でも、その上でもコンテンツをレンダリングできます。この方法で統合する利点は、レンダリングを実行するために余分なレンダーターゲットが不要になり、高価なテクスチャリングのステップが不要になることです。欠点は、カスタムレンダリングは Qt Quick 自身のレンダリングの最初か最後にしか発行できないことです。QQuickWindow シグナルの代わりにQSGRenderNode を使用することで、この制限は多少緩和されますが、深度テストや深度書き込みを有効にしたレンダリングに依存すると、カスタムコンテンツと Qt Quick コンテンツの深度バッファの使用が競合する状況が簡単に発生するため、いずれの場合も 3D コンテンツと深度バッファの使用には注意が必要です。

Qt 6.6 以降、QRhi API はセミパブリック、つまりアプリケーションに提供され、限定的な互換性保証はあるものの、ドキュメント化されています。これにより、シーングラフ自体が使用するのと同じグラフィックスとシェーダの抽象化を使用することで、移植性の高い、クロスプラットフォームの2D/3Dレンダリングコードを作成することができます。

Scene Graph - RHI Under QMLの例では、QRhi を使ったアンダーレイ・オーバーレイアプローチの実装方法を紹介しています。

Scene Graph - OpenGL Under QMLの例では、OpenGL を使用して、これらのシグナルをどのように使用するかの例を示しています。

Scene Graph - Direct3D 11 Under QML の例では、Direct3Dを使用したシグナルの使用例を示しています。

Scene Graph - Metal Under QML の例では、これらのシグナルを Metal で使用する例を示しています。

Scene Graph - Vulkan Under QMLの例では、Vulkan を使ったこれらのシグナルの使い方の例を示しています。

Qt 6.0から、基礎となるグラフィックスAPIの直接使用は、QQuickWindow::beginExternalCommands ()とQQuickWindow::endExternalCommands ()の呼び出しで囲む必要があります。この概念は、QPainter::beginNativePainting() から馴染みがあるかもしれませんが、同様の目的を果たします。Qt Quick Scene Graph は、キャッシュされた状態や、現在記録されているレンダーパス内の状態に関する仮定があれば、それが無効であることを認識できます。これは、QRhi を使用する場合には適用されず、必要でもありません。

カスタムOpenGLレンダリングとシーングラフを混在させる場合、アプリケーションがOpenGLコンテキストを、バッファがバインドされた状態、属性が有効な状態、zバッファやステンシルバッファに特別な値がある状態などにしないことが重要です。そうすると、予測できない動作になる可能性があります。

カスタムレンダリングコードは、アプリケーションのGUI(メイン)スレッドで実行されることを想定してはならないという意味で、スレッドアウェアでなければなりません。QQuickWindow シグナルに接続する場合、アプリケーションはQt::DirectConnection を使用し、接続されたスロットがシーングラフ専用のレンダースレッドがある場合は、そのスレッドで起動されることを理解する必要があります。

テクスチャベースのアプローチ

テクスチャベースのアプローチは、アプリケーションがQt Quickシーン内のカスタム3Dレンダリングの「平坦化」された2Dイメージを持つ必要がある場合に、最も柔軟なアプローチです。これは、メインのレンダーパスで使用されるバッファとは独立した、専用の深度/ステンシルバッファを使用することもできます。

OpenGL を使用する場合、レガシーコンビニエンスクラスQQuickFramebufferObject を使用してこれを実現できます。QRhiOpenGL以外のカスタムレンダラーやグラフィックスAPIも、このアプローチに従うことができます(ただし、QQuickFramebufferObject は現在サポートしていません)。基礎となる API で直接テクスチャを作成してレンダリングし、その後、カスタムQQuickItem の Qt Quick シーン内でこのリソースをラッピングして使用する方法を、次の例で示します:

Scene Graph - RHI Texture Item の例。

Scene Graph - Vulkan Texture Import の例。

Scene Graph - Metal Texture Import の例。

インラインアプローチ

QSGRenderNode を使用すると、カスタム描画コールは、シーングラフのレンダリングパスの記録の最初でも最後でもなく、シーングラフのレンダリングプロセス中に注入されます。これは、QRhi または OpenGL、Vulkan、Metal、Direct 3D などのネイティブ 3D API 経由でグラフィックコマンドを発行できるように特別に存在するシーングラフノード、QSGRenderNode のインスタンスに基づくカスタムQQuickItem を作成することで達成されます。

Scene Graph - Custom QSGRenderNodeの例は、このアプローチのデモンストレーションを提供します。

QPainterを使ったカスタムアイテム

QQuickItem は、QQuickPaintedItem というサブクラスを提供しており、QPainter を使ってコンテンツをレンダリングすることができます。

警告: QQuickPaintedItem を使用すると、ソフトウェア・ラスタライズを使用するか、OpenGLフレーム・バッファ・オブジェクト(FBO)を使用して、コンテンツをレンダリングするために間接的な2Dサーフェスを使用するため、レンダリングは2段階の操作になります。最初にサーフェスをラスタライズし、次にサーフェスを描画する。シーングラフAPIを直接使用する方が、常に大幅に高速です。

ロギング・サポート

シーングラフは多くのロギング・カテゴリーをサポートしています。これらは Qt のコントリビューターに役立つだけでなく、パフォーマンスの問題やバグの追跡に役立ちます。

  • qt.scenegraph.time.texture - テクスチャのアップロードに費やされた時間を記録します。
  • qt.scenegraph.time.compilation - シェーダのコンパイルに費やされた時間を記録します。
  • qt.scenegraph.time.renderer - レンダラーの様々なステップに費やされた時間を記録します。
  • qt.scenegraph.time.renderloop - レンダリングループの様々なステップに費やされた時間を記録します。 レンダリングループでは、GUI とレンダースレッドの両方で、さまざまなフ レーム準備ステップ間の経過時間を知ることができます。そのため、例えば vsync ベースのスロットリングや () などの低レベルの Qt イネーブラがレンダリングやプレゼンテーションのパイプラインにどのような影響を与えるかを確認するための有用なトラブルシューティングツールにもなります。threaded QWindow::requestUpdate
  • qt.scenegraph.time.glyph - 距離フィールドグリフの準備に費やされた時間を記録します。
  • qt.scenegraph.general - シーングラフとグラフィックススタックのさまざまな部分に関する一般的な情報をログに記録します。
  • qt.scenegraph.renderloop - レンダリングに関わるさまざまな段階の詳細なログを作成します。このログモードは、主に Qt で作業する開発者にとって有用です。

レガシーQSG_INFO 環境変数も利用できます。これをゼロ以外の値に設定すると、qt.scenegraph.general カテゴリが有効になります。

注意: グラフィックスの問題に遭遇したときや、どのレンダリングループやグラフィックスAPIが使用されているか疑わしいときは、常に少なくともqt.scenegraph.generalqt.rhi.* を有効にしてアプリケーションを起動するか、QSG_INFO=1 を設定してください。これにより、初期化中にデバッグ出力に重要な情報が表示されます。

シーングラフバックエンド

パブリックAPIに加えて、シーングラフにはハードウェア固有の適応を行うための実装を開放する適応レイヤーがあります。これはドキュメント化されていない、内部的でプライベートなプラグインAPIで、ハードウェア適応チームがハードウェアを最大限に活用できるようにする。これには以下が含まれる:

  • カスタムテクスチャ; 特に、QQuickWindow::createTextureFromImage の実装と、ImageBorderImage タイプで使用されるテクスチャの内部表現。
  • カスタムレンダラ:アダプテーションレイヤは、シーングラフをどのようにトラバースしてレンダリングするかをプラグインに決定させるため、レンダリングアルゴリズムを特定のハードウェアに最適化したり、パフォーマンスを向上させる拡張機能を利用したりすることができます。
  • テキストやフォントのレンダリングを含む、デフォルトの QML タイプの多くをカスタムシーングラフで実装。
  • カスタムアニメーションドライバ: アニメーションシステムが低レベルディスプレイの垂直リフレッシュにフックすることで、スムーズなレンダリングが可能になります。
  • カスタムレンダーループ: QML が複数のウィンドウをどのように扱うかをよりよく制御できるようにしました。

©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。