前回の Vulkano API を用いた Triangle 描画のまとめです。
Vulkan と Vulkano のチュートリアルサイト、Vulkano 関連のドキュメントを参考にしています。
・ Vulkan ( Introduction - Vulkan Tutorial )
・ Vulkano ( Vulkano )
・ Vulkano ドキュメント ( vulkano - Rust )
Vulkano による Triangle 描画の手順
1 インスタンスの作成
2 物理デバイスの列挙と設定
3 ウィンドウとウィンドウサーフェイスの作成
4 Vulkanデバイスとコマンドキューの作成
5 スワップチェインとイメージの作成
6 vertex バッファと uniform バッファの作成
7 shader プログラムの読込み
8 レンダーパスの作成
9 パイプラインの作成
10 フレームバッファの作成
描画ループ内
11 スワップチェインの生成
12 フレームバッファの生成
13 uniform バッファの set の設定
14 コマンドバッファの作成
15 future の生成
16 event ループ
この手順に従って、Triangle 描画の方法を見ていきます。
* 書式は簡単に書いてあります。正しい書き方は、各クレート(crate)のドキュメントを参照して下さい。
1 インスタンスの作成
インスタンス(Instance)オブジェクトは、Struct vulkano::instance::Instance のnewメソッドで作成します。
書式
fn new(app_infos, extensions, layers) -> Result<Instance, InstanceCreationError>
screenに描画するには、newの2番目の引数extensions(InstanceExtensions)を用いて、サーフェイス(surface)を利用可能にします。
利用可能となっているextensionsは、vulkano_win クレートの required_extensions()メッソドを使って取得できます。これを設定します。
取得したextensions(利用可能なサーフェイスのリスト、Ubuntu 17.10)
[VK_KHR_surface, VK_KHR_xlib_surface, VK_KHR_xcb_surface, VK_KHR_wayland_surface, VK_KHR_mir_surface ]
残りの2つの引数は使用しないので、 Noneを設定します。
処理結果は、 Result(Ok(), Err())型で帰ってきます。Result型は、正しく値が得られたときはOk()でラップ(wrap)され、得られなかったときはエラー値がErr()でラップされています。データの取り出しには、match式やunwrap()、expect(msg) メソッドなどを用います。
・match式
パターンマッチを使って2つに場合分けし、処理する。
・unwrapメソッド
正しい値が得られたとき:Ok()でラップされた値を帰す。
正しい値が得られなかったとき:パニック(panic)を起こし処理を停止する。
・expect(msg)メッソド
正しい値が得られたとき:Ok()でラップされた値を帰す。
正しい値が得られなかったとき:パニック(panic)を起こし処理を停止するが、
その時msgの文字を表示する。(エラー箇所の表示に用いる。)
ここではexpect(msg)を使っています。
InstanceExtensionsの設定とInstanceの作成は、次のようになります。
let extensions = vulkano_win::required_extensions(); let instance = Instance::new(None, &extensions, None).expect("failed to create Vulkan instance");
extensionsの式は、instanceの式の中に入れることもできます。
let instance = { let extensions = vulkano_win::required_extensions(); Instance::new(None, &extensions, None).expect("failed to create Vulkan instance") };
2 物理デバイスの列挙
使用できる物理デバイスの列挙には、Struct vulkano::instance::PhysicalDevice のenumerate メソッドを使用します。
書式
fn enumerate(instance) -> PhysicalDevicesIter
引数に、先に作成した instance を渡します。結果は、物理デバイスのイテレータ(PhysicalDevicesIter)が帰ってきます。
イテレータから個別の値を取り出すには、next() メソッドを使います。
next メソッドは Option(Some(), None) 型の値を帰します。Option型のデータの取り出しは、Result型と同様です。ここでも、expect(msg) メソッドを使用しています。
物理デバイスの列挙は、次のようになります。
let physical = PhysicalDevice::enumerate(&instance).next().expect("no device available");
得られた物理デバイスの名前(NECノートPC 2016年製)
Intel(R) HD Graphics 520 (Skylake GT2)
3 ウィンドウとウィンドウサーフェイスの作成
ウィンドウの作成には、Struct winit::WindowBuilderのnewメソッドを使います。
書式
fn new() -> WindowBuilder
newメソッドは、WindowBuilderを帰します。続けてWindowBuilderのwith_dimensions(width, height)、with_title(title)メソッドで、ウィンドウサイズやウィンドウタイトルを設定します。これらのメソッドもWindowBuilderを帰します。
次にbuild_vk_surfaceメソッドを使って、Vulkanサーフェイスを持ったウィンドウを作成します。
書式
fn build_vk_surface(events_loop, instance) -> Result<Window, CreationError>
このメソッドは、引数にウィンドウのevents_loopの参照とinstanceのコピーを取ります。
結果はResult型で帰ってきます。作成したウィンドウ(Window)か作成できない場合は
エラー(CreationError)を帰します。データの取り出しには、unwrap()を使用しています。
events_loopの設定とウィンドウサーフェイスの作成は次のようになります。
let mut events_loop = winit::EventsLoop::new(); let window = winit::WindowBuilder::new() .with_dimensions(800, 600) .with_title(format!("Vulkano Triangle")) .build_vk_surface(&events_loop, instance.clone()).unwrap();
4 Vulkanデバイスとコマンドキューの作成
Vulkanデバイス(論理デバイス)とコマンドキューを作成します。Vulkanデバイスは、物理デバイスとコニュニケーションするための窓口である。
Vulkanデバイスとコマンドキューは、Struct vulkano::device::Deviceのnewメッソドで作成します。
書式
fn new(phys, requested_features, extensions, queue_families) -> Result<(Device, QueuesIter), DeviceCreationError>
引数には、物理デバイス (phys) 、Vulkan features (requested_features) 、デバイス extensions (extensions) 、キューファミリー (queue_families) を指定します。
戻り値はResult型で、Okの場合はVulkanデバイス (Device) とキューのイテレータ (QueuesIter) のタプル ( Device, QueuesIter )を返します。
引数の指定
- Vulkan features
物理デバイスがサポートする features を取得して、指定する。
physical.supported_features()
- デバイス extensions
利用するデバイスextensionsを設定する。
(デバイスextensionsには、khr_swapchain、khr_display_swapchain、
khr_sampler_mirror_clamp_to_edgeなどがある。)
let device_ext = vulkano::device::DeviceExtensions { khr_swapchain: true, .. vulkano::device::DeviceExtensions::none() };
利用しないextensionsは、none()メソッドを使ってまとめて「false」に設定
している。
- キューファミリー
キューファミリーには、物理デバイスから取得したキューファミリーを指定
する。
キューファミリーの取得
1 queue_families()メソッドを使って、キューファミリーのイテレータを
取得する。
physical.queue_families()
2 find()メソッドを使って、条件「q.supports_graphics() &&
window.surface().is_supported(q).unwrap_or(false)」を満たすキュー
ファミリーを取り出す。
qはイテレータの要素(キューファミリー)で、グラフィックス用で
サーフェイスに描画できるキューファミリーを見つけ出している。
サーフェイスへの描画をサポートするキューファミリーが見つからなった
場合には、「false」を帰すようにunwrap_or(false)メソッドを使っている。
find()の結果(Option型)は、expect()メソッドを使って取り出す。
let queue_family = physical.queue_families() .find(|&q| q.supports_graphics() && window.surface().is_supported(q).unwrap_or(false)) .expect("couldn't find a graphical queue family");
キューファミリーの指定
引数queue_familiesは、タプル(QueueFamily, f32)を要素とするイテレータで
指定する。f32はキューの計算スピードのpriorityを表し、0.0から1.0の値を設定
する。
キューファミリーは次のように指定する。
[(queue_family, 0.5)].iter().cloned()
タプルの配列 [(queue_family, 0.5)] からiter()とcloned()メソッドを用いて
イテレータを生成する。cloned()を用いて、 イテレータの要素を参照ありから
参照なしに変更している。
キューファミリーの取得とVulkanデバイスとコマンドキューの作成は、
次のようになる。
// queue family let queue_family = physical.queue_families() .find(|&q| q.supports_graphics() && window.surface().is_supported(q).unwrap_or(false)) .expect("couldn't find a graphical queue family"); // Device and queues let (device, mut queues) = { let device_ext = vulkano::device::DeviceExtensions { khr_swapchain: true, .. vulkano::device::DeviceExtensions::none() }; Device::new(physical, physical.supported_features(), &device_ext, [(queue_family, 0.5)].iter().cloned()).expect("failed to create device") }; let queue = queues.next().unwrap();
最後の行で、next()メソッドを用いてキューのイテレータqueuesから初めの要素を
取り出している。
(2018年2月8日 更新)