以OpenGL/ES視角介紹gfx-hal(Vulkan) Framebuffer介面使用

熊皮皮發表於2019-02-26

文件列表見: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: 描述ImagesFramebuffer的區別。

/// 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.

www.khronos.org/registry/vu…

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(),這一步相當於eglSwapbufferEAGLContext presentRenderbuffer交換前後幀緩衝區,不算OpenGL/ES Framebuffer的操作,但是,不切EGLContext的預設幀緩衝區將看不到畫面,為了方便,在此將其當成Framebuffer的協同操作。

// present frame
swapchain.present(
    &mut queues.queues[0],
    frame,
    Some(&*image_present),
)
複製程式碼

相關文章