Qt6.6でDirect3D 12がサポートされました

この記事はDirect3D 12 Support in Qt 6.6の抄訳です。


Qt 6.6では、Direct3D 12用の新しいQRhiバックエンドが導入されました。これにより、サポートされる3D APIは Vulkan、Metal、OpenGL / OpenGL ES、Direct3D 11、Direct3D 12 の 5つになりました。Qt Quick および Qt Quick 3D を使用するアプリケーションは、QQuickWindow または QQuickView のコンテンツのレンダリングに D3D12 を選択できるようになりました。さらに、このブログ記事で紹介したアーキテクチャの改善により、QQuickWidget も完全にサポートされました。

これよりDirect3D 12サポートの概要をご紹介します。

Direct3D 12 サポートによる影響

多くの場合、ユーザはDirect3D 12サポートを意識することはないでしょう。Qt Quick アプリケーションのデフォルトのレンダリングバックエンドは、Windows 上では Direct3D 11 であり続け、これは当面変更される予定はないからです。Qtで開発したアプリケーションをWindows 上で実行するときにVulkan または OpenGL を使用することを選択できる様に、Direct3D 12 を使用するには明示的に選択する必要があります。

D3D12バックエンドは十分テストされていますが、何らかの問題が発生する可能性は常にあります。さらに、既存のアプリケーションとの互換性や、予期せぬ動作(ユーザを驚かせるような変化)を防止することは、Qt グラフィックス・スタックにとって重要な目標です。既存のデフォルトを継続することで、幅広いシステム(仮想マシンやドライバの状況があまり良くない古いシステムを含む)にアプリケーションをデプロイして出荷する際にも、予期せぬリスクを最小限に抑えることができます。

Qt Quick アプリケーションでDirect3D 12バックエンドを有効にするには

QQuickWindow (または Window QML 要素)、QQuickView、QQuickWidget を操作するときは、QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D12) を呼び出します。これは通常、main()の早い段階で実行されます。この設定はアプリケーション・グローバルで、すべてのQt QuickWindowに適用されます。

あるいは、アプリケーションを起動する前にQSG_RHI_BACKEND環境変数にd3d12を設定してください。

トラブルシューティングや、要求が正しく受け入れられるかどうかを確認する場合、QSG_INFO=1 を設定する(または qt.scenegraph.general/qt.rhi.general logging カテゴリを有効にする)ことで、一般的な scenegraph ログを有効にすると便利です。

D3Dデバッグレイヤーは、QSG_RHI_DEBUG_LAYER=1で起動するか、QQuickGraphicsConfigurationで適切なフラグを設定し、QQuickWindow/Viewで設定することで有効になります。(つまり、Vulkan を使用しているときに Vulkan 検証レイヤーを有効にするように要求するのとまったく同じ方法です)。これには、実行時にデバッグレイヤーが利用可能であることが必要です。疑わしい場合は、ログで "Enabling D3D12 debug layer "という行を探してください。これはおそらく一般的な Qt Quick アプリケーションではあまり使用されるものではありませんが(標準の Qt レンダリングでデバッグレイヤーのエラーが発生することは想定していないため)、外部の D3D12 ベースのグラフィックス、計算、機械学習のコードを Qt Quick アプリケーションに統合する場合に役立つ可能性があります。

外部レンダリングエンジンといえば、QQuickRenderTargetは適切なfromD3D12Texture()関数を取得し、QQuickRenderControlを介してQQuickWindowを駆動する際に、既存のD3D12テクスチャをラップしてQt Quickレンダリングをリダイレクトすることができます。 QQuickGraphicsDeviceで、QQuickWindowが既存のネイティブ物理デバイス(アダプタ)、デバイス、コンテキストなどのオブジェクトを採用するために使用されるfromAdapter()は、今後D3D11とD3D12の両方に対応します。

QRhiを直接使用するアプリケーションで使用するには?

Qt 6.6ではQRhiファミリーのAPIが大幅にオープン化され、これらのクラスのドキュメントがQtの標準ドキュメントの一部となりました。 また、APIはQPAクラスと同様に「準パブリック」レベルに引き上げられました。Qt Quick や Qt Quick 3D を実装するために Qt 自身が使用しているのと同じクロスプラットフォームのレンダリング機能を使用したいアプリケーションは、生成されたドキュメントのない完全なプライベート API を扱うことなくレンダリングできるようになりました。

