Vulkan TutorialのRust版をやってみた 〜グラフィックパイプライン編〜


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

Vulkanのグラフィックパイプライン

VulkanのグラフィックパイプラインはOpenGL等と似たような感じで、図のようになっています。

(画像は
https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Introductionより引用)

シェーダの作成と読み込み

Vulkanoの場合、vulkano-shadersを利用してコンパイル時にGLSLをSPIR-Vに変換することができるので、今回はこれを使います。
まず、GLSLでvertex shaderとfragment shaderを作成します。このとき、

#extension GL_ARB_separate_shader_objects : enable

をつけてください。次にmain.rs

mod vertex_shader {
    vulkano_shaders::shader! {
        ty: "vertex",
        path: "path/to/vertex_shader"
    }
}
mod fragment_shader {
    vulkano_shaders::shader! {
            ty: "fragment",
            path: "path/to/fragment_shader"
        }
    }
vertex_shader::Shader::load(device.clone()).expect("Failed to load the vertex shader");
fragment_shader::Shader::load(device.clone()).expect("Failed to load the fragment shader");

とします。これで最低限のシェーダが読み込めました。

render passの作成

参考元と順番が異なりますが、Vulkanoではパイプラインを作るのにrender passが必要になるので先にこれを作ります。またVulkanoの場合は通常、single_pass_renderpass!マクロで各アタッチメントやパスを書いていくので、C/C++とは大分変わります。

let render_pass = single_pass_renderpass!(device.clone(),
    attachments: { /* 各アタッチメント */ },
    pass: { /* パス */ }
).unwrap()

各アタッチメントの設定

各アタッチメントは上のattachments:に書いていきます。今回はcolorAttachmentを設定します。

attachments: {
    color: {
        load: Clear,
        store: Store,
        format: swapchain.format(),
        samples: 1,
    }
},

ここで、load及びstoreはVulkanのloadOp及びstoreOpに相当するもので、samplesは今回はマルチサンプリングをしていないので1となっています。

パスの設定

上で設定したアタッチメントを用いてパスを設定します。

pass: {
    color: [color],
    depth_stencil: {}
}

fixed functionの設定

fixed functionとはViewportやラスタライザ等の総称のようです。Vulkanの場合、fixed functionに分類されるものはパイプライン毎に固定で基本は変更できないようです。ただし、Viewport等は設定時に動的に変換することも出来ます。
Vulkanoの場合はvulkano::pipeline::GraphicsPipelineにいわゆるビルダーパターンで作っていきます。

vertex input及びvertex shaderの設定

今回の参考元ではvertex shader自身がデータを持っているので外部から頂点データを入力する必要がありません。Vulkanoの場合、こういうときはvulkano::pipeline::vertex::BufferlessDefinitionを使います。なお、頂点データを入力する際にはimpl_vertex!を使います。

.vertex_input(BufferlessDefinition)
.vertex_shader(vertex_shader.main_entry_point(), ())

viewportの設定

上記のように動的なviewportにすることも可能ですがここでは固定サイズにし、scissorは全体を写すようにします。

let viewport = Viewport {
    origin: [0.0, 0.0],
    dimensions: [
        swapchain.dimensions()[0] as _,
        swapchain.dimensions()[1] as _
    ],
    depth_range: 0.0..1.0,
};
builder.viewports(vec![viewport]);

Subpassの設定

上で作成したrender passをSubpassに変換して使います。

.render_pass(Subpass::from(render_pass, 0).unwrap())

まとめと感想

今回も特に実行結果は変化せずシェーダの読み込みやパイプラインの作成で終わりましたが、次回はいよいよ描画できるはずです。