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

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

# Vulkanで描画
前回はVulkanといいながら一切GUIが出てこない回でしたが、ついにウィンドウを描画します。といっても、ウィンドウそのものはVulkan関係ないですが。

## winitでウィンドウ表示
Rustの場合ウィンドウ表示にはwinitを使うことが多いので、ここでもwinitを使っていきます。以下では、最低限のウィンドウ表示とウィドウを閉じる処理をしています。

```rust
let event_loop = EventLoop::new();
let _window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(|event, _, control_flow| {
    *control_flow = ControlFlow::Wait;
    match event {
        Event::WindowEvent {
            event: WindowEvent::CloseRequested,
            ..
        } => {
            *control_flow = ControlFlow::Exit;
        }
        _ => {}
    };
});
```

winitは割と頻繁にAPIが変わっているのでいつも慣れない感じですが、`winit = "0.22.0"`の段階ではこう書くのが正しいようです。

## `Surface`の作成
Vulkanが描画するのは`VkSurfaceKHR`に対してであり、これは各プラットフォーム固有の部分とVulkanを繋ぐため、各ウィンドウライブラリに対応した`Surface`を用意する必要があります。今回の場合vulkano-winがそれに相当するので、それを使って`Surface`を作成します。
まず、`InstanceExtensions`を変更します。これまでは、

```rust
InstanceExtensions::supported_by_core().unwrap()
```

と書いていたところを、

```rust
vulkano_win::required_extensions()
```

とします。次に、上で

```rust
WindowBuilder::new().build(&event_loop)
```

のところを

```rust
WindowBuilder::new().build_vk_surface(&event_loop, instance)
```

に変更します。これで得られる値が`Window`から`Surface<Window>`に変わります。

## `Queue`の追加
前回では論理デバイスの作成時に`supports_graphics`を満たす`Queue`だけを要求していましたが、今回は表示用の`Queue`も必要なのでこれを加えます。

[追記] **ここで`QueueFamily`が重複しないようにしてください。そうでないと、実行時エラーで強制終了します。** [追記終了]

```rust
let (_device, mut queues) = PhysicalDevice::enumerate(&instance)
    .filter_map(|device| {
        let graphics_queue_family = device
            .queue_families()
            .find(|queue_family| queue_family.supports_graphics());
        let present_queue_family = device
            .queue_families()
            .find(|queue_family| surface.is_supported(*queue_family) == Ok(true));
        let mut queue_families_set = HashSet::new();
        let unique_queue_families: Vec<_> = vec![graphics_queue_family, present_queue_family]
            .iter()
            .filter_map(|queue_family| *queue_family)
            .filter(|queue_family| queue_families_set.insert(queue_family.id()))
            .collect();
            Some((device, unique_queue_families))
    })
    .filter_map(|(device, queue_families)| {
        Device::new(
            device,
            &Features::none(),
            &DeviceExtensions::supported_by_device(device),
            queue_families
                .iter()
                .map(|queue_family| (*queue_family, 1.0)),
        )
        .ok()
    })
    .next()
    .expect("Could not find any GPU");
```

少し長くなってしまいましたが、前回は1つだけ渡していた`QueueFamily`を複数渡せるようにしたためです。

## swap chainの作成
swap chainとは ~~いわゆるダブルバッファリングやトリプルバッファリングのことだと思います。~~ [追記]どうやら違うようです。単に表示待ちの画像のキューということらしいですが、理解不足です。[追記終了]
![swap chainの説明画像](https://vulkan.lunarg.com/doc/view/1.2.131.2/linux/tutorial/images/Swapchain.png)
(画像は[https://vulkan.lunarg.com/doc/sdk/1.2.131.2/linux/tutorial/html/05-init_swapchain.html](https://vulkan.lunarg.com/doc/sdk/1.2.131.2/linux/tutorial/html/05-init_swapchain.html)から引用)

### swap chainに対応しているかの確認
デバイスがswap chainに対応していない場合もあるので、論理デバイスの作成時に`DeviceExtensions`としてswap chainを要求します。

```rust
let extensions = DeviceExtensions {
    khr_swapchain: true,
    ..DeviceExtensions::supported_by_device(device)
};
Device::new(
    device,
    &Features::none(),
    &extensions,
    queue_families
    .iter()
    .map(|queue_family| (*queue_family, 1.0)),
)
.ok()
```

### swap chainの要求
上記で得られたデバイスはswap chainに対応しているので、次は作りたいswap chainの設定をして、要求します。なお、`Swapchain::new`で全ての設定を引数に渡していくのですが数が多く、また参考元の値をほぼそのまま写しているだけなので個々の設定については省略します。

```rust
let (_swapchain, _swapchain_image) = Swapchain::new(...).unwrap();
```

# まとめと感想
今回はウィンドウの表示とswap chainの作成を行いました。swap chainはたしかに細かく設定できそうですがその分記述量が多くて大変という、Vulkanらしさ(?)を味わいました。まだ実質何も表示できていないですが、次回からいよいよシェーダに関われそうです。