RenderPass从名字就可以看出是与渲染相关的,非渲染的任务则不需要使用RenderPass,例如:计算着色器就不需要RenderPass。
RenderPass渲染时用于设置FrameBuffer的格式,以及subpass。创建FrameBuffer和GraphicsPipeline时需要提供一个RenderPass对象。
typedef struct VkRenderPassCreateInfo {
VkStructureType sType;
const void* pNext;
VkRenderPassCreateFlags flags;
uint32_t attachmentCount;
const VkAttachmentDescription* pAttachments;
uint32_t subpassCount;
const VkSubpassDescription* pSubpasses;
uint32_t dependencyCount;
const VkSubpassDependency* pDependencies;
} VkRenderPassCreateInfo;
RenderPass的概念和作用
RenderPass是Vulkan中一个重要的概念,它用于描述一系列的渲染操作,以及这些操作所涉及的资源和依赖关系。RenderPass的作用可以分为以下几个方面:
- 设置FrameBuffer的格式
- 设置渲染的subpass,主要是subpass的输入/输出 attachment
- 设置各个subpass之间的同步
- 转换attachment中Image的Layout
设置FrameBuffer格式
一个FrameBuffer可以有多个attachment,每个attachment会对应一个image。创建FrameBuffer时需要提供一个RenderPass,这个RenderPass中的attachment需要与FrameBuffer的attachment一一对应,且RenderPass中每个attachment设置的VkFormat需要和FrameBuffer中attachment对应的image格式一致。
设置渲染的subpass
typedef struct VkSubpassDescription {
VkSubpassDescriptionFlags flags;
VkPipelineBindPoint pipelineBindPoint;
uint32_t inputAttachmentCount;
const VkAttachmentReference* pInputAttachments;
uint32_t colorAttachmentCount;
const VkAttachmentReference* pColorAttachments;
const VkAttachmentReference* pResolveAttachments;
const VkAttachmentReference* pDepthStencilAttachment;
uint32_t preserveAttachmentCount;
const uint32_t* pPreserveAttachments;
} VkSubpassDescription;
一个RenderPass中可以有多个subpass,每个subpass可以设置输入的attachment、 输出的attachment、多重采样resolve的attachment
输入attachment
一个Renderpass中有多个subpass,后面的subpass需要用到前面subpass的渲染结果当作输入,这时就需要设置subpass的pInputAttachments,每个subpass可以设置多个输入。
输出attachment
每个subpass都必须有输出,可以设置多个颜色输出和一个深度模板输出。
多个颜色输出是用于MTR(Multiple Render Targets)技术,可以一次渲染输出到多个颜色attachment,例如延迟渲染就需要使用该技术用于同时输出颜色、位置、法线等。
深度模板缓存只有一个,也就是说如果同时用到深度测试和模板测试,需要Image同时包含深度和模板。
多重采样resolve的attachment
当使用多重采样时subpass可以在渲染完成后自动resolve,只需要设置pResolveAttachments,pResolveAttachments需要与pColorAttachments一一对应。
渲染时切换subpass
1. 在创建GraphicsPipeline时需要设置subpass的索引
2. vkCmdBeginRenderPass后渲染完一个subpass需要调用vkCmdNextSubpass切换到下一个subpass
subpass之间的同步和Layout转换
当有多个subpass时,需要控制subpass的执行顺序,让后面的subpass可以读取到输入attachment的渲染结果。
对于image使用前,需要根据用途转换到相应的Layout,例如:纹理用作Frame Buffer时Layout需要是COLOR_ATTACHMENT_OPTIMAL,纹理用作着色器采样时Layout需要是SHADER_READ_ONLY_OPTIMAL。
控制执行顺序和转换Layout需要通过SubpassDependency完成。
首先每个attachment有initialLayout和finalLayout,当前执行第一个subpass时,Vulkan会根据SubpassDependency插入一个vkCmdPipelineBarrier,这个Barrier会把Image从initialLayout转换到输出attachment设置的layout。如果没有SubpassDependency,Vulkan会帮我们生成一个默认的Barrier,这个默认的Barrier也同样会转换Layout,但是srcStageMask=TOP_OF_PIPE,这不是我们想要的,我们需要将srcStageMask=COLOR_ATTACHMENT_OUTPUT。
当最后一个subpass执行完后,Vulkan也会根据SubpassDependency插入一个vkCmdPipelineBarrier,这个Barrier会把Image从subpass的layout转换到finalLayout。如果没有SubpassDependency,Vulkan会帮我们生成一个默认的Barrier,这个默认的Barrier也同样会转换Layout,dstStageMask=BOTTOM_OF_PIPE。如果attachment的Image是来自SwapChain,渲染后是用于显示,这个默认生成的Barrier刚好是我们想要的,因为显示是通过semaphore同步的。如果不是来自SwapChain则需手动提供一个SubpassDependency。
在各个subpass之间我们也需要提供SubpassDependency,用来控制subpass的执行顺序,以及将输入attachment的Image Layout从COLOR_ATTACHMENT_OPTIMAL转换为SHADER_READ_ONLY_OPTIMAL。
Dynamic rendering
因为创建FrameBuffer和GraphicsPipeline时需要提供一个RenderPass对象,这导致RenderPass一旦有修改就需要重新创FrameBuffer和Pipeline。而且创建GraphicsPipeline时还需要设置一个subpass索引,使得Pipeline不能在不同的subpass之间复用。Vlkan的这种设计导致RenderPass和FrameBuffer、GraphicsPipeline之间的耦合太高,FrameBuffer和GraphicsPipeline不能复用,一点都不灵活。
在Vulkan 1.2版本中提供了一个扩展VK_KHR_dynamic_rendering,这个扩展用于支持Dynamic Rendering 可以渲染时不用创建Render Pass和FrameBuffer,让渲染更加灵活,可以更方便的复用Pipeline和代码。这个扩展不是强制要求实现的,所以并不是所有支持Vulkan1.2版本的显卡都支持。
虽然Dynamic Rendering可以不用创建RenderPass和FrameBuffer,但RenderPass做的事依然不能少,需要手动添加imageMemoryBarrier用于控制pass的执行顺序和Layout的转换。具体实现可以参考官方的例子:Dynamic Rendering。
参考
https://github.com/SaschaWillems/Vulkan/tree/master/examples/inputattachments
https://github.com/SaschaWillems/Vulkan/blob/master/examples/dynamicrendering
Yet another blog explaining Vulkan synchronization – Maister's Graphics Adventures