この記事は The Qt Blog の Vulkan Support in Qt 5.10 – Part 3 を翻訳したものです。
執筆: Laszlo Agocs, 2017年7月3日
過去2回の記事(パート1、パート2)では、Vulkan のインスタンスの生成まで紹介が済んでいます。今回はスクリーンに表示するところです。
QWindow か QVulkanWindow か?
このまま順調に行くと、Qt 5.10 では少なくとも5つのサンプルが提供されるでしょう。それは以下の5つで(ドキュメントはスナップショット版にリンクされています)、シンプルなもの順に羅列しています。
- hellovulkanwindow
- hellovulkantriangle
- hellovulkanwidget
- hellovulkantexture
- hellovulkancubes
hellovulkancubes のサンプルが 、NVIDIA Shield TV 上の Android 7.0 で動いているところです
これらのサンプルのソースを見てみるとある共通の側面が見られます。すべてのサンプルで QWindow の派生クラスでスワップチェーンやウィンドウ固有の処理を実装している QVulkanWindow が使われています。常にあてはまるわけではないですが、QVulkanWindow は Qt アプリケーションで Vulkan での描画ができるようになるまでの時間を大幅に短縮します。
次に、さらに進んだウィンドウの制御やスワップチェーンの完全な制御をする場合はどうでしょう?もちろん対応は可能ですが、ドキュメントもちゃんと整備されている QVulkanWindow の利用よりは敷居が高くなります。それでは実際に見てみましょう。
素の QWindow と QVulkanInstance の組み合わせ
この組み合わせはすぐに複雑になってしまうため、現時点では簡単なサンプルは提供していません。Qt 自体のソースを代わりに見ていただくのがよいでしょう。QVulkanWindow のソース の他、Vulkan を利用する QWindow を生成するような マニュアルのテストコード が存在しています。
これらによると、QWindow の派生クラスで Vulkan を利用する際の主なルールは以下のようになります
- 新しいサーフェスの種類 VulkanSurface が登場しています。すべての Vulkan を利用する QWIndow は setSurfaceType(VulkanSurface) を実行してください。
- ウィンドウは QVulkanInstance が結びついている必要があり、前回紹介した setVulkanInstance() 関数でこれを設定します。
- スワップチェーンの管理は完全にアプリケーション次第です。しかし、正しく振る舞うために QVulkanInstance で現在のオペレーションのキューが追加された(vkQueuePresentKHR)直後に presentQueued() を実行することが求められます。
- VkSurfaceKHR の取得は surfaceForWindow() 経由で行う必要があります。
- 物理デバイス内のキューファミリーがウィンドウの表示に対応しているかの問い合わせは、必要があれば supportsPresent() が利用可能です(サーフェスの場合と同様に、vkGetPhysicalDeviceWin32PresentationSupportKHR などと直接関わらな。くて済むのでこれはとても便利です)
- すべての Vulkan を利用するウィンドウの派生クラスでは QPlatformSurfaceEvent、特に QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed の処理をする必要があります。これはサーフェスの破棄の前にスワップチェーンの開放が必要なためで、QWindow ではネイティブのウィンドウが破棄された際にサーフェスが消えてしまうためです。これは意外と早い段階で起こりうることで、アプリケーションの構成にもよりますが、スワップチェーンを適切なタイミングで破棄するタイミングを逃さないためにも、SurfaceAboutToBeDestroyed の対応はほぼ必須となります。
- exposeEvent() を正しく理解することも非常に重要です。実際のセマンティックスはプラットフォームによって異なりますが、exposeEvent() の実装の正しい振る舞いは、単に isExposed() でステータスを確認して、前回と異なった場合に描画ループの開始/終了をすること「ではありません」。これは可能ではありますが、グラフィックリソースの開放も含めて、通常はこれは必要がありません。
- 同様に、実際のグラフィックスの初期化は最初の expose イベントと結びついている必要があります。その処理は QWindow の派生クラスのコンストラクタで行うべきではありません。そのタイミングでは QVulkanInstance が適切に指定されていない可能性があり、その場合には実際のネイティブのウィンドウも存在しないでしょう。
- 描画内容の更新が適切に描画される(アプリケーション次第ですが、vsync と同期しているかもしれません)ための、もっとも単純な選択肢は requestUpdate() ですべてのフレームでトリガーをかけて、event() の再実装の中で QEvent::UpdateRequest をハンドリングすることです。しかし、ほとんどのプラットフォームでこれは実際のウィンドウシステムで対応していない5ミリ秒のタイマーが必要となります。アプリケーションで描画の更新処理をもっと自由に実装することも可能です。
Core API Function ラッパー
Vulkan API へのアクセスについて見てみましょう。設定項目は QVulkanInstanceのドキュメントに詳しくかいてあります。Qt のアプリケーションのほとんどのケースでは、functions() と deviceFunctions() の返り値のラッパーオブジェクト経由で core Vulakn 1.0 API が利用できることを想定しています。エクステンションについては、スワップチェーンの管理をマニュアルでする場合の初期化処理は getInstanceProcAddr() が利用できます。
この方法ですべてのサンプルとテストは実装されていますが、必須ではありません。LIBS += -vulkan のアプローチや、その他のお世話用のライブラリの利用の選択も常に可能です。QVulkanInstance のドキュメントの中の、Using C++ Bindings for Vulkansection も合わせてご覧ください。
では、今回はここまで。パート4でお会いしましょう!