この記事はQt Quick on Vulkan, Metal, and Direct3D - Part 3の抄訳です。
Qtグラフィックスに関するシリーズのパート3では、Qt 5.14のQt Quickにおいて、QRhi (Qt Rendering Hardware Interface)を使用したレンダリングにシーングラフを切り替えるときのシェーダーの取り扱いについて説明します。 ShaderEffectアイテムまたはカスタムマテリアルを使用するQt Quickアプリケーションは、フラグメントやバーテックスシェーダーコード自体を提供する必要があるため、RHI自体を掘り下げる前にシェーダー処理をカバーすることにしました。そのため、ShaderEffect等を使用するアプリケーションでは、シェーダー処理の新しいやり方を考慮する(そしてQt6までには移行する)必要があります。
ここで説明するすべてはQt 5.14に限ってのみ適用されます。今後のリリースで変更される可能性もありますが、ここにあるものは、荒削りなまま残っている部分をなくした後、Qt 6でのグラフィックスおよびシェーダー処理の基礎を形成することになると思います。
qtdeclarativeソースツリー(QtQml、QtQuick、および関連モジュールを含むgitリポジトリ)の中にある、Qt Quickシーングラフの組み込みマテリアルのバーテックスシェーダーとフラグメントシェーダーを含むシェーダーディレクトリを見てみると、Qt Quickは現在それぞれのGLSLバーテックス・フラグメントシェーダーについて2つのバージョンを用意していることが分かります:
なぜでしょう?これは、コアプロファイルOpenGLコンテキスト(バージョン3.2以降)をサポートしているためです。このようなコンテキストではGLSL 100/110/120シェーダーのコンパイルをサポートする必要がないため、Qtには2種類のシェーダー(OpenGL ES 2.0、OpenGL 2.1、および互換性プロファイルに適したものと、もう一つの、コンテキストがコアプロファイルである場合のみ使用されるもの)を同梱する以外に選択肢がありません。このブログシリーズのパート1で説明したように、これは、アプリケーションが独自のカスタムOpenGLレンダリングとQt QuickベースのUIを組み合わせたときに、アプリケーション開発者が要求するOpenGLコンテキストを決定できるようにするためには不可欠です。コンテキストが互換性またはコアプロファイルコンテキストであっても、Qt Quickは引き続きレンダリングできます。
この問題は2種類で済んでいる場合はまだ問題ではありません。しかし、Vulkan互換のGLSL、HLSL、およびMSLがさらに必要な場合はどうでしょうか。残念ながら、このやり方では対応していくことができません。
OpenGLとは異なり、一部の新しいグラフィックスAPIには、シェーダーコンパイルのサポートが組み込まれていません。 (さよなら、glCompileShader)そして、別のライブラリとして最低限サポートしていても、実行中に反映することができないかもしれません。つまり、頂点入力、およびバーテックス、フラグメント、またはコンピュートシェーダーの入力となるその他のシェーダーリソース、また、これらのリソースのレイアウトを動的に検出する方法がありません。 (たとえば、ユニフォームブロック内のメンバーの名前とオフセットは何であるか、など)
内部の詳細:Qt Quickシーングラフのバッチ処理システムは、いわゆるマージバッチ(複数のジオメトリノードを1つのDrawコールで描画する場合に使用するもの)で使用されるマテリアルのバーテックスシェーダーをわずかに書き換えています。シェーダーをglCompileShaderに渡す前に書き換えることは、使用中のシェーディング言語が1つしかない場合に適していますが、複数の異なる言語に同じロジックを実装する必要がある場合はスケールしません。
Khronos SPIRページを見ると、そこにはSPIR-Vオープンソースエコシステムに関する参考図があります。これに基づいて構築してみるのはどうでしょうか。
私たちにとって興味深い重要なコンポーネントは以下のとおりです。
したがって、VulkanスタイルのGLSLなどの単一の言語で「標準化」し、それをSPIR-Vにコンパイルすると、Vulkanに適したものが得られます。その後、SPIRV-Crossを介してSPIR-Vバイナリを実行すると、必要なリフレクション情報を取得し、さまざまなGLSLバージョン、HLSL、およびMetal Shading Languageのソースコードを生成できます。
(つまりGLSLは依然として必須なのです、なぜなら、OpenGLにはSPIR-Vを使用する拡張がありますが、Qtのターゲットプラットフォームとデバイスの90%ではそのような拡張をサポートしない傾向にあるため、実際には機能しません。OpenGL ES 2.0が2019年になっても未だに使われているように。)
最後に、(リフレクションメタデータを含む)これをすべて一緒に簡単に(デ)シリアライズ可能なパッケージにすると、ソリューションが完成します。
したがって、QSG_RHI = 1を設定してQt Quickアプリケーションを実行するときに使用されるパイプラインは現在次のとおりです。
Vulkanスタイル GLSL
[ -> バッチ化可能なバーテックスシェーダを生成]
-> glslang : SPIR-V バイトコード
-> SPIRV-Cross : reflection metadata + GLSL/HLSL/MSL source
-> すべてまとめて .qsb ファイルにシリアライズ
.qsb拡張子は、上記の手順を実行するコマンドラインツールの名前(Qt Shader Bakerの略)に由来します。 (qbsとは別物です)
上記のリストのHLSLおよびMSLエントリは、最初は少し違和感があるように見えるかもしれません。これは、実行時にソースからHLSLとMSLをコンパイルできますが(デフォルトのアプローチ)、プリコンパイルされた中間形式も. qsbパッケージに含めることができるように、いくつかの実験が既に行われているためです。実際には、これはfxc(まだdxcのサポートはありません-計画にはありますが、D3D12をターゲットにいれはじめた後にのみ意味を持ちます)またはMetalコマンドラインツールを、上記のパイプラインをすべてまとめる前に呼び出すことを意味します。
もちろん、ここでの課題は、そのようなツールがプラットフォーム(それぞれWindowsとmacOS)に関連付けられているため、qsbはそのプラットフォームで実行しているときにのみそれらを起動できることです。そのため、たとえばLinuxで.qsbファイルを手動で生成する場合、この選択肢はありません。 Qt 6では、なんにせよビルドシステムのインテグレーションを改善する必要があり、qsbのようなツールを手動で実行することはあまり一般的ではなくなるため、長期的に見れば問題ではないでしょう。
Qt Shader Toolsモジュールからです。これは、API、QShaderBaker、およびコマンドラインホストツール、qsbの両方を提供し、上記のコンパイル、翻訳、パッケージ化の手順を実行します。
これにはちょっとした罠があって、Qt Shader Toolsは当面はqt-labsモジュールとなるため、Qt 5.14には含まれていません。
何故でしょうか? これは、主にglslangやSPIRV-Crossなど、サードパーティの依存関係のためです。すべてのターゲットプラットフォームでコンパイルして実行できるようになると、ライセンスに関連することなど、調査すべきことがたくさんあります。これらをなぜか聞いことがあるように思えるのは、この内いくつかは、API翻訳ソリューションのことをお話した際にパート1の記事内で触れているためです。
そのため、今のところ.qsbパッケージを生成するには、このモジュールをチェックアウトしてビルドし、qsbツールを手動で実行する必要があります。
Qtの一部であり、Qtに付属するソリューションが必要になりますが、オフラインシェーダー処理に依存することはそれほど悪いことではありません。それはいずれにせよQt 6の目標の1つだからです。上記のシェーダー処理ステップがアプリケーション(またはライブラリ)ビルド時に実行されるように、Qtのビルドシステムと統合するものを持つことが目標です。ただし、これは主に今後予定されているqmake-> CMake移行と関係するため、まだ手を付けていません。諸々が安定したら、新しいシステムの上にソリューションの構築を始めることができます。
qtdeclarative/src/quick/scenegraph/shaders_ngを見ると、答えは明らかです。qsbを手動で実行し(適切な名前が付けられたcompile.batに注意してください)、Qtリソースシステムを介してQt Quickライブラリに結果の.qsbファイルをインクルードします。これまでに述べたように、これは今後もう少し洗練されたものになるはずですが、今のところはこれで動いています。
.vertおよび.fragファイルにはVulkan互換のGLSLコードが含まれており、Qt Quickをビルドする際には含まれません。 .qsbファイルのみがscenegraph.qrcにリストされます。
このプロセスは、NDC TechTown 2019プレゼンテーションから引用したこのスライドでまとめられています。
各マテリアルには、バーテックスとフラグメントシェーダーのペアが1つだけあり、常にVulkan互換のGLSLとして記述され、いくつかの簡単な規則に従っています(バインディング0に配置された単一のバッファーのみを使用するなど)。
これらの各ファイルは、シェーダーベイキングマシンを介して実行され、QShaderパッケージになります。この例では、結果は同じシェーダーの6つのバージョンとリフレクションデータ(qsbはJSONテキストとして印刷できます。ただし、.qsbファイル自体は圧縮バイナリファイルであり、人間が読むことはできません)。したがって、上記の課題1と2は解決されます。
シェーダーリストの[標準]タグに注意してください。これがバーテックスシェーダーであり、-b引数も指定されていた場合、出力シェーダーの数は6ではなく12になります。追加の6つは[Batchable]とマークされ、それらがバッチ処理に適していることを示し、Qt Quickシーングラフのレンダラー向けにわずかに変更されたバリアントになっています。これにより、ストレージ要件がわずかに高くなりますが、課題3が解決されます。(ただし、実行時間の短縮により、結果的には意味のあるものになります)
これは、新しいシェーダーパイプラインの背後にある、コアとなる概念をカバーしています。 ShaderEffectとQSGMaterialを別の投稿でみていく必要があります。基本的な考え方(Qt 5.14時点で)は、シェーダーソース文字列ではなく.qsbファイル名を渡すことですが、特にマテリアルは、いくつかのことをさらに注意する必要があります(主に単一のユニフォームではなくユニフォームバッファーを使用するため、また、誰でも状態を任意に変更できるスレッドごとの現在のコンテキストの概念を持たないため)。それではこの先は、また別の機会にお話しましょう。