Vulkanoで三角形描画
そもそも、vulkano
とは
Vulkano is a Rust wrapper around the Vulkan graphics API.
Vulkan graphics API
のRustラッパーらしい。
Vulkanをまともに触ったことさえないが、Rustが好きなのでvulkano
に入門してみることにする。
このスクラップには、vulkano
で遊ぶ上で気になったことやメモを残すことにしようと思う。
Windowの表示
vulkano
はレンダリングAPIなので、ウィンドウの作成と管理はwinit
が行う。
// ロガーの初期化
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
// イベントループを作成
let event_loop = EventLoop::new()?;
let window = Arc::new(WindowBuilder::new().build(&event_loop)?);
event_loop.run(move |event, elwt| {
elwt.set_control_flow(ControlFlow::Poll);
match event {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
elwt.exit();
}
Event::AboutToWait => window.request_redraw(),
_ => (),
}
})?;
インスタンスの作成
描画に必要な拡張機能や、有効化するレイヤー(今回は検証レイヤー)を指定してインスタンスを作成する。
検証レイヤーはVulkan SDK
をインストールしないと使用できないので注意。
// 描画に必要な拡張をSurfaceに問い合わせる
let required_extensions = Surface::required_extensions(&event_loop)?;
let available_layers = library.layer_properties()?;
let requested_layers = VALIDATION_LAYERS
.iter()
.map(|s| s.to_string())
.collect::<HashSet<_>>();
let instance = Instance::new(
library,
InstanceCreateInfo {
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
enabled_extensions: required_extensions,
enabled_layers: if VALIDATION_ENABLED {
available_layers
.filter(|p| requested_layers.contains(p.name()))
.map(|p| p.name().to_string())
.collect::<Vec<_>>()
} else {
vec![]
},
..Default::default()
},
)?;
物理デバイスの選択と論理デバイスの作成
物理デバイスの選択
複雑そうに見えるがしていることは単純で
- 物理デバイスのリストから必要な拡張がサポートされたデバイスをフィルター
- GRAPHICSに使えてsurfaceをサポートしているデバイスをフィルター
- デバイスのタイプに重みを付けて適しているものを選ぶ
queue_family_index
はよくわからないが、あとから使うのでその時に調べる。
let (physical_device, queue_family_index) = instance
.enumerate_physical_devices()?
.filter(|p| p.supported_extensions().contains(&device_extensions))
.filter_map(|p| {
p.queue_family_properties()
.iter()
.enumerate()
.position(|(i, q)| {
q.queue_flags.intersects(QueueFlags::GRAPHICS)
&& p.surface_support(i as u32, &surface).unwrap_or(false)
})
.map(|i| (p, i as u32))
})
.min_by_key(|(p, _)| match p.properties().device_type {
PhysicalDeviceType::DiscreteGpu => 0,
PhysicalDeviceType::IntegratedGpu => 1,
PhysicalDeviceType::VirtualGpu => 2,
PhysicalDeviceType::Cpu => 3,
PhysicalDeviceType::Other => 4,
_ => 5,
})
.expect("Failed to find a suitable device.");
論理デバイスの作成
Vulkanoでは論理デバイスを作成するときに、一緒にリクエストしたキューがイテレータで帰ってくるらしい。
// キューがイテレーターで帰ってくる
let (device, mut queues) = Device::new(
physical_device,
DeviceCreateInfo {
enabled_extensions: device_extensions,
queue_create_infos: vec![QueueCreateInfo {
queue_family_index,
..Default::default()
}],
..Default::default()
},
)?;
// 今回はキューを1つしかリクエストしていないので最初の要素を取り出す
let queue = queues.next().unwrap();
スワップチェーンの作成
Vulkanoではスワップチェーンを作成するときに画面上に表示されるimage
も一緒に返却される。
surface_capabilities.min_image_count
は一部のドライバでフルスクリーン時に1
が返されることがあるらしいので、最小値を2
にしておく。
let (mut swapchain, images) = {
let surface_capabilities = device
.physical_device()
.surface_capabilities(&surface, Default::default())?;
let image_format = device
.physical_device()
.surface_formats(&surface, Default::default())?[0]
.0;
Swapchain::new(
device.clone(),
surface,
SwapchainCreateInfo {
min_image_count: surface_capabilities.min_image_count.max(2),
image_format,
image_extent: window.inner_size().into(),
image_usage: ImageUsage::COLOR_ATTACHMENT,
composite_alpha: surface_capabilities
.supported_composite_alpha
.into_iter()
.next()
.unwrap(),
..Default::default()
},
)?
};
頂点バッファの作成
GPU側に頂点情報を渡すために頂点バッファを作成する。
vulkano
ではメモリをアロケーターを通して管理しているため、安全に使用することができるらしい。
memory_type_filter
については、よくわかっていないのでまた調べる。
#[derive(BufferContents, vertex_input::Vertex)]
#[repr(C)]
struct Vertex {
#[format(R32G32_SFLOAT)]
position: [f32; 2],
}
let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));
let vertices = [
Vertex {
position: [-0.5, -0.25],
},
Vertex {
position: [0.0, 0.5],
},
Vertex {
position: [0.25, -0.1],
},
];
let vertex_buffer = Buffer::from_iter(
memory_allocator,
BufferCreateInfo {
usage: BufferUsage::VERTEX_BUFFER,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
vertices,
)?;
シェーダーモジュールの作成
vulkano
ではvulkano_shader
のshader!
マクロを使用して簡単にシェーダーモジュールを記述できる。
mod vs {
vulkano_shaders::shader! {
ty: "vertex",
path: "src/shaders/triangle.vert"
}
}
mod fs {
vulkano_shaders::shader! {
ty: "fragment",
path: "src/shaders/triangle.frag"
}
}
#version 450
layout(location = 0) in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
#version 450
layout(location = 0) out vec4 f_color;
void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0);
}
レンダーパスと描画パイプラインの作成
レンダーパスとは描画の処理順序を記述したもので、vulkano
ではマクロを使って簡単に記述できるようになっている。
パイプラインとは、頂点シェーダーとかテッセレーションとかの演算順序を記述するオブジェクトのようなもの。
また、subpass
というVulkanには存在しない構造体が出てきているが、実際はRenderPass
とsubpass index
のタプルである。
let render_pass = vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: swapchain.image_format(),
samples: 1,
load_op: Clear,
store_op: Store,
}
},
pass: {
color: [color],
depth_stencil: {}
}
)?;
let pipeline = {
let vs = vs::load(device.clone())?.entry_point("main").unwrap();
let fs = fs::load(device.clone())?.entry_point("main").unwrap();
let vertx_input_state = <Vertex as vertex_input::Vertex>::per_vertex()
.definition(&vs.info().input_interface)?;
let stages = [
PipelineShaderStageCreateInfo::new(vs),
PipelineShaderStageCreateInfo::new(fs),
];
let layout = PipelineLayout::new(
device.clone(),
PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages)
.into_pipeline_layout_create_info(device.clone())?,
)?;
let subpass = Subpass::from(render_pass.clone(), 0).unwrap();
GraphicsPipeline::new(
device.clone(),
None,
GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
// 頂点バッファからどのようにして頂点を読み込むかを指定
vertex_input_state: Some(vertx_input_state),
// デフォルトでは三角形
input_assembly_state: Some(InputAssemblyState::default()),
viewport_state: Some(ViewportState::default()),
// 多分かリングを行うとか行わないとか
rasterization_state: Some(RasterizationState::default()),
// マルチサンプリングの設定
multisample_state: Some(MultisampleState::default()),
// カラーブレンドの設定
color_blend_state: Some(ColorBlendState::with_attachment_states(
subpass.num_color_attachments(),
ColorBlendAttachmentState::default(),
)),
dynamic_state: [DynamicState::Viewport].into_iter().collect(),
subpass: Some(subpass.into()),
..GraphicsPipelineCreateInfo::layout(layout)
},
)?
};