QRhiを直接操作する場合、QRhi::create()QRhi::D3D12を渡し、QRhiD3D12InitParamsを指定することができます。これは現在のところ、デバッグ・レイヤーを有効にするかどうかを制御するためにのみ使用されます。特定のアダプタを使用したり、既存のデバイスとコマンドキューコンボを採用したりするには、QRhiD3D12NativeHandlesを使用します。PreferSoftwareRendererは、D3D11と同様に、このバックエンドでも尊重されます; これは、DXGI_ADAPTER_FLAG_SOFTWAREを持つアダプタの選択を要求します。 (例:WARP)

QRhiインスタンスからネイティブオブジェクトを取り出すとき、QRhi::nativeHandles()は、使用されたアダプタ、ID3D12Device、ID3D12CommandQueueを報告するQRhiD3D12NativeHandlesを提供します。当然のことながら、QRhiTextureからネイティブテクスチャをクエリすると、ID3D12ResourceD3D12_RESOURCE_STATESが返されます。

内部的な側面

内部的には、D3D12バックエンドはVulkanバックエンドと多くの点で似ています。ある面ではよりシンプルですが、ある面では多くの厄介な問題、特にシェーダーリソースバインディングの分野とシェーダーの可視ディスクリプタヒープを管理することになる場合に対処しなければなりません。

