大致翻译了一下,为了屏蔽细节,注释有删减
注:其实我们写程序应该屏蔽细节,直接从vulkano里的teapot案例改起
fn main() {
let required_extensions = vulkano_win::required_extensions();
let instance = Instance::new(None, &required_extensions, None).unwrap();
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
println!("Using device: {} (type: {:?})", physical.name(), physical.ty());
// 本案例目标是如何在窗口上画一个三角形。
// 首先创建个窗口
// 使用winit这个crate中的WindowBuilder来完成
// 然后调用build_vk_surface方法,这个方法是由vulkano_win提供的VkSurfaceBuild trait
// 如果发生build_vk_surface未定义的编译错误,可能你忘记去导入这个trait了
//
// 这返回一个vulkano::swapchain::Surface对象
// 包含一个跨平台的winit窗口和这个窗口的Vulkan surface
let event_loop = EventLoop::new();
let surface = WindowBuilder::new().build_vk_surface(&event_loop, instance.clone()).unwrap();
// 下一步是选择执行绘制命令的GPU队列
//
// 设备能够提供执行命令的多个队列,比如渲染队列跟计算队列,类似CPU线程
// 现实中,我们会并行使用队列处理数据,但本案例中我们就用一个
// 得先选一个,给后面的步骤提供信息
let queue_family = physical.queue_families().find(|&q| {
// 选能够支持的第一个队列
q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
}).unwrap();
// 现在创建设备,这可能是Vulkan里最重要的东西
//
// 创建设备时,要传入5个参数
//
// - 要连接上的那个物理设备
//
// - 程序所需的可选特性和扩展列表
// Vulkan一些特性是可选的,需要手动开启
// 本案例中仅需khr_swapchain扩展,此扩展运行在窗口上绘制
//
// - 一些layer的启用
//
// - 使用的队列列表
// 准确的参数是一个(Queue, f32)的迭代器,浮点数表示队列优先级,范围是0.0到1.0
//
// 最终返回创建的队列列表
let device_ext = DeviceExtensions { khr_swapchain: true, .. DeviceExtensions::none() };
let (device, mut queues) = Device::new(physical, physical.supported_features(), &device_ext,
[(queue_family, 0.5)].iter().cloned()).unwrap();
// 因为能用多个队列,变量queues实际上是一个迭代器
// 这个案例中我们仅用一个队列,找到第一个就行
let queue = queues.next().unwrap();
// 在surface上绘制之前,先要创建交换链
// 创建交换链时分配的颜色缓冲就是最终包含要在屏幕上显示图像的地方
let (mut swapchain, images) = {
// 查询surface的兼容性,仅当允许传值的情况下在创建交换链
let caps = surface.capabilities(physical).unwrap();
let usage = caps.supported_usage_flags;
// alpha模式表示整个窗口是否含有透明部分,比如做个圆形窗口
let alpha = caps.supported_composite_alpha.iter().next().unwrap();
// 选择一个支持的内部图片格式
let format = caps.supported_formats[0].0;
// 窗口分辨率
let dimensions: [u32; 2] = surface.window().inner_size().into();
Swapchain::new(device.clone(), surface.clone(), caps.min_image_count, format,
dimensions, 1, usage, &queue, SurfaceTransform::Identity, alpha,
PresentMode::Fifo, FullscreenExclusive::Default, true, ColorSpace::SrgbNonLinear).unwrap()
};
// 现在创建用来存储三角形形状的缓存
let vertex_buffer = {
#[derive(Default, Debug, Clone)]
struct Vertex { position: [f32; 2] }
vulkano::impl_vertex!(Vertex, position);
CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), false, [
Vertex { position: [-0.5, -0.25] },
Vertex { position: [0.0, 0.5] },
Vertex { position: [0.25, -0.1] }
].iter().cloned()).unwrap()
};
// 下一步是创建shader
//
// 由于各种原因,vulkano创建shader的的api是unsafe
// 所以用vulkano-shaders中的vulkano_shaders::shader宏来搞
mod vs {
vulkano_shaders::shader!{
ty: "vertex",
src: "
#version 450
layout(location = 0) in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
"
}
}
mod fs {
vulkano_shaders::shader!{
ty: "fragment",
src: "
#version 450
layout(location = 0) out vec4 f_color;
void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0);
}
"
}
}
let vs = vs::Shader::load(device.clone()).unwrap();
let fs = fs::Shader::load(device.clone()).unwrap();
// 此时,OpenGL初始化算是完成。但在Vulkan里还不够。
// 当绘制的时候,OpenGL隐式做了一堆计算。在Vulkan里,你得手动来做。
// 下一步是创建渲染过程,这是描述图形管线将要输出的对象
// 描述了图像的布局,颜色,深度,模板等将要写入的信息
let render_pass = Arc::new(vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
// load: Clear表示绘制之前清除原有画面
load: Clear,
// store: Store表示将绘制结果储存到真实图像中
store: Store,
// format: <ty>指定图像格式,此处与交换链相同
format: swapchain.format(),
samples: 1,
}
},
pass: {
// 只使用名为color的唯一色彩attachment
color: [color],
// 用{}表示没有深度和模板attachment
depth_stencil: {}
}
).unwrap());
// 画之前得先创建管线,这与OpenGL相似,但粒度更细
let pipeline = Arc::new(GraphicsPipeline::start()
// 需要表明顶点的layout
// SingleBufferDefinition类型实际上包含了与每个顶点类型相对应的模板参数
// 但在本代码中自动推导
.vertex_input_single_buffer()
// Vulkan shader理论上能含有多个入口点,得手动指定一个
// main_entry_point中的main对应shader中的入口点名字
.vertex_shader(vs.main_entry_point(), ())
// 顶点缓存中存储着三角形列表
.triangle_list()
// 用可动态变化的viewport
.viewports_dynamic_scissors_irrelevant(1)
.fragment_shader(fs.main_entry_point(), ())
// 指定渲染子管线
.render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
// 现在建造者已经完成,调用build获得整个管线(注:建造者模式)
.build(device.clone())
.unwrap());
// 动态viewport运行窗口缩放时重建viewport
let mut dynamic_state = DynamicState { line_width: None, viewports: None, scissors: None, compare_mask: None, write_mask: None, reference: None };
// 此前创建的渲染过程只描述了帧缓冲的layout
// 绘制之前得需要创建真实的帧缓冲
//
// 因为要绘制多张图像,要为每张图像创建不同的帧缓冲
let mut framebuffers = window_size_dependent_setup(&images, render_pass.clone(), &mut dynamic_state);
// 初始化完成
// 某些情况,交换链会失效,比如窗口缩放,此时需要重建交换链
let mut recreate_swapchain = false;
// 在下面的循环中,向GPU提交命令Submitting a command produces
// 提交命令会产生一个实现GpuFuture trait的对象,持有gpu所需的资源
//
// 直到gpu结束执行,才会销毁GpuFuture,此前一直会被阻塞
// 为此,我们存储前一帧的任务
let mut previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>);
event_loop.run(move |event, _, control_flow| {
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
*control_flow = ControlFlow::Exit;
},
Event::WindowEvent { event: WindowEvent::Resized(_), .. } => {
recreate_swapchain = true;
},
Event::RedrawEventsCleared => {
// 要不时调用本函数,否则资源累积会爆掉内存
previous_frame_end.as_mut().unwrap().cleanup_finished();
// 当窗口缩放时,我们需要重建依赖窗口尺寸的一切对象
// 本案例中是交换链,帧缓冲和动态viewport
if recreate_swapchain {
// 得到新窗口的分辨率
let dimensions: [u32; 2] = surface.window().inner_size().into();
let (new_swapchain, new_images) = match swapchain.recreate_with_dimensions(dimensions) {
Ok(r) => r,
Err(SwapchainCreationError::UnsupportedDimensions) => return,
Err(e) => panic!("Failed to recreate swapchain: {:?}", e)
};
swapchain = new_swapchain;
// 因为帧缓冲包含老交换链的arc,也需要重建帧缓冲
framebuffers = window_size_dependent_setup(&new_images, render_pass.clone(), &mut dynamic_state);
recreate_swapchain = false;
}
// 绘制之前,需要从交换链获得一张图像的索引
//
// 如果图像不可用,此函数会被阻塞
let (image_num, suboptimal, acquire_future) = match swapchain::acquire_next_image(swapchain.clone(), None) {
Ok(r) => r,
Err(AcquireError::OutOfDate) => {
recreate_swapchain = true;
return;
},
Err(e) => panic!("Failed to acquire next image: {:?}", e)
};
if suboptimal {
recreate_swapchain = true;
}
// 指定clear颜色
let clear_values = vec!([0.0, 0.0, 1.0, 1.0].into());
// 为了绘制,我们首先要创建命令缓冲
// 命令缓冲对象存储要执行的命令列表
//
// 构建命令缓冲是开销昂贵的操作 (通常几百毫秒)
//
// 注意当创建命令缓冲时,已经传入了队列族
// 命令缓冲仅在指定的队列族上执行
let command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), queue.family()).unwrap()
// 绘制之前,得先进入渲染过程
//
// 第三个参数构建清理attachment的值的列表
// API与构建帧缓冲attachment列表相似,不过attachment仅使用load: Clear
.begin_render_pass(framebuffers[image_num].clone(), false, clear_values).unwrap()
// 现在在渲染过程的第一个子过程中,添加一个绘制命令
//
// 后面两个参数包含了传递到shader里的资源列表
// 因为我们使用了一个EmptyPipeline对象,对象必须是()
.draw(pipeline.clone(), &dynamic_state, vertex_buffer.clone(), (), ()).unwrap()
// 调用draw_end结束渲染管线
.end_render_pass().unwrap()
// 调用build结束构建命令缓冲
.build().unwrap();
let future = previous_frame_end.take().unwrap()
.join(acquire_future)
.then_execute(queue.clone(), command_buffer).unwrap()
// 颜色输出包含着想要的三角形
// 但像在屏幕上显示,需要调用present方法
//
// 此方法实际上并不会立即显示图片,而是在队列末尾提交一条显示命令
// 意味着只显示一次
// GPU此时已经结束执行绘制三角形的命令缓冲
.then_swapchain_present(queue.clone(), swapchain.clone(), image_num)
.then_signal_fence_and_flush();
match future {
Ok(future) => {
previous_frame_end = Some(Box::new(future) as Box<_>);
},
Err(FlushError::OutOfDate) => {
recreate_swapchain = true;
previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<_>);
}
Err(e) => {
println!("Failed to flush future: {:?}", e);
previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<_>);
}
}
},
_ => ()
}
});
}