Vulkan Tutorial (Drawing a triangle/Setup/Base code) 日本語訳


https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Base_code

# 基本となるコード (Base code)

## 全体的な構造 (General structure)

前章では、適切な設定を全て行ったVulkanプロジェクトを作成し、サンプルコードでテストしました。この章では、次のようなコードでスクラッチから始めます：

```cpp
#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](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)や`<memory>`ヘッダで提供されるスマートポインタを使って、リソース管理を自動で行うことができます。しかし、このチュートリアルではVulkanオブジェクトの確保と解放を明示的に行うようにしました。何と言っても、Vulkanの特徴は間違いを避けるために全ての命令を明示することなので、オブジェクトのライフタイムを明示的に扱うのはAPIの仕組みを学ぶのに良いことです。

このチュートリアルが終わったあと、あなたはコンストラクタでVulkanオブジェクトを構築してデストラクタで解放するようなクラスを書くか、要求する所有権によって`std::unique_ptr`か`std::shared_ptr`のいずれかにカスタムデリータを渡すことで、自動的なリソース管理を実装することができます。RAIIは大規模なVulkanプログラムでは推奨されるモデルですが、学習目的では舞台裏で何が行われているかを知るのは良いことです。

Vulkanオブジェクトは[`vkCreateXXX`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCreateXXX.html)のような関数で直接作成されるか、または別のオブジェクトを通じて[`vkAllocateXXX`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkAllocateXXX.html)のような関数で割り当てられます。オブジェクトがもうどこからも使われないのを確認したら、対となる関数である[`vkDestroyXXX`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkDestroyXXX.html)か[`vkFreeXXX`](https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkFreeXXX.html)で破棄する必要があります。これらの関数に渡すパラメータは、一般的にはオブジェクトの型によって違いますが、一つのパラメータ、`pAllocator`だけは共通です。これはカスタムメモリアロケータのコールバックを指定する追加のパラメータです。私達はこのチュートリアルではこのパラメータを無視して、常に`nullptr`を渡します。

## GLFWの統合 (Integrating GLFW)

Vulkanは、オフスクリーンレンダリングを使いたいのであれば、ウィンドウを作成しなくても完全に動作しますが、実際に何かを表示するほうがエキサイティングでしょう！最初に`#include <vulkan/vulkan.h>`の行を次のように置き換えます：
```cpp
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
```

こうすることで、GLFWは自身の定義を含み、Vulkanヘッダを自動的に一緒にロードします。`initWindow`関数を追加して`run`関数で最初に呼び出されるようにしてください。この関数を使ってGLFWの初期化とウィンドウの作成を行います。
```cpp
void run() {
    initWindow();
    initVulkan();
    mainLoop();
    cleanup();
}

private:
    void initWindow() {

    }
```

`initWindow`で最初に、GLFWライブラリを初期化する`glfwInit()`を呼び出します。GLFWはもともとOpenGLコンテキストを作成するように設計されたので、次のようにしてOpenGLコンテキストを作らないことを伝える必要があります。
```cpp
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
```

ウィンドウがリサイズされたときの対応は特別な注意が必要で、それは後で見ていきますので、今はウィンドウヒントの呼び出しで無効にしておきます。
```cpp
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
```

あと残っているのは実際にウィンドウを作成するだけです。作成したウィンドウを参照するために`GLFWwindow* window;`をプライベートメンバに追加し、次のようにしてウィンドウを初期化します。
```cpp
window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr);
```

はじめの3つの引数で、ウィンドウの横幅、高さ、タイトルを指定しています。4つめの引数でウィンドウを開くモニタを指定することもできます。最後の引数はOpenGL以外には関係ありません。

ハードコーディングされた横幅と高さの代わりに定数を使うのはいいアイデアです。なぜならこれらの値を将来的に何回か参照することになりますので。次の行をHelloTriangleApplicationクラス定義の前に追加します。
```cpp
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
```
そしてウィンドウ作成の呼び出しを置き換えます
```cpp
window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
```

今、`initWindow`関数はこのようになっているはずです：
```cpp
void initWindow() {
    glfwInit();

    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

    window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
}
```

エラーが起こるかウィンドウが閉じられるまでアプリケーションが動き続けるようにするため、以下のように`mainLoop`関数にイベントループを追加する必要があります：
```cpp
void mainLoop() {
    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }
}
```

このコードは非常に自明です。ユーザーによってウィンドウが閉じられるまで、ループしながらXボタンが押されるなどのイベントをチェックします。これはまた、私達が後で呼び出す、フレームをレンダリングする関数のためのループでもあります。

ウィンドウが閉じられたら、リソースを破棄してGLFWを終了しクリーンアップする必要があります。最初の`cleanup`関数は次のようになります：
```cpp
void cleanup() {
    glfwDestroyWindow(window);

    glfwTerminate();
}
```
これで、プログラムを実行すると、`Vulkan`というタイトルのウィンドウが表示され、ウィンドウを閉じるとプログラムが終了するはずです。これでVulkanアプリケーションのスケルトンを手に入れました。それでは[最初のVulkanオブジェクトを作りましょう！](https://qiita.com/shibainuudon/items/b7f348931e17449fef9a)

[C++ code](https://vulkan-tutorial.com/code/00_base_code.cpp)

[前の記事](https://qiita.com/shibainuudon/items/bd26bd8027901f8f620c)
[次の記事](https://qiita.com/shibainuudon/items/b7f348931e17449fef9a)