以OpenGL/ES视角介绍gfx-hal(Vulkan) Framebuffer接口使用

文档列表见: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),
)
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值