文档列表见:Rust 移动端跨平台复杂图形渲染项目开发系列总结(目录)
草稿状态
以OpenGL/ES Framebuffer角度看,如果用gfx-hal(Vulkan)接口实现类似OpenGL/ES Framebuffer的功能,这一过程远比OpenGL/ES那几个函数调用复杂,因为涉及了多个组件:Swapchain、RenderPass、Framebuffer、CommandBuffer、Submission等。对于不同场景,这些组件并不是必需的:
- 当渲染到屏幕时,它们是必需的,全都要合理配置。
- 当渲染到纹理(Render to Texture,后面简称RTT)时,可忽略Swaphain,只需RenderPass、Framebuffer、CommandBuffer等。
Swapchain
The
Swapchain
is the backend representation of the surface. It consists of multiple buffers, which will be presented on the surface.A
Surface
abstracts the surface of a native window, which will be presented on the display.
Backbuffer - Swapchain的后端缓冲区类型
todo: 描述Images
和Framebuffer
的区别。
/// Swapchain backbuffer type
#[derive(Debug)]
pub enum Backbuffer<B: Backend> {
/// Color image chain
Images(Vec<B::Image>),
/// A single opaque framebuffer
Framebuffer(B::Framebuffer),
}
复制代码
SwapchainConfig
SwapchainConfig定义
Contains all the data necessary to create a new
Swapchain
: color, depth, and number of images.
SwapchainConfig初始化
let (caps, formats, _present_modes) = surface.compatibility(&physical_device);
println!("formats: {:?}", formats);
let format = formats
.map_or(format::Format::Rgba8Srgb, |formats| {
formats
.iter()
.find(|format| format.base_format().1 == ChannelType::Srgb)
.map(|format| *format)
.unwrap_or(formats[0])
});
println!("Surface format: {:?}", format);
let swap_config = SwapchainConfig::from_caps(&caps, format);
复制代码
创建Swapchain
值得注意的是,RTT场景无需创建Swapchain。
let (swapchain, backbuffer) = device.create_swapchain(
&mut surface,
swap_config,
None,
);
复制代码
RenderPass
A render pass represents a collection of attachments, subpasses, and dependencies between the subpasses, and describes how the attachments are used over the course of the subpasses. The use of a render pass in a command buffer is a render pass instance.
RenderPass包含Attachment、SubpassDesc和SubpassDependency。RTT场景由于偷懒没创建Surface和Swapchain,那么,在创建Attachment时format值不能再从swapchain获取,改用Image(Texture)的format才合理。
初始化RenderPass
根据前面可知,创建RenderPass需要先创建它的子组件,下面逐次描述。
创建Attachment
let attachment = pass::Attachment {
format: Some(swapchain.format.clone()), // NOTE: RTT case
samples: 1,
ops: pass::AttachmentOps::new(
pass::AttachmentLoadOp::Clear,
pass::AttachmentStoreOp::Store,
),
stencil_ops: pass::AttachmentOps::DONT_CARE,
layouts: image::Layout::Undefined..image::Layout::Present,
};
复制代码
创建SubpassDesc
let subpass = pass::SubpassDesc {
colors: &[(0, image::Layout::ColorAttachmentOptimal)],
depth_stencil: None,
inputs: &[],
resolves: &[],
preserves: &[],
};
复制代码
创建SubpassDependency
let dependency = pass::SubpassDependency {
passes: pass::SubpassRef::External..pass::SubpassRef::Pass(0),
stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT
..PipelineStage::COLOR_ATTACHMENT_OUTPUT,
accesses: image::Access::empty()
..(image::Access::COLOR_ATTACHMENT_READ | image::Access::COLOR_ATTACHMENT_WRITE),
};
复制代码
创建RenderPass
终于,三大组件就绪后,可以从Device创建一个RenderPass。
let render_pass = device.create_render_pass(&[attachment], &[subpass], &[dependency]);
复制代码
Framebuffer
初始化Framebuffer
创建Framebuffer
渲染到View场景会根据swapchain.backbuffer
类型进行不同的Framebuffer创建流程,主体逻辑示意如下:
let (frame_images, framebuffers) = match swapchain.backbuffer.take().unwrap() {
Backbuffer::Images(images) => {
let extent = // ...
let pairs = // ...
let fbos = pairs.iter().map(
/* ... */
device.create_framebuffer(/* ... */);
/* ... */).collect();
(pairs, fbos)
}
Backbuffer::Framebuffer(fbo) => (Vec::new(), vec![fbo]),
};
复制代码
创建CommandPool
let iter_count = if frame_images.len() != 0 {
frame_images.len()
} else {
1 // GL can have zero
};
let mut fences: Vec<B::Fence> = vec![];
let mut command_pools: Vec<hal::CommandPool<B, hal::Graphics>> = vec![];
let mut acquire_semaphores: Vec<B::Semaphore> = vec![];
let mut present_semaphores: Vec<B::Semaphore> = vec![];
for _ in 0..iter_count {
fences.push(device.create_fence(true));
command_pools.push(device.create_command_pool_typed(
&queues,
pool::CommandPoolCreateFlags::empty(),
16,
));
acquire_semaphores.push(device.create_semaphore());
present_semaphores.push(device.create_semaphore());
}
复制代码
创建CommandBuffer
// Rendering
let submit = {
let mut cmd_buffer = command_pool.acquire_command_buffer(false);
cmd_buffer.set_viewports(0, &[self.viewport.clone()]);
cmd_buffer.set_scissors(0, &[self.viewport.rect]);
cmd_buffer.bind_graphics_pipeline(self.pipeline);
cmd_buffer.bind_vertex_buffers(
0,
Some((self.vertex_buffer.get_buffer(), 0)),
);
cmd_buffer.bind_graphics_descriptor_sets(
self.pipeline.pipeline_layout,
0,
vec![self.image.desc.set, self.uniform.desc.set],
&[],
); //TODO
{
let mut encoder = cmd_buffer.begin_render_pass_inline(
render_pass,
framebuffer,
self.viewport.rect,
&[command::ClearValue::Color(command::ClearColor::Float([
cr, cg, cb, 1.0,
]))],
);
encoder.draw(0..6, 0..1);
}
cmd_buffer.finish()
};
复制代码
提交CommandBuffer到GPU队列
RTT场景到这一步就结束了,通常还会配置Submission执行完成的回调,方便我们提交下一个Submission。
let submission = Submission::new()
.wait_on(&[(&*image_acquired, PipelineStage::BOTTOM_OF_PIPE)])
.signal(&[&*image_present])
.submit(Some(submit));
queues.queues[0].submit(submission, Some(framebuffer_fence));
复制代码
交换前后帧缓冲区
渲染到屏幕才需要swapchain.present()
,这一步相当于eglSwapbuffer
或EAGLContext presentRenderbuffer
交换前后帧缓冲区,不算OpenGL/ES Framebuffer的操作,但是,不切EGLContext的默认帧缓冲区将看不到画面,为了方便,在此将其当成Framebuffer的协同操作。
// present frame
swapchain.present(
&mut queues.queues[0],
frame,
Some(&*image_present),
)
复制代码