Vulkan互換のGLSLソースコードはSPIR-Vにコンパイルされ、SPIRV-Crossを介してHLSLに翻訳されます。ここでのキャッチフレーズは、特定の構成要素がSPIR-VとHLSLの間でそのままでは完全にマッピングされないため、翻訳プロセスで追加のメタデータ、たとえば、SPIR-Vの結合画像サンプラーバインディングXをHLSLのシェーダーレジスタtYとsZにマッピングするといったマッピングテーブルが生成されることです。このメタデータはQRhiバックエンドによって消費されます。これは、ルートシグネチャを構築し、記述子テーブルをレイアウトする際に、いくつかの興味深い課題を提示します。例えば、前述のマッピングテーブルはシェーダ単位であり、完全に独立しています(つまり、バインディングレジスタマッピングは、頂点シェーダとフラグメントシェーダで異なる可能性があります。(これはD3D11にも当てはまりますが、D3D11のAPIはより高水準であるため、これらの問題のいくつかを都合よく隠しています)

テクスチャ、バッファ、コマンドバッファは Vulkan と同様に動作します。テクスチャのアップロードはステージングバッファを通りますが、D3D12バックエンドはフレーム(スロット)ごとに小さなステージング領域(現在は16 MB)を確保し、それが適合する限り、すべての非ダイナミックバッファとテクスチャのアップロードにそれを使用することで新しいことを試みています。動的な(ホストが見える、つまりアップロードヒープ)QRhiBuffersは複数のID3D12Resourceによってバックアップされ、通常のフレームスロットモデルが、潜在的に飛行中の複数のフレームを持つことを可能にするために使用されます。(つまり、他のお行儀の良いバックエンドのように、D3D12のものも、フレームのコマンドを送信するときに、単純にブロックして完了を待つことはありません)。コマンドバッファも同様で、連続したQRhi::beginFrame()コールでFRAMES_IN_FLIGHT ID3D12GraphicsCommandListsを循環します。現時点ではFRAMES_IN_FLIGHTは2に設定されています。

コンピュートシェーダのサポート、ストレージイメージとバッファは他のバックエンドと同様に実装されています。万が一、GLSL シェーダで読み込み専用の SSBO を使用する場合(例えば、 layout (binding = 0, std430) readonly buffer someBuffer { ... ...})、Qt 6.6.0ではこれがHLSLのByteAddressBufferに変換される(SRVを使う)バグがあるのに対して、QRhiバックエンドではRWByteAddressBuffer(とUAV)しか扱えないことに注意する必要があります。

他のバックエンドと同様に、バックエンド固有の動作を制御したり、アプリケーションを変更せずにいくつかの設定をオーバーライドするために使用できる環境変数が多数あります:

  • QT_D3D_ADAPTER_INDEX - D3D11 と同様に、これはアダプタが使用する環境変数ベースの手動オーバーライドです。インデックスを取得するには、QSG_INFO=1(または qt.rhi.general)から出力されるログをチェックしてください。
  • QT_D3D_STABLE_POWER_STATE - SetStablePowerState() を呼び出します。これは、QRhiCommandBuffer::lastCompletedGpuTime()を使ってタイムスタンプを扱うときに関係します。 GPU タイミングは、実は Qt 6.6 のもう一つの新機能であり、将来のブログ記事のテーマです。
  • QT_RHI_LEAK_CHECK - Release (または RelWithDebInfo) ビルドでは、Debug が自動的に有効になっているため、関連性があります。ゼロ以外の値に設定すると、QRhiインスタンスの破棄時にまだ解放されていないリソース(バッファ、テクスチャなど)がある場合、QRhiは有益なリマインダーを表示します。D3D12バックエンドでは、未リリースのディスクリプタ(RTV、DSV、SRVなど)もチェックするため、この機能はさらに拡張されています。
  • QT_D3D_DEBUG_BREAK - デバッグレイヤーからの破損、エラー、警告の重大度メッセージのブレークを有効にします。
  • QT_D3D_NO_SUBALLOC - D3D12MemoryAllocator の使用を無効にします。そうすることは、すべてのバッファとテクスチャがそれ自身のCreateCommittedResource()を行うことを意味します。

Direct3D12バックエンドの利点

D3D12バックエンドには主に2つの利点があります:

QQuickWindow も D3D12 を使用できるため、Qt ベースでない D3D12 コードと統合する必要があるアプリケーションでも、より自然な方法で統合できるようになりました。D3D11On12のようなトランスレーションレイヤーを経由する必要はもうありません。この例としては、Qt 自身の 2D/3D レンダラー(レイトレーシング、高度な計算、ワークグラフなど)では現在使用されていない、または適用されない機能を使用するレンダリング/計算エンジンや、DirectML を使用する機械学習コードなどがあります。

もう 1 つは、長期的には Qt 自身にメリットがあり、D3D11 では利用できなくなった新しい機能や今後の機能を使用できるようにすることです。この良い例が、Qt の(現在開発中の)6.7 ブランチにあります。このブランチには、マルチビューレンダリングのための最低レベルのイネーブラがすでに統合されています。(GL_OVR_multiviewと他の3D APIにある同等のものを考えてみてください。マルチビューのサポートは、将来的にQt Quick 3Dで効率的なVR/ARサポートを行うために重要になります) D3D11のバックエンドでは、マルチビューはベンダー固有の拡張としてしか利用できないため、これを有効にする計画はありませんでしたが(現在もあります)、D3D12のバックエンドを持つことは、ビューインスタンシングをそのまま使えるという素晴らしいニュースでした。

(さらに、D3D12上でQtアプリケーションを実行できるようになったことで、以前は不可能だったPIXなどのツールが使えるようになりました。)

グラフィック・シェーダーやコンピュート・シェーダーに対する影響

今のところ、Shader Model 5.0をターゲットにすれば十分なので、ほとんど何もありません。Qt QuickやQt Quick 3Dでは、この点に関する変更はありません:すべての組み込みマテリアルシェーダは、qsbを実行するときに--hlsl 50で条件付けされます(直接またはCMake経由で)。QRhiのバックエンドをD3D12に切り替えても、シェーダのコンディショニング設定を変更することなく動作します。

将来使用されるかもしれないいくつかの新機能を考えると、Shader Model 5.0では十分ではないかもしれません。例えば、前述の Qt 6.7 でのマルチビューでは、SV_ViewID (gl_ViewIndex の変換先) がそのバージョンからしか使えないため、マルチビューに関わるシェーダはShader Model 6.1 を使う必要があります。そして、古いDXBCベースのツール(fxcD3DCompile())ではもはや十分ではなく、DXILベースのツールチェーンに切り替える必要があります。このため、Qt 6.7 では、要求されたShader Modelが 6.0 以上であれば、最新のシェーダーコンパイラである dxc を透過的に実行し、実行時に dxcapi.h インターフェイスを使用するようになりました。5.0では何も変わらず、古いツールを使い続けます。


Blog Topics:

Comments