このページでは

Qt Quick シーングラフ

のシーングラフQt Quick

Qt Quick 2のシーングラフは、専用のシーングラフを利用しており、OpenGL ES、OpenGL、Vulkan、Metal、Direct 3DなどのグラフィックスAPIを介してトラバースおよびレンダリングが行われます。 従来の命令型描画システム(QPainter など)ではなく、グラフィックスにシーングラフを使用することで、レンダリング対象のシーンをフレーム間で保持でき、レンダリング開始前にレンダリング対象となるプリミティブの完全なセットが判明します。これにより、状態変更を最小限に抑えるバッチレンダリングや、隠れたプリミティブの破棄など、さまざまな最適化が可能になります。

例えば、ユーザーインターフェースに10個の項目からなるリストがあり、各項目に背景色、アイコン、テキストがあるとします。従来の描画手法を使用した場合、これには30回の描画呼び出しと、同程度の状態変更が発生することになります。 一方、シーングラフを使用すれば、レンダリング対象のプリミティブを再編成し、まず1回の描画呼び出しですべての背景を描画し、次にすべてのアイコン、その後にすべてのテキストを描画するようにすることで、描画呼び出しの総数をわずか3回に削減できます。このようなバッチ処理や状態変更の削減は、一部のハードウェアにおいてパフォーマンスを大幅に向上させることができます。

シーングラフはQt Quick 2.0と密接に関連しており、単独では使用できません。シーングラフはQQuickWindow クラスによって管理・レンダリングされ、カスタムItem型はQQuickItem::updatePaintNode()を呼び出すことで、そのグラフィックプリミティブをシーングラフに追加できます。

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

注: このページに記載されている情報の多くは 、Qt Quick シーングラフの組み込みのデフォルト動作に固有のものです。software アダプテーションなど、別のシーングラフアダプテーションを使用する場合、すべての概念が当てはまるとは限りません。さまざまなシーングラフアダプテーションの詳細については、「シーングラフアダプテーション」を参照してください。

Qt Quick シーングラフの構造

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

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

ノード

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

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

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

利用可能なノードは以下の通りです:

QSGClipNode

シーングラフにおけるクリッピング機能を実装します

QSGGeometryNode

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

QSGNode

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

QSGOpacityNode

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

QSGTransformNode

シーングラフ内の変形を実装します

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

警告: ネイティブグラフィックス(OpenGL、Vulkan、Metalなど)の操作やシーングラフとのやり取りは、主にupdatePaintNode()の呼び出し中に、レンダリングスレッド上でのみ行われることが極めて重要です。 経験則として、QQuickItem::updatePaintNode()関数内では「QSG」というプレフィックスが付いたクラスのみを使用するようにしてください。

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

前処理

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

ノードの所有権

ノードの所有権は、作成者によって明示的に設定されるか、フラグ `QSGNode::OwnedByParent` を設定することでシーングラフによって設定されます。シーングラフが GUI スレッドの外で実行されている場合、クリーンアップが簡素化されるため、所有権をシーングラフに割り当てる方が望ましい場合が多いです。

マテリアル

マテリアルは、QSGGeometryNode 内のジオメトリの内部がどのように塗りつぶされるかを記述します。マテリアルは、グラフィックスパイプラインの頂点ステージおよびフラグメントステージ用のグラフィックスシェーダーをカプセル化しており、実現可能な表現に十分な柔軟性を提供します。ただし、Qt Quick のアイテムのほとんどは、単色やテクスチャによる塗りつぶしなど、ごく基本的なマテリアルのみを使用しています。

QMLアイテム型にカスタムシェーディングを適用したいだけのユーザーは、ShaderEffect 型を使用してQML内で直接これを行うことが可能です。

以下に、マテリアルクラスの完全な一覧を示します:

QSGFlatColorMaterial

シーングラフ内で単色のジオメトリをレンダリングする便利な方法

QSGMaterial

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

QSGMaterialShader

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

QSGMaterialType

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

QSGOpaqueTextureMaterial

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

QSGTextureMaterial

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

