gfx-hal 逐帧渲染流程介绍

文档列表见:Rust 移动端跨平台复杂图形渲染项目开发系列总结(目录)

gfx-hal接口以1:1模仿Vulkan,下面改用Vulkan接口作说明。由于Vulkan接口粒度过细,比OpenGL / ES难学数倍。根据个人经验,对于移动端图形开发者,照着OpenGL ES的接口讲解Vulkan可降低学习难度。从逐帧渲染部分开始学习,跳过这些数据结构的初始化过程,可以更明显地感受到Vulkan的核心流程。

OpenGL / ES 逐帧渲染流程示例

// 准备渲染目标环境
glBindFramebuffer();
glFramebufferTexture2D(); glCheckFramebufferStatus(); // 假如渲染到纹理
glViewport(x, y, width, height);
// 准备渲染目标环境
glUseProgram(x);
glBindBuffer(i)
loop i in 0..VertexVarCount {
    glEnableVertexAttribArray(i);
    glVertexAttribPointer(i, ...); 
}
loop i in 0..UniformVarCount {
    switch UniformType {
        case NoTexture: glUniformX(i, data); break;
        case Texture: {
            glActiveTexture(j);
            glBindTexture(type, texture_name);
            glUniform1i(location, j);
            break;
        }
        default:ERROR();
    }
}
// 配置其他Fragment操作,比如glBlend, glStencil
glDrawArrays/Elements/ArraysInstanced...
// 到此完成Draw Call,视情况调用EGL函数交换前后帧缓冲区,非GL函数,
// 渲染到纹理则无此操作。
// 为了不干扰后续绘制,恢复刚才设置的Fragment操作为默认值。
eglSwapbuffers()/[EAGLContext presentRenderbuffer]; 
复制代码

可见,OpenGL / ES的接口屏蔽了绝大部分细节,整体代码量显得很少,但初学时也不好理解,用久了就成套路,觉得就该这样,以致于第一次接触Vulkan发现很多细节之前完全不了解,有点懵。

gfx-hal逐帧渲染到视图的调用流程介绍

gfx-hal(Vulkan)逐帧渲染到视图的核心调用流程如下所示:

EventSource ->[CommandPool -> ComanndBuffer
                -> Submit -> Submission
                -> QueueGroup -> CommandQueue]
-> GraphicsHardware
复制代码

说明:

  • EventSource:表示信号源,比如相机回调一帧图像、屏幕的vsync信号、用户输入等。
  • CommandQueue:用于执行不同类型任务的队列,比如渲染任务、计算任务。
  • QueueGroup:CommandQueue集合
  • GraphicsHardware:图形硬件

具体流程代码:

  • 重置Fence,给后面提交Submission到队列使用。

    device.reset_fence(&frame_fence);
    复制代码
  • 重置CommandPool

    command_pool.reset();
    复制代码
  • 从SwapChain获取Image索引

    let frame = swap_chain.acquire_image(!0, FrameSync::Semaphore(&mut frame_semaphore));
    复制代码
  • 通过CommandPool创建、配置CommandBuffer,命令录制结束后得到有效的Submit对象

    let mut cmd_buffer = command_pool.acquire_command_buffer(false);
    // 一系列类似OpenGL / ES的Fragment操作、绑定数据到Program的配置
    // 两个值得注意的Pipeline操作
    cmd_buffer.bind_graphics_pipeline(&pipeline);
    cmd_buffer.bind_graphics_descriptor_sets(&pipeline_layout, 0, Some(&desc_set), &[]);
    // 联合RenderPass的操作
    let mut encoder = cmd_buffer.begin_render_pass_inline(&render_pass,...);
    let submit = cmd_buffer.finish()
    复制代码
  • 通过Submit创建Submission

    let submission = Submission::new()
        .wait_on(&[(&frame_semaphore, PipelineStage::BOTTOM_OF_PIPE)])
        .submit(Some(submit));
    复制代码
  • 提交Submission到队列

    queue.submit(submission, Some(&mut frame_fence));
    复制代码
  • 等待CPU编码完成

    device.wait_for_fence(&frame_fence, !0);
    复制代码
  • 交换前后帧缓冲区

    swap_chain.present(&mut queue_group.queues[0], frame, &[])
    复制代码

    配置CommandBuffer的进一步介绍

OpenGL / ES 2/3.x没CommandPoolCommandBuffer数据结构,除了最新的OpenGL小版本才加入了SPIR-V和Command,但OpenGL ES还没更新。Metal的CommandBuffer接口定义不同于Vulkan。Metal创建MTLCommandBuffer,由Buffer与RenderPassDescriptor一起创建出 Enconder,然后打包本次渲染相关的资源,最后提交Buffer到队列让GPU执行。Vulkan基本把Metal的Encoder操作放到CommandBuffer,只留了很薄的Encoder操作。

总体流程:

  • 由Command Pool分配可用Command Buffer
  • 配置viewport等信息
  • 设置输出目标
  • 设置绘制方式,draw/draw_indexed/draw_indirect等等
  • 结束配置

代码示例如下:

let submit = {
    // 从缓冲区中取出一个实际为RawCommandBuffer的实例,加上线程安全对象,组装成CommandBuffer实例,这是线程安全的
    let mut cmd_buffer = command_pool.acquire_command_buffer(false);

    cmd_buffer.set_viewports(0, &[viewport.clone()]);
    cmd_buffer.set_scissors(0, &[viewport.rect]);
    cmd_buffer.bind_graphics_pipeline(&pipeline.as_ref().unwrap());
    cmd_buffer.bind_vertex_buffers(0, pso::VertexBufferSet(vec![(&vertex_buffer, 0)]));
    cmd_buffer.bind_graphics_descriptor_sets(&pipeline_layout, 0, Some(&desc_set)); //TODO

    {
        let mut encoder = cmd_buffer.begin_render_pass_inline(
            &render_pass,
            &framebuffers[frame.id()],
            viewport.rect,
            &[command::ClearValue::Color(command::ClearColor::Float([0.8, 0.8, 0.8, 1.0]))],
        );
        encoder.draw(0..6, 0..1);
    }

    cmd_buffer.finish()
};
复制代码

前面代码显示了CommandBuffer两个很关键的操作:bind_graphics_pipeline(GraphicsPipeline)bind_graphics_descriptor_sets(PipelineLayout, DescriptorSet)。GraphicsPipeline相当于OpenGL / ES的Program,PipelineLayoutDescriptorSet描述了Shader的Uniform变量如何读取Buffer的数据。

猜你喜欢

转载自juejin.im/post/5c064b9bf265da612859e462
HAL