Vulkan 排他的フルスクリーンのサンプル
はじめに
本記事はVulkanの排他的フルスクリーンについて記載したものです。この機能はVK_EXT_full_screen_exclusive拡張機能として定義されており、2019年に提案されたものです。情報として新しくはないのですが、手元で試してみたのと、トライした情報がネット上であまり見かけることがなかったので、記事化することにしました。
排他的フルスクリーンとは
Windows環境ではDesktop Window Manager (DWM)というコンポジッターが各ウィンドウの描画結果を合成してディスプレイに表示をします。このような仕組みになっているため、その処理時間分が表示までの遅れとなってしまいます。
タイミングにシビアなアプリケーションからは嫌われる傾向にあり、昔のWindows XPの頃までDirectXのフルスクリーンアプリがやっていたような排他的なフルスクリーンモードを使いたいという意見を見かけることがあります。
VulkanのVK_EXT_full_screen_exclusive拡張機能はこれを実現するものです。
動作環境
- Windows11
- NVIDIA GeForce 3060
- ドライババージョン: 522.06
- Vulkan SDK 1.3.250
リポジトリ
本記事で使用したサンプルコードは以下のリポジトリで公開しています。
GLFWとVolkとVulkan
Vulkanの拡張機能を使う際に、関数ポインタを取り出す作業が手間のため、Volkライブラリを使用します。またウィンドウの処理などの手間を省くためGLFWライブラリも使います。
この2つを組み合わせて使用する例が少ないと思いますので、ここで手順を紹介します。
インクルードブロック
インクルードする順番や事前定義のdefineが大切です。
ここでは次のようにして、GLFWおよびVolkのインクルードを行います。
// volk をインクルード.
#define VK_NO_PROTOTYPES
#define VOLK_IMPLEMENTATION
#define VK_USE_PLATFORM_WIN32_KHR
#include "Volk/volk.h"
#define GLFW_EXPOSE_NATIVE_WIN32
#include "GLFW/glfw3.h"
#include "GLFW/glfw3native.h"
VK_NO_PROTOTYPES
の定義してからでないと、定義が衝突します。
また、GLFWのものより先にVolkをインクルードする必要があります。
インスタンス初期化
vkCreateInstance(&instanceCreateInfo, nullptr, &m_vkInstance);
volkLoadInstance(m_vkInstance);
デバイス初期化
vkCreateDevice(m_gpu, &deviceCreateInfo, nullptr, &m_vkDevice);
volkLoadDevice(m_vkDevice);
排他的フルスクリーンの実装について
VK_EXT_full_screen_exclusiveを使うための手順や実装について説明します。
初期化時の項目が少し多めとなっている点に注意です。
初期化の設定
インスタンスの初期化時
VK_EXT_full_screen_exclusiveが依存するインスタンス拡張機能を有効化します。これには以下の定義で示される拡張名を初期化時に使用するVkInstanceCreateInfo
構造体のppEnabledExtensionNames
のリストに含まれるようにします。
- VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME
- VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME
デバイスの初期化時
VK_EXT_full_screen_exclusiveデバイス拡張機能を有効化します。
以下の定義で示される拡張機能名をVkDeviceCreateInfo
構造体のppEnabledExtensionNames
のリストに含まれるようにします。
- VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME
スワップチェインの生成処理
スワップチェインの生成で使用するVkSwapchainCreateInfoKHR
構造体のpNextメンバから、VkSurfaceFullScreenExclusiveInfoEXT
構造体とVkSurfaceFullScreenExclusiveWin32InfoEXT
構造体のデータをポインタで接続します。
この実装を擬似コードで書いたものが以下の通りです。
VkSwapchainCreateInfoKHR swapchainCreateInfo;
VkSurfaceFullScreenExclusiveInfoEXT fullscreenExclusiveInfo;
VkSurfaceFullScreenExclusiveWin32InfoEXT fullscreenExclusiveWin32Info;
fullscreenExclusiveInfo.pNext = &fullscreenExclusiveWin32Info;
swapchainCreateInfo.pNext = &fullscreenExclusiveInfo;
VkSurfaceFullScreenExclusiveWin32InfoEXT
構造体にはディスプレイを示すモニターハンドルを設定します。
fullscreenExclusiveWin32Info.hmonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
VkSurfaceFullScreenExclusiveInfoEXT
構造体には、フルスクリーンのモードを示すパラメータを設定します。
本サンプルにおいてはこれまでの動作と同じ挙動を指定する VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT
を設定して、初期化をしました。
必要な要素を設定した後であれば、以下のパラメータを指定して、起動直後からフルスクリーン有効とすることもできると考えられます。
- ドライバーに処理を任せる
VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT
- 明示的にフルスクリーン化をする
VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT
これらの設定を変更する際には、一度スワップチェインを再生成する処理が必要です。
ボーダーレスフルスクリーンウィンドウ
Vulkanを用いたWindowsアプリケーションでフルスクリーンを実現する際には、ウィンドウを全画面サイズにして、ウィンドウスタイルから枠をなくす、いわゆるボーダーレスウィンドウとするのが一般的でした。これはVulkanに限らず、OpenGLを使用したアプリケーションでも同様です。
Khronosのサンプルコードによれば、ボーダーレスウィンドウのウィンドウスタイルは、以下のものを使うのが想定されているようです。
// 現在のウィンドウの設定から所定のフラグを落とす
m_windowStyle = GetWindowLongA(hwnd, GWL_STYLE);
dwStyle = m_windowStyle & (~WS_BORDER) & (~WS_DLGFRAME) & (~WS_THICKFRAME);
dwStyle |= WS_POPUP;
そのように作ったフラグで、ウィンドウを調整します。
// 新しい設定をセットして、最大化する.
SetWindowLongA(hwnd, GWL_STYLE, newStyle);
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
排他的フルスクリーンの有効化
排他的フルスクリーン VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT
を設定したときの有効化について説明します。
この設定でスワップチェインを作り直した後、さらにvkAcquireFullScreenExclusiveModeEXT
関数で有効化します。この設定から抜けるときには、vkReleaseFullScreenExclusiveModeEXT
関数で解除します。
ウィンドウスタイルの設定が正しい、ウィンドウの解像度が正しい、モニターハンドルが正しいなど、条件を満たしていればvkAcquireFullScreenExclusiveModeEXT
関数は成功を返します。失敗すると、VK_ERROR_INITIALIZATION_FAILED
の戻り値が返ります。
失敗時の要因については検証レイヤーを有効化していても現時点では情報が出力されていなかったので原因調査には手間取りそうです。
実行について
今回作成したサンプルは、キー押下によってモードを変えるものです。
- F1: ウィンドウモード
- F2: ボーダーレスウィンドウによるフルスクリーン (ドライバー任せ)
- F3: ボーダーレスウィンドウによるフルスクリーン (アプリ制御)
F3キーによってフルスクリーン化しているときにスタートメニュー出したり他のウィンドウに切り替えたりすると、排他的フルスクリーンの状態が解除されプレゼンテーションに失敗します。
この状態の時vkQueuePresentKHR
関数の戻り値で、VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
が返ります。また、vkAcquireNextImageKHR
関数の戻り値でVK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
が返ってくるとドキュメントに記載はありましたが、手元の環境ではvkQueuePresentKHR
が失敗する状態でもVK_SUCCESS
が返ってきて検出はできませんでした。
vkQueuePresentKHR
とvkQueuePresentKHR
の2カ所でチェックが安全なのかもしれません。
ロストした状態から適切にスワップチェインを生成し直すなどすれば、実行を復帰させることは可能です。本サンプルではフルスクリーンの状態が解除されたことを知りたいために、実行停止としています。
測定する環境がないため、排他的フルスクリーンによってディスプレイ表示までのレイテンシーが短くなったかどうかは不明です。
Discussion