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


Vulkanの入門をRustでやっていきます。目次はこちら。今回までの実装をしたGitHubはこちら

Vulkanで描画

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

winitでウィンドウ表示

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

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を変更します。これまでは、

InstanceExtensions::supported_by_core().unwrap()

と書いていたところを、

vulkano_win::required_extensions()

とします。次に、上で

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

のところを

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

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

Queueの追加

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

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

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/sdk/1.2.131.2/linux/tutorial/html/05-init_swapchain.htmlから引用)

swap chainに対応しているかの確認

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

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で全ての設定を引数に渡していくのですが数が多く、また参考元の値をほぼそのまま写しているだけなので個々の設定については省略します。

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

まとめと感想

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