Vulkan TutorialのRust版をやってみた 〜準備編〜

Vulkanの入門をRustでやっていきます。[目次はこちら](https://qiita.com/kbone/items/1f8bc5d571dcc3ddaac3)。今回までの実装をした[GitHubはこちら](https://github.com/KBone12/vulkan-tutorial/tree/logical_device)。

# Vulkanの概要
Vulkanではアプリケーション毎に`Instance`を作り、各物理デバイスに対応した`Loader`が`Layer`を読み込み、その`Layer`を通して各ドライバーを扱います。
![Vulkanアーキテクチャの基本](https://vulkan.lunarg.com/doc/view/1.2.131.2/linux/tutorial/images/BasicAppLoader.png)
(画像は[https://vulkan.lunarg.com/doc/sdk/1.2.131.2/linux/tutorial/html/01-init_instance.html](https://vulkan.lunarg.com/doc/sdk/1.2.131.2/linux/tutorial/html/01-init_instance.html)から引用)
また描画する際は、描画命令用の`Queue`や各OS固有のウィンドウと描画先の`Surface`を作り、レンダーパスを作り、パイプラインを構築し、`Queue`にコマンドを送ることで描画できるようになります。

# Vulkanで描画するまでの準備
今回は準備編として、デバイス(GPU)を取得するまでを書きます。

## 開発環境の準備
1. Vulkanのライブラリその他をインストール
2. 適当なディレクトリで`cargo new [プロジェクト名]`をしてRustのプロジェクトを作る
3. VulkanのRustラッパである[Vulkano](https://crates.io/crates/vulkano)を`Cargo.toml`に追加

## Validation layersの追加
現状では関係ないですが、Validation layersは不正な値がGPU側に渡らないように監視すること等ができるため、この段階からデバッグ時のみ有効にしておきます。

### `Layer`の列挙
レイヤーを有効にするには`Instance`の作成時に引数にレイヤー名を渡す必要があるので、その前にレイヤーを列挙して名前を確認してみます。

```rust
vulkano::instance::layers_list().unwrap().for_each(|layer| ...);
```

列挙された名前から、今回は`VK_LAYER_LUNARG_standard_validation`と`VK_LAYER_KHRONOS_validation`を読み込んでみます。

### `Layer`が対応しているかの確認
上では先に`Layer`を列挙してから`Layer`を読み込むと書きましたが、実行環境によっては読み込みたい`Layer`に対応していない可能性があるため、対応している`Layer`だけ抽出します。

```rust
let validation_layers = [
    "VK_LAYER_LUNARG_standard_validation",
    "VK_LAYER_KHRONOS_validation",
];
let supported_validation_layers: Vec<_> = layers_list()
    .unwrap()
    .filter(|layer| validation_layers.contains(&layer.name()))
    .collect();
```

### `VK_EXT_debug_utils`の有効化
デバッグ情報等を取得することができるようになるので、`VK_EXT_debug_utils`を有効にします。Vulkanoでは、これらextensionsは`Instance`の作成時に引数で渡すので、とりあえず何を有効にするのかという設定をしておきます。

```rust
let extensions = InstanceExtensions {
    ext_debug_utils: true,
    ..InstanceExtensions::supported_by_core().unwrap()
};
```

`ext_debug_utils: true`とすることで`VK_EXT_debug_utils`が有効化されます。

## `Instance`の作成
アプリケーション毎に用意する必要のある、`vulkano::instance::Instance`を作ります。

```rust
let instance = Instance::new(
    Some(&app_info_from_cargo_toml!()),
    &extensions,
    supported_validation_layers.iter().map(|layer| layer.name()),
)
.expect("Could not build a Vulkan instance");
```

ここではそのまま書いていますが、Validation layersをデバッグ時のみ有効にするために`cfg!(debug_assertions)`等で分岐させてください。

## `DebugCallback`の設定
デフォルトでValidation layersは標準出力に情報を書いていくのですが、どのレベルの情報を書くか等細かい設定をするためにはコールバックを作成する必要があります。

```rust
DebugCallback::new(&instance, severity, ty, |message| ...).ok();
```

注意点として、`ext_debug_utils: true`としておかないと`DebugCallback::new`が`Err`を返します。

## 物理デバイスの取得


### 物理デバイスの列挙
物理デバイスは`vulkano::instance::PhysicalDevice`で表されており、`PhysicalDevice::enumerate`で列挙できます。

```rust
PhysicalDevice::enumerate(&instance).for_each(|device| ...);
```

### GPUの取得
列挙された物理デバイスのうちグラフィック用の`Queue`を持っているデバイスが必要になるので、ここでは最初に見つかったグラフィック用の`Queue`を持っているデバイスを取得します。[参考元のサイト](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Physical_devices_and_queue_families)及び[そのRust版](https://github.com/bwasty/vulkan-tutorial-rs/blob/master/src/bin/03_physical_device_selection.rs)だとちょっと面倒な感じですが、以下のように書けば同じように動作するはずです。

```rust
let device = PhysicalDevice::enumerate(&instance)
    .filter(|device| {
        device
            .queue_families()
            .any(|queue_family| queue_family.supports_graphics())
    })
    .next()
    .expect("Could not find any GPU");
```

## 論理デバイスの取得
上で取得したGPUから対応する論理デバイスを取得します。

```rust
let (_device, _queues) = PhysicalDevice::enumerate(&instance)
    .filter_map(|device| {
        device
            .queue_families()
            .filter(|queue_family| queue_family.supports_graphics())
            .map(|queue_family| (device, queue_family))
            .next()
    })
    .filter_map(|(device, queue_family)| {
        Device::new(
            device,
            &Features::none(),
            &DeviceExtensions::supported_by_device(device),
            vec![(queue_family, 1.0)],
        )
        .ok()
    })
    .next()
    .expect("Could not find any GPU");
```

ここまできてようやく準備が出来た形です。

# まとめと感想
この段階だとまだ`Queue`等よくわからないところも多いですが、最低限Vulkanが動いていることの確認は出来ました。また、Vulkanoのおかげで元のVulkanより大分記述が楽な気がします。ありがとうRust。ありがとうVulkano。