発端
Visual Studio 2019 環境で Vulkan のコードを書いている際に vkCmdSetPrimitiveTopologyEXT
を使おうとしたら次のエラーが出ました。
LNK2019 未解決の外部シンボル vkCmdSetPrimitiveTopologyEXT が関数 "public: void __cdecl vk::DispatchLoaderStatic::vkCmdSetPrimitiveTopologyEXT(struct VkCommandBuffer_T *,enum VkPrimitiveTopology)const " (?vkCmdSetPrimitiveTopologyEXT@DispatchLoaderStatic@vk@@QEBAXPEAUVkCommandBuffer_T@@W4VkPrimitiveTopology@@@Z) で参照されました
どうやら Vulkan の一部関数は static リンクだけでは使えないようです。
これの解決方法を日本語で全て解説しているサイトが見当たらなかったので書いておきます。
公式情報
https://github.com/KhronosGroup/Vulkan-Hpp の Extensions / Per Device function pointers
に記載されています。
解決方法
大きく3つの手順をこなす必要があります。
- プリプロセッサを定義
- DispatchLoaderDynamic のインスタンスを定義
- 関数を解決する処理を追加
1. プリプロセッサを定義
vulkan.hpp
をインクルードする前に VULKAN_HPP_DISPATCH_LOADER_DYNAMIC
を定義してください。
// 例
# define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
# include <vulkan/vk_sdk_platform.h>
# include <vulkan/vulkan.hpp>
上記の例のようにコード上に書いても良いですし、VisualStudio のプロジェクトファイルで定義しても大丈夫です。
この対応をすることで、関数解決がリンク時ではなく実行時になるようです。
ちなみにこの対応をした状態でビルドするとリンク時に以下のエラーが出力されます。
エラー LNK2001 外部シンボル "class vk::DispatchLoaderDynamic vk::defaultDispatchLoaderDynamic" (?defaultDispatchLoaderDynamic@vk@@3VDispatchLoaderDynamic@1@A) は未解決です
2. DispatchLoaderDynamic の実体を定義
先ほどのエラーを解決するために ::vk::defaultDispatchLoaderDynamic の実体を定義してあげる必要があります。
どこかの cpp ファイルに vulkan.hpp をインクルードした上で以下のコードを追加してください。
// #define はつけないように注意!
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
これをすることで vulkan.hpp で extern 宣言されていた ::vk::defaultDispatchLoaderDynamic の実体が定義されます。
ヘッダファイルに定義すると多重定義になるため、必ず cpp ファイルで定義してください。
でもこのコード、ちょっと何しているのかイメージしづらいですよね…?
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
の中身は以下のようになっています。
# define VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE \
namespace VULKAN_HPP_NAMESPACE \
{ \
VULKAN_HPP_STORAGE_API DispatchLoaderDynamic defaultDispatchLoaderDynamic; \
}
ちなみにこの対応をした状態でビルドするとビルドには成功します。しかし、実行時に vulkan の関数を実行しようとすると null アクセスが発生してエラーになります。
3. 関数を解決する処理を追加
vulkan の関数に有効なポインタが代入されていくように処理を書きます。
まず、 vulkan の関数を実行する前に vk::DynamicLoader
クラスのインスタンスを作成してください。これは全ての vulkan の関数の実行で必要なため、アプリケーションの終了時まで破棄しないことをオススメします。
そして、生成したローダーオブジェクトを使いつつ vk::Instance
の作成に必要な関数を使用できるようにします。
// 例
::vk::DynamicLoader loader;
VULKAN_HPP_DEFAULT_DISPATCHER.init(loader.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr"));
次に、::vk::createInstance
で vk::Instance
を作成後、同様のコードを追加します。
// 例
::vk::Instance instance;
::vk::createInstance(&info, nullptr, &instance);
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance); // この処理を追加
この処理を追加することで ::vk::Device
の作成に必要な関数を使用できるようになります。
最後に、::vk::Device
を作成後に同様のコードを追加します。
// 例
::vk::Device device;
physicalDevice.createDevice(&info, nullptr, &device);
VULKAN_HPP_DEFAULT_DISPATCHER.init(device); // この処理を追加
これで Device に関わる全ての関数が使用できるようになります。
おわり
お疲れ様でした!