Qt 5.10 の Vulkan 対応 - パート2

この記事は The Qt BlogVulkan Support in Qt 5.10 – Part 2 を翻訳したものです。
執筆: Laszlo Agocs, 2017年6月16日

前回 は Qt 5.10 の Vulkan 対応の背景を紹介しました。今回は詳細を見ていきましょう。

Qt ビルド時の Vulkan オプション

Window や Linux、Android で Qt をソースコードからビルドする際に、ビルド環境で Vulkan のヘッダファイルが見つかった場合には自動で Vulkan が有効になるようになっています。Windows では VULKAN_SDK という LunarG Vulkan SDK によって自動的に設定される環境変数を参照する実装になっています。

configure の出力(config.summary というファイルにも保存されています)を確認しましょう:

Qt Gui:
...
Vulkan ................................. yes
...

If it says no, go to qtbase/config.tests/qpa/vulkan, run make, and see why it did not compile.

もし no の場合は、config.tests/qpa/vulkan に移動して、make を実行して、コンパイル失敗の原因を探ってください。

パート1でお伝えしたとおり、QtGui ライブラリも、プラットフォームプラグインも libvulkan(か、それ相当のもの)には直接リンクをしていません。Qt のアプリケーションもデフォルトでは同様になります。こういう設計によるメリットは、Vulkan 対応の Qt のバイナリと Vulkan 非対応の開発環境を同じにできる ということです。DLL が見つからないといったようなことで悩まされることはありません。実行時に Vulkan が利用できない場合は、QVulkanInstance::create() が失敗し、常に false を返すようになります。アプリケーション自体でもなにかしらの理由があれば Vulkan(ローダー)ライブラリにリンクをするかどうかを選択することができ、指定は .pro ファイルに LIB += -lvulkan と記載するだけです。

Vulkan インスタンスの取得

Vulkan では、すべてのアプリケーション単位の状態は VkInstance オブジェクトが保持しています。概要は スペック を参照してください。Qt では、Vulkan インスタンスは QVulkanInstance として存在します。背後では QPlatformVulkanInstance という仕組みで、一般的な QPA の設計パターンに沿って実装されています。Vulkan 対応を提供するプラットフォームプラグイン側で、実際の実装がなされます。前述のとおり、現時点では WindowsxcbAndroid のプラグインが該当します。

QWindow と QOpenGL* 系のパターンと同様に、QVulkanInstance は create() が呼ばれるまでは初期化を一切しません。Vulkan ライブラリ(や、ベンダー実装とつながっているローダー)のロードはそのタイミングで行われます。(例外がいくつかあり、後述します)

生成した VkInstance は vkInstance() にて取得可能です。

当然ながら、QVulkanInstance はインスタンス化のための設定オプションを指定することが可能で、特定の API のバージョンの指定や、もっと重要なものでは有効にしたい レイヤーエクステンション のリストの指定をここでします。

Qt API でサポート外のレイヤーやエクステンションを指定することも可能です(自動的にフィルタされます )が、すべてのサポートされているバージョンのレイヤーやエクステンションの名前を知りたいケースもあるかもしれません。その場合、supportedExtensions()supportedLayers() を呼んでください。どのタイミングでも(create() の前でも)動作します。当然ですが、この機能を実行すると Vulkan のインスタンスがロードされます。

基本動作に必要なサーフェス関連のエクステンション(VK_KHR_surfaceVK_KHR_win32_surface など)は Qt が自動でリストに追加するため、アプリケーション側での対応は不要になります。

一般的な main() のパターン

というわけで、Vulkan 対応のウィンドウを利用する(もしくはそのようなウィンドウを QWidget 上に持つ)Qt アプリケーションの main() 関数は以下のようになります。

int main(int argc, char **argv)
{
QGuiApplication app(argc, argv); // or QApplication when widgets are involved

const bool enableLogsAndValidation = ...

QVulkanInstance inst;

if (enableLogsAndValidation) {
QLoggingCategory::setFilterRules(QStringLiteral("qt.vulkan=true"));

#ifndef Q_OS_ANDROID
inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation");
#else // see Android-specifics at https://developer.android.com/ndk/guides/graphics/validation-layer.html
inst.setLayers(QByteArrayList()
<< "VK_LAYER_GOOGLE_threading"
<< "VK_LAYER_LUNARG_parameter_validation"
<< "VK_LAYER_LUNARG_object_tracker"
<< "VK_LAYER_LUNARG_core_validation"
<< "VK_LAYER_LUNARG_image"
<< "VK_LAYER_LUNARG_swapchain"
<< "VK_LAYER_GOOGLE_unique_objects");
#endif
}

if (!inst.create())
qFatal("Failed to create Vulkan instance: %d", inst.errorCode());

MyVulkanWindow w;
w.setVulkanInstance(&inst);
w.resize(1024, 768);
w.show();

return app.exec();
}

通常、QVulkanInstance が1つだけ存在するようになります。スタック上に存在していても問題ありませんが、QWindow や QVulkanWindow(の派生ウィンドウ)の生成より前のタイミングで QVulkanInstance が利用可能になっている必要があります。(詳細や異なるパターンのトピックはパート3でカバーします)

なにか問題がある場合には、qt.vulkan のログカテゴリを有効にしてみてください。QVulkanInstance と、使っている場合は QVulkanWindow が有益なデバッグ情報を出力するようになります。初期化時のログを主にチェックしてみてください。上記のコードでは setFilterRules() がハードコードされていて、これがいつもベストな方法というわけではありませんが、QT_LOGGING_RULES の環境変数の問題があるプラットフォームではよい選択肢ではあります。Windows と Linux では 環境変数や設定ファイルによる指定 をお勧めします。

Vulkan からの出力に関して、ほとんどはレイヤーの評価ですが、QVulkanInstance は自動的にそれらのメッセージを qDebug にリダイレクトします。デフォルトでは、VK_EXT_debug_report が有効で、リダイレクトがアクティブになります。この挙動を変更したい場合は、必要なフラグ を create() の実行前に設定してください。

外部の描画エンジンとの連携

Qt 5.x 系の間は、Qt Quick とそのしたのレイヤーの OpenGL を外部のエンジンと連携できるようにするのが基本的な戦略になります。これの元、QQuickRenderControl の導入や、QOpenGLContext の 既存のネイティブコンテキストへの対応 や、別の似たような改善 が、描画関連のレイヤーに対して行われてきています。

おなじように、QVulkanInstance でも create() の前に setVkInstance() を呼ぶことで、既存の VkInstance の適用が可能になっています。これにより、VkInstance 生成に関するのすべての機能がアプリケーションや Qt 以外のフレームワークから利用可能になり、その場合には、QVulkanInstance は新たな VkInstance を生成する代わりに、単に既存のインスタンスのラッパーとして振る舞います。

今回は以上です、パート3をお楽しみに!


Blog Topics:

Comments