Rust で Vulkan を使う

はじめに

思い立って Vulkan の勉強をすることにしたのですが、 Rust 力がまだまだなので「Rust で Vulkan のコード書きながら勉強すれば一石二鳥では・・・?」と考え実行してみた件についてのまとめ記事です。

上記リポジトリで使用した crate の紹介などをしていきます。

Vulkan の Rust Binding を導入する

Rust は「~を Rust から使ってみたい」と思って検索すると大体なんとかなるケースが多く、 C/C++ の主要な SDK 、ライブラリには割と高い確率で Binding ライブラリが creates.io に存在しています。

今回勉強にあたって使ってみた Vulkan の Binding は下記のものです。

  • Vulkano
  • Ash

他にもいくつか存在しているようですが、この二つが最も使われているもののようです (crates.io 調べ) 。

API のレベル (高 > 低) は個人的な感覚として

Vulkano > vulkan.hpp (C++) > Ash > vulakn.h (C)

の順かなという感じがしました。

Vulkano

Vulkano は比較的「高レベル API」のようです。ラッパーも Arc で管理されていて、インスタンスの破棄に関しては基本的に配慮しなくてもよいようになっています。初期化もある程度隠蔽化されていて比較的手続きが少なく書けそうです。 unsafe も原則的には出てきません。今回は試していませんが、 Rust のコードの中に記述したシェーダーのコンパイルをするマクロの定義もあるようです。

Vulkan の知識があって、 Rust で Vulkan のコードを書くのであれば面倒な部分をある程度簡略化できて便利そうですが、 Vulkan の全てをコントロールしたい、といった場合は向かないかもしれません。また、公式の C/C++ API との差異により、 Vulkan の知識がない状態から参考書を参考に勉強を始めると差異の吸収に手間取るかもしれません。

Ash

Ash は "A very lightweight wrapper" を謳っているくらいなので非常に薄いラッパー API を提供しています。

No validation, everything is unsafe

と書かれており、ほとんどの API は unsafe ブロックの中で使用する必要があります。 drop trait の実装もされていないのでインスタンス管理も自分で行う必要があります。

基本的に Vulkan の API をそのまま Rust で見える形にしたものに近く、 C/C++ のコードをそのまま Rust で書き直すのもそれほど難しくはないように思います。 API 的には C++ の vulkan.hpp に近く、各種構造体は Builder pattern で設定できるので C API ほど面倒くさいという事もないように思いました。 vulkan.hpp からデストラクタがなくなった?というのが近いかなという感じです。

drop trait が実装されていないのが面倒に感じるかもしれませんが、 Vulkan Memory Allocator のようなリソースマネージャーと組み合わせて使う場合は固定的な drop trait は実装されていると逆に困ってしまうことになるので、これはこれでよいかなと思います。

その他のライブラリ

scopeguard

スコープを抜ける際に実行するコードを記述できるようにする crate です。 C++ ではお馴染みだと思います。 Swift の defer ですね。

Ash では drop trait がないので後始末コードは全て自前で書く必要があります。ある程度規模のあるコードであれば管理用の構造体に drop trait を実装すればよいと思いますが、学習コードなどで 1 メソッド内で完結してしまう場合はなかなか面倒です。そういった場合にこの crate を使うと後始末処理を細かく書く必要がなくなって楽です。

この scopeguard crate は defer! マクロという、 Swift の defer そのままの使い勝手を実現できるようになっているのでとても楽です。

let device = unsafe { instance.create_device( /*略*/ ).unwrap() };

defer! {
    unsafe { device.destroy_device(None); }
}

nalgebra

glmath の Rust 版という観点から nalgebra の中の nalgebra-glm を使用してみました。後から調べてみると cgmath-rs を使ってみてもよかったかもとは思いました。

vk-mem

Vulkan Memory Allocator の Rust Binding です。 vk-mem 自体が Ash に依存しているので Vulkano との組み合わせは難しいのではないかと思います (Vulkano をあきらめた切っ掛けでもあります) 。

glfw-rs

OpenGL / Vulkan のデスクトップアプリを簡単に作れる GLFW の Rust Binding です。

今回は参考書のコードを極力改変せずにそのまま Rust で書き直す、としていたので使いましたが、 GLFW を使う強い理由がないのであれば winit を使った方が Rust では簡単かもしれません。

おわりに

「Rust で Vulkan の勉強をする」はこれから習得しようとしている Vulkan という技術自体に加えてその Binding ラッパーの使い方も把握しないといけないのでどうしても遠回りになってはしまいます。当初はその辺から戸惑ってしまいましたが、 Ash は差異も小さかったので使い方をつかんできてからは Rust と Vulkan の勉強の両立っぽくできてきたかなあという感じはあります。

とはいっても Rust のコードはそこそこ書けたのでよかったですが、 Vulkan の理解はまだ全然なので継続していこうと考えています (やりたい事への達成にはまだまだ遠い・・・) 。

C/C++ だと各種 SDK などの事前準備がちょっと面倒ですが、 Rust は Cargo が強力なので始めるまでが圧倒的に楽なのはとてもよいです。

Ash は vulkan.hpp とほとんど変わらないような感じですし、リソースの破棄は些末な事と思いますので個人的には Ash をメインで使っていくことになるかなあと思います。