QSGVertexColorMaterial

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

便利ノード

シーングラフAPIは低レベルであり、利便性よりもパフォーマンスを重視しています。カスタムジオメトリやマテリアルをゼロから作成するには、たとえ最も基本的なものであっても、少なからぬ量のコードが必要となります。このため、APIには、最も一般的なカスタムノードをすぐに利用できるようにするための便利なクラスがいくつか用意されています。

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

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

シーングラフレンダラーの動作に関する詳細については、「Qt Quick シーングラフのデフォルトレンダラー」を参照してください。

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

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

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

以下は、スレッド化されたレンダリングループとOpenGLを使用してフレームがレンダリングされる仕組みの簡単な概要です。OpenGLコンテキスト固有の点を除けば、これらの手順は他のグラフィックスAPIでも同様です。

GUIスレッドとレンダリングスレッドの同期を示すフローチャート

  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 を使用して) 直接接続することで、カスタムグラフィックス API 呼び出しを利用でき、それらは視覚的に QML シーンの下に重ねられます。
    2. QSGNode::UsePreprocess が指定されているアイテムについては、QSGNode::preprocess()関数が呼び出されます。
    3. レンダラーはノードを処理します。
    4. レンダラーは状態を生成し、使用中のグラフィックス API に対する描画呼び出しを記録します。
    5. QQuickWindow::afterRendering() シグナルが発信されます。アプリケーションは、このシグナルに(Qt::DirectConnection を使用して)直接接続し、カスタムグラフィックス API 呼び出しを発行することで、QML シーンの上に視覚的に重ねて表示させることができます。
    6. これでフレームの準備が整いました。バッファがスワップされる(OpenGL)、あるいはプレゼンテーションコマンドが記録され、コマンドバッファがグラフィックキューに送信されます(Vulkan、Metal)。QQuickWindow::frameSwapped() が発火します。
  9. レンダリングスレッドがレンダリングを行っている間、GUIはアニメーションの進行やイベントの処理などを自由に実行できます。

スレッド化されたレンダラーは現在、Windows での Direct3D 11 および opengl32.dll を使用する場合の OpenGL、Mesa llvmpipe を除く Linux、Metal を使用する macOS、モバイルプラットフォーム、EGLFS を使用する組み込み Linux、およびプラットフォームを問わず Vulkan において、デフォルトで使用されています。 これらは将来のリリースで変更される可能性があります。環境変数QSG_RENDER_LOOP=threaded を設定することで、スレッド化されたレンダラーの使用を強制することはいつでも可能です。

非スレッド型レンダリングループ(「basic」)

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

macOS および OpenGL では、Xcode 10(10.14 SDK)以降でビルドする場合、スレッド化されたレンダリングループはサポートされません。これは、macOS 10.14 ではレイヤーバックされたビューがデフォルトで有効になるためです。 Xcode 9(10.13 SDK)でビルドすれば、レイヤーバッキングを無効にできます。その場合、スレッド化されたレンダリングループが利用可能となり、デフォルトで使用されます。Metal にはこのような制限はありません。

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

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

以下は、スレッド化されていないレンダラーにおけるフレームのレンダリング順序を簡略化した図です。

シングルスレッドのレンダリングループの処理順序を示すフローチャート

アニメーションの駆動

上記の図において、「Advance Animations 」とは何を指していますか?

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

レンダリングループの設計(シングルスレッドかマルチスレッドか)に依存せず、画面上のカクつきを抑えつつより正確な結果を得るために、レンダリングループは独自のカスタムアニメーションドライバーを実装し、タイマーに依存することなく、advancing の処理を自ら行うことを選択する場合があります。

これが、threaded のレンダリングループで実装されている仕組みです。実際、このループは1つではなく2つのアニメーションドライバをインストールします。1つはGUIスレッド上(NumberAnimation などの通常のアニメーションを駆動するため)、もう1つはレンダリングスレッド上(レンダリングスレッドのアニメーション、すなわちAnimator のタイプ、例えばOpacityAnimator やXAnimator などを駆動するため)です。 これらはいずれもフレームの準備中に進行します。つまり、アニメーションはレンダリングと同期されるようになりました。これは、基盤となるグラフィックススタックによって表示がディスプレイの垂直同期に制限されるため、理にかなった仕様です。

