Vulkanの入門をRustでやっていきます。目次はこちら。今回までの実装をしたGitHubはこちら。
Vulkanの概要
Vulkanではアプリケーション毎にInstanceを作り、各物理デバイスに対応したLoaderがLayerを読み込み、そのLayerを通して各ドライバーを扱います。

(画像はhttps://vulkan.lunarg.com/doc/sdk/1.2.131.2/linux/tutorial/html/01-init_instance.htmlから引用)
また描画する際は、描画命令用のQueueや各OS固有のウィンドウと描画先のSurfaceを作り、レンダーパスを作り、パイプラインを構築し、Queueにコマンドを送ることで描画できるようになります。
Vulkanで描画するまでの準備
今回は準備編として、デバイス(GPU)を取得するまでを書きます。
開発環境の準備
- Vulkanのライブラリその他をインストール
- 適当なディレクトリで
cargo new [プロジェクト名]をしてRustのプロジェクトを作る - VulkanのRustラッパであるVulkanoを
Cargo.tomlに追加
Validation layersの追加
現状では関係ないですが、Validation layersは不正な値がGPU側に渡らないように監視すること等ができるため、この段階からデバッグ時のみ有効にしておきます。
Layerの列挙
レイヤーを有効にするにはInstanceの作成時に引数にレイヤー名を渡す必要があるので、その前にレイヤーを列挙して名前を確認してみます。
vulkano::instance::layers_list().unwrap().for_each(|layer| ...);
列挙された名前から、今回はVK_LAYER_LUNARG_standard_validationとVK_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::newがErrを返します。
物理デバイスの取得
物理デバイスの列挙
物理デバイスは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。