基本となるコード (Base code)
全体的な構造 (General structure)
前章では、適切な設定を全て行ったVulkanプロジェクトを作成し、サンプルコードでテストしました。この章では、次のようなコードでスクラッチから始めます:
#include <vulkan/vulkan.h>
#include <iostream>
#include <stdexcept>
#include <cstdlib>
class HelloTriangleApplication {
public:
void run() {
initVulkan();
mainLoop();
cleanup();
}
private:
void initVulkan() {
}
void mainLoop() {
}
void cleanup() {
}
};
int main() {
HelloTriangleApplication app;
try {
app.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
最初に、LunarG SDK の Vulkan ヘッダをインクルードし、関数、構造体、enumを使えるようにします。stdexcept
とiostream
ヘッダはエラーの伝搬とレポートのためにインクルードされます。cstdlib
ヘッダはEXIT_SUCCESS
とEXIT_FAILURE
マクロを提供します。
プログラム自体はクラスにラッピングされ、Vulkan オブジェクトはそのクラスのプライベートメンバ変数として格納されます。それらの変数を初期化する関数を追加し、その関数はinitVulkan
関数から呼ばれます。全ての準備ができたら、フレームのレンダリングを始めるためにメインループに入ります。この後すぐ、ウィンドウが閉じられるまで繰り返すループをmainLoop
関数に追加します。ウィンドウが閉じられてmainLoop
から抜けたら、cleanup
関数でリソースを開放するようにします。
もし実行中に何らかの致命的なエラーが発生したら、説明的なメッセージとともにstd::runtime_error
例外が投げられ、それはmain
関数まで伝搬してコマンドプロンプトに表示されます。標準的な例外を継承した様々なクラスを扱えるようにするため、より一般的なstd::exception
をキャッチします。一つの例として、要求された拡張がサポートされていなかったというものがあり、これはすぐに対処する予定です。
これ以降の章ではだいたい、initVulkan
から呼び出される関数が1つ追加され、1つ以上の新しいVulkanオブジェクトがプライベートメンバとして追加され、それをcleanup
関数で解放する必要があります。
リソース管理 (Resource management)
malloc
で確保したメモリチャンクは必ずfree
を呼び出して解放しなければいけないように、作成された全てのVulkanオブジェクトは、必要なくなれば明示的に破棄される必要があります。C++では、RAIIや<memory>
ヘッダで提供されるスマートポインタを使って、リソース管理を自動で行うことができます。しかし、このチュートリアルではVulkanオブジェクトの確保と解放を明示的に行うようにしました。何と言っても、Vulkanの特徴は間違いを避けるために全ての命令を明示することなので、オブジェクトのライフタイムを明示的に扱うのはAPIの仕組みを学ぶのに良いことです。
このチュートリアルが終わったあと、あなたはコンストラクタでVulkanオブジェクトを構築してデストラクタで解放するようなクラスを書くか、要求する所有権によってstd::unique_ptr
かstd::shared_ptr
のいずれかにカスタムデリータを渡すことで、自動的なリソース管理を実装することができます。RAIIは大規模なVulkanプログラムでは推奨されるモデルですが、学習目的では舞台裏で何が行われているかを知るのは良いことです。
VulkanオブジェクトはvkCreateXXX
のような関数で直接作成されるか、または別のオブジェクトを通じてvkAllocateXXX
のような関数で割り当てられます。オブジェクトがもうどこからも使われないのを確認したら、対となる関数であるvkDestroyXXX
かvkFreeXXX
で破棄する必要があります。これらの関数に渡すパラメータは、一般的にはオブジェクトの型によって違いますが、一つのパラメータ、pAllocator
だけは共通です。これはカスタムメモリアロケータのコールバックを指定する追加のパラメータです。私達はこのチュートリアルではこのパラメータを無視して、常にnullptr
を渡します。
GLFWの統合 (Integrating GLFW)
Vulkanは、オフスクリーンレンダリングを使いたいのであれば、ウィンドウを作成しなくても完全に動作しますが、実際に何かを表示するほうがエキサイティングでしょう!最初に#include <vulkan/vulkan.h>
の行を次のように置き換えます:
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
こうすることで、GLFWは自身の定義を含み、Vulkanヘッダを自動的に一緒にロードします。initWindow
関数を追加してrun
関数で最初に呼び出されるようにしてください。この関数を使ってGLFWの初期化とウィンドウの作成を行います。
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
void initWindow() {
}
initWindow
で最初に、GLFWライブラリを初期化するglfwInit()
を呼び出します。GLFWはもともとOpenGLコンテキストを作成するように設計されたので、次のようにしてOpenGLコンテキストを作らないことを伝える必要があります。
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
ウィンドウがリサイズされたときの対応は特別な注意が必要で、それは後で見ていきますので、今はウィンドウヒントの呼び出しで無効にしておきます。
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
あと残っているのは実際にウィンドウを作成するだけです。作成したウィンドウを参照するためにGLFWwindow* window;
をプライベートメンバに追加し、次のようにしてウィンドウを初期化します。
window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
はじめの3つの引数で、ウィンドウの横幅、高さ、タイトルを指定しています。4つめの引数でウィンドウを開くモニタを指定することもできます。最後の引数はOpenGL以外には関係ありません。
ハードコーディングされた横幅と高さの代わりに定数を使うのはいいアイデアです。なぜならこれらの値を将来的に何回か参照することになりますので。次の行をHelloTriangleApplicationクラス定義の前に追加します。
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
そしてウィンドウ作成の呼び出しを置き換えます
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
今、initWindow
関数はこのようになっているはずです:
void initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}
エラーが起こるかウィンドウが閉じられるまでアプリケーションが動き続けるようにするため、以下のようにmainLoop
関数にイベントループを追加する必要があります:
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
}
このコードは非常に自明です。ユーザーによってウィンドウが閉じられるまで、ループしながらXボタンが押されるなどのイベントをチェックします。これはまた、私達が後で呼び出す、フレームをレンダリングする関数のためのループでもあります。
ウィンドウが閉じられたら、リソースを破棄してGLFWを終了しクリーンアップする必要があります。最初のcleanup
関数は次のようになります:
void cleanup() {
glfwDestroyWindow(window);
glfwTerminate();
}
これで、プログラムを実行すると、Vulkan
というタイトルのウィンドウが表示され、ウィンドウを閉じるとプログラムが終了するはずです。これでVulkanアプリケーションのスケルトンを手に入れました。それでは最初のVulkanオブジェクトを作りましょう!