したがって、上記のthreaded のレンダリングループの図では、両方のスレッドに明示的なAdvance animations ステップが存在します。 レンダリングスレッドの場合、これは単純なことです。スレッドが垂直同期(vsync)に合わせてスロットリングされているため、各フレームで(Animator 型の場合)アニメーションを、あたかも16.67ミリ秒が経過したかのように進める方が、システムタイマーに依存するよりも正確な結果が得られます。 (vsyncのタイミング(60 Hzのリフレッシュレートでは1000/60 ミリ秒)に合わせてスロットリングされている場合、前のフレームで同じ操作が行われてから、およそそのくらいの時間が経過したと想定しても差し支えない)

このアプローチは、GUI(メイン)スレッド上のアニメーションにも有効です: GUIスレッドとレンダリングスレッド間のデータ同期が不可欠であるため、GUIスレッドは事実上、レンダリングスレッドと同じレートに制限されます。その一方で、処理すべき作業量が少なくなるという利点もあり、レンダリングの準備作業の多くがレンダリングスレッドにオフロードされるため、アプリケーションロジックのためにより多くの余裕を確保できます。

上記の例では毎秒60フレームを使用しましたが、Qt Quick は他のリフレッシュレートにも対応しています。レートはQScreen およびプラットフォームから取得されます。 たとえば、144 Hzの画面の場合、間隔は6.94 msになります。同時に、これは、vsyncベースのスロットリングが期待通りに機能していない場合に問題を引き起こす原因にもなり得ます。なぜなら、レンダリングループが認識している状況と現実が一致していない場合、アニメーションのペースが不自然になってしまうからです。

注: Qt 6.5以降 、スレッド化されたレンダリングループでは、経過時間のみに基づいて別のアニメーションドライバを有効にするオプションが提供されています(QElapsedTimer )。これを有効にするには、環境変数 `QSG_USE_SIMPLE_ANIMATION_DRIVER ` を 0 以外の値に設定してください。 これには、複数のウィンドウが存在する場合にQTimer にフォールバックするためのインフラストラクチャが一切不要であること、vsyncベースのスロットリングが欠落しているか破損しているかを判断しようとするヒューリスティックが不要であること、vsyncスロットリングにおけるあらゆる種類の時間的ずれに対応できること、 また、プライマリ画面のリフレッシュレートに縛られないため、マルチスクリーン環境ではより良好に動作する可能性があります。さらに、vsync ベースのスロットリングが機能していない、あるいは無効化されている場合でも、レンダリングスレッドのアニメーション(Animator タイプ)を正しく駆動します。 その一方で、このアプローチではアニメーションの滑らかさが損なわれるように感じられる可能性があります。互換性を考慮し、現時点ではオプトイン機能として提供されています。

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

  • 画面上にウィンドウが 1 つだけ(QQuickWindow の場合と同様)表示されていること。
  • VSync ベースのスロットリングが、基盤となるグラフィックスおよびディスプレイスタックにおいて期待通りに動作していること。

表示されているウィンドウがない場合や、2つ以上ある場合はどうなるのでしょうか?

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

画面上に複数のQQuickWindow インスタンスが存在する場合も同様です。レンダリングスレッドとの同期によって実現されていた、GUIスレッド上でのアニメーション進行に関する前述のモデルは、複数のレンダリングスレッド(ウィンドウごとに1つ)が存在するため、もはや満足のいくものではありません。 ここでは、システムタイマーに基づくアプローチに回帰することも必要になります。なぜなら、GUIスレッドがブロックされる時間や頻度は、ウィンドウ内のコンテンツ (アニメーションは実行されているか? 更新頻度はどれくらいか?)や、グラフィックススタックの挙動(2つ以上のスレッドが「wait-for-vsync」で描画を行う場合、具体的にどのように処理されるか?)など、多くの要因に依存するようになったため、GUIスレッドがブロックされる時間や頻度は定まらなくなっている。(そもそも、どのウィンドウのプレゼンテーションレートにスロットリングされるのか?)を、安定したクロスプラットフォームな方法で保証することはできないため、アニメーションの進行をレンダリングに依存させることはできない。 ウィンドウのプレゼンテーションレート(そもそもどのウィンドウを指すのか?)に合わせてスロットリングされることを、安定したクロスプラットフォームな方法で保証することはできないため、アニメーションの進行をレンダリングに基づいて行うことはできません。

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

