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


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

Vulkanの概要

Vulkanではアプリケーション毎にInstanceを作り、各物理デバイスに対応したLoaderLayerを読み込み、そのLayerを通して各ドライバーを扱います。
Vulkanアーキテクチャの基本
(画像は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ラッパであるVulkanoCargo.tomlに追加

Validation layersの追加

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

Layerの列挙

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

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

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

Layerが対応しているかの確認

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

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の作成時に引数で渡すので、とりあえず何を有効にするのかという設定をしておきます。

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

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

Instanceの作成

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

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は標準出力に情報を書いていくのですが、どのレベルの情報を書くか等細かい設定をするためにはコールバックを作成する必要があります。

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

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

物理デバイスの取得

物理デバイスの列挙

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

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

GPUの取得

列挙された物理デバイスのうちグラフィック用のQueueを持っているデバイスが必要になるので、ここでは最初に見つかったグラフィック用のQueueを持っているデバイスを取得します。参考元のサイト及びその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から対応する論理デバイスを取得します。

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。