Vala プログラミング

WebGPU プログラミング

おなが@京都先端科学大

Rust Vulkano Triangle (2)

前回の Vulkano API を用いた Triangle 描画のまとめです。
Vulkan と Vulkano のチュートリアルサイト、Vulkano 関連のドキュメントを参考にしています。
・ Vulkan ( Introduction - Vulkan Tutorial )
・ Vulkano ( Vulkano )
・ Vulkano ドキュメント ( vulkano - Rust )

Vulkano による Triangle 描画の手順
インスタンスの作成
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)のドキュメントを参照して下さい。

インスタンスの作成
 インスタンス(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には、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日 更新)