vsync ベースのスロットリングが機能しない場合、グローバルに無効化されている場合、あるいはアプリケーション自体がそれを無効にした場合はどうなるのでしょうか?

threaded のレンダリングループは、スロットリングのためにグラフィックスAPIの実装および/またはウィンドウシステムに依存しています。例えば、OpenGL(GLX、EGL、WGL)の場合はスワップ間隔を1に設定するよう要求したり、Direct 3Dの場合は間隔を1に設定してPresent()を呼び出したり、Vulkanの場合はFIFO のプレゼンテーションモードを使用したりします。

一部のグラフィックスドライバでは、ユーザーがこの設定を上書きして無効にすることができ、Qtからの要求を無視します。その一例として、Vsyncに関するアプリケーションの設定を上書きできる、グラフィックスドライバのシステム全体のコントロールパネルが挙げられます。 また、グラフィックススタックが適切な vsync ベースのスロットリングを提供できない場合もあります。これは、一部の仮想マシンで発生することがあり(主に、OpenGL や Vulkan のソフトウェアラスタライズベースの実装が使用されていることが原因です)。

スワップ/プリゼント操作(またはその他のグラフィックス操作)でブロックを行わない場合、このようなレンダリングループではアニメーションの進行が速くなりすぎます。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 以降では、シーングラフもいくつかの単純なヒューリスティックを用いて、フレームの表示が「速すぎる」ことを認識しようと試み、必要と判断された場合は自動的にシステムタイマーに切り替えます。 つまり、ほとんどの場合、ユーザー側で何かを行う必要はなく、デフォルトのレンダリングループがthreaded であっても、アプリケーションは期待通りにアニメーションを実行します。これはアプリケーションからは透過的に処理されますが、トラブルシューティングや開発の目的上、QSG_INFO またはqt.scenegraph.general が有効になっている場合に、"Window 0x7ffc8489c3d0 is determined to have broken vsync throttling ..." というメッセージがログに出力されることを知っておくと便利です。 この方法の欠点は、評価に必要なデータをまず収集する必要があるため、数フレーム経過するまで有効化されないことです。つまり、QQuickWindow を開いた際、アプリケーションでは短時間ながらアニメーションが過度に高速に表示される可能性があります。さらに、vsyncが破綻している可能性のあるすべての状況を捕捉できない場合もあります。

ただし、設計上、これらの対策はいずれもレンダリングスレッドのアニメーション(Animator タイプ)には効果がないことに注意してください。VSyncに基づくブロッキングがない場合、通常のanimations に対して回避策が有効になっていても、animators はデフォルトで期待よりも速く、誤って進行してしまいます。これが問題となる場合は、QSG_USE_SIMPLE_ANIMATION_DRIVER を設定して、代替のアニメーションドライバの使用を検討してください。

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

注: 判断に迷った場合は 、トラブルシューティングのために「qt.scenegraph.general 」および「qt.scenegraph.time.renderloop 」のロギングカテゴリを有効にしてください。これらにより、レンダリングやアニメーションが期待通りの速度で実行されていない理由に関する手がかりが得られる場合があります。

QQuickRenderControl によるレンダリングの詳細な制御

QQuickRenderControl を使用する場合、レンダリングループを駆動する責任はアプリケーションに移ります。この場合、組み込みのレンダリングループは使用されません。その代わりに、適切なタイミングでpolish、synchronize、およびレンダリングの各ステップを呼び出すのはアプリケーションの役割となります。上記で示したものと同様の、スレッド化された挙動またはスレッド化されていない挙動のいずれかを実装することが可能です。

