1. 程式人生 > >gfx-hal 逐幀渲染流程介紹

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的資料。