インスタンス (Instance)
インスタンスの作成 (Creating an instance)
あなたがまずはじめにやらなければいけないことは、インスタンス を作ることでVulkanライブラリを初期化することです。インスタンスとはアプリケーションとVulkanライブラリのコネクションで、作成するにはアプリケーションの詳細をドライバに指定する必要があります。
はじめにcreateInstance
関数を追加し、initVulkan
関数から呼び出します。
void initVulkan() {
createInstance();
}
加えて、インスタンスへのハンドルをメンバ変数に追加します。
private:
VkInstance instance;
次に、インスタンスを作成するために、アプリケーションについての情報を構造体に設定する必要があります。このデータは技術的にはオプションですが、特定のアプリケーションを最適化するためにドライバに有益な情報を提供するかもしれません(例えば、特定の特殊な振る舞いをする、有名なグラフィックスエンジンを使っているためなど)。この構造体はVkApplicationInfo
と呼ばれます。
void createInstance() {
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
}
前述したように、Vulkanの多くの構造体はsType
メンバで明示的に型を指定するように要求します。これもまた多くの構造体で、pNext
メンバで将来の拡張情報を設定できます。私たちは、ここでは値初期化を使って、これをnullptr
のままにしておきます。
Vulkanでは多くの情報は関数の引数の代わりに構造体で渡されます。そしてインスタンスを作るのに十分な情報を提供するために、もう一つの構造体を設定する必要があります。この構造体はオプションではなく、どのグローバル拡張とバリデーションレイヤを使いたいかをVulkanドライバに伝えます。ここでグローバルとは、特定のデバイスではなくプログラム全体に適用されるということを意味しています。これは次のいくつかの章ではっきりとします。
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
最初の2つのパラメータは単純です。次の2つのレイヤは要求するグローバル拡張を指定します。概要の章で述べた通り、Vulkanはプラットフォーム不可知論者のAPIなので、ウィンドウシステムのインターフェースに拡張が必要になるということを意味しています。GLFWはそれに必要な拡張を返す便利な組み込み関数を持っていて、私たちはそれをそのまま構造体にわたすことができます。
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
構造体の最後の2つのメンバで、有効にするグローバルバリデーションレイヤを決定します。私たちはこれについて次の章でより深く解説するので、今はこれを空のままにしておきます。
createInfo.enabledLayerCount = 0;
これで、Vulkanがインスタンスを作るために必要なものは全て設定したので、ようやくvkCreateInstance
を呼び出すことができます。
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
ご覧のとおり、Vulkanでオブジェクトを作成する関数の引数の一般的なパターンは:
- 作成情報の構造体へのポインタ
- カスタムアロケータのコールバックへのポインタ。このチュートリアルでは常に
nullptr
。 - 新しいオブジェクトへのハンドルを格納する変数へのポインタ
全てが上手く行けばVkInstance
型のメンバ変数にインスタンスへのハンドルが格納されます。ほとんど全てのVulkan関数はVkResult
型の値を返し、それはVK_SUCCESS
またはエラーコードのいずれかです。インスタンスの作成に成功したかどうかチェックするのに、結果を変数に格納する必要はなく、たんに成功値かどうかチェックするだけです。
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
ここで、インスタンスの作成に成功しているかどうか確認するためにプログラムを実行してください。
拡張サポートのチェック (Checking for extension support)
vkCreateInstance
のドキュメントを見ると、可能性のあるエラーコードの一つにVK_ERROR_EXTENSION_NOT_PRESENT
があります。私たちは単純に要求する拡張を指定して、このエラーコードが帰ってきたら停止することもできます。これはウィンドウシステムのインターフェースのような基本的な拡張機能なら理にかなっていますが、オプションの機能についてチェックしたいとしたら、どうしたらいいのでしょうか?
インスタンスを作成する前にサポートされている拡張機能のリストを取得するために、vkEnumerateInstanceExtensionProperties
関数があります。この関数は、拡張機能の数を格納する変数へのポインタと、拡張機能の詳細を格納するVkExtensionProperties
の配列を受け取ります。また、最初にオプションの引数を取り、特定のバリデーションレイヤによって拡張機能をフィルタリングできますが、今は無視します。
拡張機能の詳細を格納するための配列を確保するため、最初にいくつそれがあるかを知る必要があります。最後の引数を空にしておくことで、拡張機能の数を取得することができます。
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
次に、拡張機能の詳細を格納する配列を確保します。(include <vector>
)
std::vector<VkExtensionProperties> extensions(extensionCount);
最後に、拡張機能の詳細を問い合わせます。
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
それぞれのVkExtensionProperties
構造体は、拡張機能の名前とバージョン番号を含みます。シンプルなforループでこれらをリストすることができます。(\t
はインデントのためのタブです)
std::cout << "available extensions:\n";
for (const auto& extension : extensions) {
std::cout << '\t' << extension.extensionName << '\n';
}
あなたがもしVulkanサポートについていくつかの詳細を提供したいなら、このコードをcreateInstance
関数に追加することができます。課題として、glfwGetRequiredInstanceExtensions
関数から返された全ての拡張機能が、サポートされている拡張機能のリストに含まれているかどうか、チェックする関数を作成してみてください。
クリーンアップ (Cleaning up)
VkInstance
はプログラムが終了する直前に破棄されなければいけません。cleanup
の中でvkDestroyInstance
関数を呼び出すことで破棄することができます。
void cleanup() {
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
vkDestroyInstance
関数の引数は簡単です。前の章で述べた通り、Vulkanの確保と開放関数はオプションのアロケータのコールバックを持ち、私たちはnullptr
を渡してこれを無視します。この後に続く章で作成する他の全てのVulkanリソースは、インスタンスが破棄される前にクリーンアップされる必要があります。
インスタンスの作成の後もっと複雑なステップに進む前に、バリデーションレイヤをチェックすることでデバッグオプションを評価するときが来ました。