さらに、アプリケーションでは、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 を使用することで、この制限をある程度緩和できますが、いずれの場合も、3Dコンテンツや深度バッファの使用に関しては注意が必要です。深度テストに依存したり、深度書き込みを有効にしてレンダリングを行ったりすると、カスタムコンテンツとQt Quick コンテンツの深度バッファの使用が互いに競合する状況が容易に発生するからです。

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 を使用してこれらのシグナルを利用する方法が示されています。

「シーングラフ - QML での Metal」の例では、Metal を使用してこれらのシグナルを使用する方法の例を示しています。

「Scene Graph - Vulkan Under QML」の例では、Vulkan を使用してこれらのシグナルを使用する方法について解説しています。

Qt 6.0 以降、基盤となるグラフィックス API を直接使用する場合は、QQuickWindow::beginExternalCommands() およびQQuickWindow::endExternalCommands() の呼び出しで囲む必要があります。 この概念は、QPainter::beginNativePainting() でお馴染みかもしれませんが、同様の目的を果たします。つまり、アプリケーションコードが基盤となるグラフィックス API を直接操作することで状態が変更された可能性があるため、Qt Quick のシーングラフが、現在記録されているレンダリングパス内のキャッシュされた状態や状態に関する仮定(もしあれば)が、もはや無効であることを認識できるようにするものです。QRhi を使用する場合は、これは適用されず、必要もありません。

カスタム OpenGL レンダリングをシーングラフと併用する場合、アプリケーションが OpenGL コンテキストを、バッファがバインドされた状態、属性が有効な状態、Z バッファやステンシルバッファに特殊な値が残っている状態などで放置しないことが重要です。そうすると、予期しない動作を引き起こす可能性があります。

カスタムレンダリングコードは、アプリケーションのGUI(メイン)スレッド上で実行されると仮定してはならないという点で、スレッドを意識した設計でなければなりません。QQuickWindow のシグナルに接続する際、アプリケーションはQt::DirectConnection を使用し、接続されたスロットが(存在する場合)シーングラフ専用のレンダリングスレッド上で呼び出されることを理解しておく必要があります。

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

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

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

シーングラフ - RHIテクスチャアイテムの例。

Scene Graph - Vulkan テクスチャインポートの例。

シーングラフ - Metalテクスチャインポートの例。

インライン方式

QSGRenderNode を使用すると、カスタム描画コールは、シーングラフのレンダリングパスの記録の開始時や終了時ではなく、シーングラフのレンダリング処理の最中に挿入されます。 これは、QSGRenderNode のインスタンスに基づくカスタムQQuickItem を作成することで実現されます。この は、QRhi またはOpenGL、Vulkan、Metal、Direct 3Dなどのネイティブ3D APIを介してグラフィックスコマンドを発行するために特別に存在するシーングラフノードです。

「シーングラフ - カスタム 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 - レンダリングループの各ステップで費やされた時間をログに記録します。threaded レンダリングループを使用すると、GUIスレッドとレンダリングスレッドの両方における、各フレーム準備ステップ間の経過時間を把握できます。 したがって、これはトラブルシューティングのツールとしても有用であり、例えば、vsync ベースのスロットリングや、QWindow::requestUpdate() などのその他の低レベルの Qt 機能有効化が、レンダリングおよび表示パイプラインにどのような影響を与えるかを確認するのに役立ちます。
  • qt.scenegraph.time.glyph - 距離フィールドのグリフの準備にかかった時間を記録します
  • qt.scenegraph.general - シーングラフやグラフィックススタックの各部分に関する一般的な情報を記録します
  • qt.scenegraph.renderloop - レンダリングに関わる各段階の詳細なログを作成します。このログモードは、主に Qt の開発者にとって有用です。

従来のQSG_INFO 環境変数も利用可能です。これを0以外の値に設定すると、qt.scenegraph.general カテゴリが有効になります。

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

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

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

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

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.