Vulkan中VkRenderPass、VkSubpass、VkSubpassDependency的概念和理解,以及多subpass下的指令记录

VkRenderPass是VulkanAPI中描述渲染流程的对象,包括附件布局变化、同步操作和子通道关系。它定义了颜色和深度/模板附件,以及它们在渲染开始和结束时的布局。子通道引用允许灵活控制附件在不同阶段的使用布局。子通道依赖用于同步资源访问和布局转换,确保渲染过程的正确性和性能。在命令缓冲中,通过vkCmdBeginRenderPass等命令记录渲染指令,实现多子通道的渲染操作。
摘要由CSDN通过智能技术生成

VkRenderPass 是一个描述性的对象,它定义了在渲染过程中附件的布局变化、同步操作、以及一系列子流程之间的关系。

VkRenderPass 的创建是在描述这些信息,而真正的命令记录则发生在命令缓冲构建的过程中。

1、附件(颜色附件和深度/模板缓冲附件)

当你创建附件时,它的描述结构体中并没有指定该附件是颜色附件还是深度附件,仅仅是描述了这块内存的像素格式等信息

对于附件描述符内的 initialLayoutfinalLayout 指定的是该附件在Renderpass之前和之后的布局,这个布局会在渲染过程中发生改变(subpass可能会改变布局),不过最终布局还是会变成 finalLayout 指定的那样

图像布局:不同的布局会让对应的操作变得高效,这涉及到GPU的访问控制以及内存中的该数据的排布方式。

void setupRenderPass()
{
	// 两个附件的描述结构体
	std::array<VkAttachmentDescription, 2> attachments = {};

	attachments[0].format = VK_FORMAT_B8G8R8A8_UNORM;                
	attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;                  // 不使用多重采样
	attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;             // 在渲染通道开始时清除此附件
	attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;           // 在渲染通道结束后保留其内容(以供显示)
	attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;  // 不使用模板,所以不需要加载
	attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;// 同上
	attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;         // 渲染通道开始时的布局。初始布局并不重要,所以我们使用未定义的布局
	attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;     // 渲染通道结束时转换到的布局,由于我们想将颜色缓冲区呈现到交换链,因此我们转换为PRESENT_KHR布局

	attachments[1].format = VK_FORMAT_D32_SFLOAT_S8_UINT;                                           
	attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
	attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;                           
	attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;                     
	attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;                // 没使用模板
	attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;              
	attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;                     
	attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 

2、附件引用

附件引用,就是附件的引用,每个subpass可以指定的流程中需要用到的附件是哪些,以及使用该附件时,该附件的布局方式应该变为什么(layout字段指定)。

通过在VkAttachmentReference中为每个附件指定layout字段,可以灵活地控制附件在子流程中的使用布局,而不仅仅受限于初始布局。这种灵活性有助于优化渲染流程,提高性能。

这里就能够看出为啥要用引用而不是直接使用附件了,灵活

	// Setup attachment references
	VkAttachmentReference colorReference = {};
	colorReference.attachment = 0;                                    
	colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 

	VkAttachmentReference depthReference = {};
	depthReference.attachment = 1;                                            
	depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 

	// 如果还有subpass2,它需要的颜色附件布局类型是不同的,则应该重新创建颜色附件的引用
	VkAttachmentReference colorReferenceForPass2 = {};
	colorReference.attachment = 0;                                    
	colorReference.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // shader只读
	// 如果还有subpass3,它的需求跟subpass1一样,那就直接重用上面的colorReference 即可

3、子通道

子通道的处理过程中,附件引用的布局在创建引用的时候就指定过了

VkSubpassDescription subpassDescription = {};
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDescription.colorAttachmentCount = 1;                            
subpassDescription.pColorAttachments = &colorReference;      
subpassDescription.pDepthStencilAttachment = &depthReference;

4、子通道依赖

子流程依赖VkSubpassDependency,主要用于描述在渲染流程的不同子流程之间的同步布局转换关系。通过显式定义子流程依赖关系,可以确保在渲染过程中的不同阶段之间正确同步资源的访问,并在需要时执行布局转换。这有助于优化渲染流程的性能和正确性。

  • 同步资源访问: 确保在不同的子流程中正确同步对共享资源(例如图像附件)的读写操作,避免数据竞争和不一致性。

  • 布局转换: 在子流程之间可能需要进行图像布局的转换,以满足不同操作阶段的需求。显式指定依赖关系可以确保在子流程之间进行正确的布局转换。

  • 提高性能: 显式指定的依赖关系可以帮助渲染引擎更好地理解渲染流程的执行顺序,从而更有效地进行优化

对于依赖关系中的内存读取和写入,通常是指对于GPU某个资源(比如图像或缓冲区)的读取和写入,不是主机内存

每个子通道对应的附件布局会根据需要进行布局转换,通过子通道依赖进行定义,比如将颜色附件布局从 COLOR_ATTACHMENT_OPTIMAL 转换为 TRANSFER_SRC_OPTIMAL 以提高图像数据拷贝的操作执行速度。

本例中,在只有一个子流程的情况下,通常不需要显式定义任何依赖关系。渲染流程会有两个默认的隐式依赖关系 ,分别处理渲染流程开始时的布局转换(从initialLayout转换成subpass的附件引用指定的布局)和结束时的布局转换(从subpass附件引用指定的layout转换成finalLayout)。

注意:在多个子流程的情况下,仍然存在这两个默认的隐式依赖关系。
子通道开始前和结束后的所有隐式的操作 可以分别看做一个subpass

  • 开始时的依赖 (VK_SUBPASS_EXTERNAL 到第一个子流程): 这个依赖关系会确保在渲染流程开始时,图像的布局会正确地转换到第一个子流程所期望的布局。这是一个隐含的依赖,通常不需要显式定义。
  • 结束时的依赖 (最后一个子流程到 VK_SUBPASS_EXTERNAL): 这个依赖关系确保在渲染流程结束时,图像的布局会正确地转换回到适合交换链呈现的布局。这同样是一个隐含的依赖,通常也不需要显式定义。

VK_SUBPASS_EXTERNAL 是一个特殊的常数,它代表所有在实际渲染通道之外执行的命令

如果显示定义依赖的话,则大概是这样:

VkSubpassDependency dependencyBegin = {};
dependencyBegin.srcSubpass = VK_SUBPASS_EXTERNAL;
dependencyBegin.dstSubpass = 0;
dependencyBegin.srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencyBegin.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencyBegin.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencyBegin.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencyBegin.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;

VkSubpassDependency dependencyEnd = {};
dependencyEnd.srcSubpass = 0;
dependencyEnd.dstSubpass = VK_SUBPASS_EXTERNAL;
dependencyEnd.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependencyEnd.dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
dependencyEnd.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
dependencyEnd.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
dependencyEnd.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;

这些 MaskFlags 在 Vulkan 中用于描述同步和依赖关系的细节。

字段解释:

  • srcSubpass :源子通道,被依赖者
  • dstSubpass :目标子通道,依赖者,也可理解为在这里做同步
  • srcStageMask:源子通道的阶段标志,即被依赖的或者说是同步的操作在哪个管线阶段,VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT表示管线底部,即读取附件内容呈现到表面
  • dstStageMask : 目标子通道的阶段标志,即这个子通道的哪个管线阶段需要指明依赖关系?谁需要同步操作?VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;表示附件写入阶段(输出内容到附件上)
  • srcAccessMask : 源子通道的操作标志,即这个阶段GPU在干啥,VK_ACCESS_MEMORY_READ_BIT;表示在读取内存呢
  • dstAccessMask : 目标子通道的操作标志,VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT表示这个阶段我要写入操作,肯定依赖于上一个阶段对这里的读取已经完毕了才行啊,同步同步

5、创建Render pass

	// Create the actual renderpass
	VkRenderPassCreateInfo renderPassInfo = {};
	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
	renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());  // renderpass附件数
	renderPassInfo.pAttachments = attachments.data();                            
	renderPassInfo.subpassCount = 1;                                             // subpass个数
	renderPassInfo.pSubpasses = &subpassDescription;                             
	renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size()); // subpass dependencies个数
	renderPassInfo.pDependencies = dependencies.data();                          

	VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
}

6、多subpass情况下的渲染指令记录

在命令缓冲构建的过程中,通过使用 vkCmdBeginRenderPassvkCmdNextSubpassvkCmdEndRenderPass 记录 Vulkan 命令,指示 Vulkan API 在 GPU 上执行渲染操作。这些命令会在每个子流程之间进行切换,执行渲染操作,并保证正确的同步和布局变化。

假设有4个subpass,那么在命令缓冲构建的过程中,你需要在每个子流程之间切换,并在每个子流程中执行相应的渲染命令。以下是一个简化的示例:

// 开始renderpass
vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);

// 第一个子流程
// ...

// 切换到下一个子流程
vkCmdNextSubpass(commandBuffer, VK_SUBPASS_CONTENTS_INLINE);

// 第二个子流程
// ...

// 切换到下一个子流程
vkCmdNextSubpass(commandBuffer, VK_SUBPASS_CONTENTS_INLINE);

// 第三个子流程
// ...

// 切换到下一个子流程
vkCmdNextSubpass(commandBuffer, VK_SUBPASS_CONTENTS_INLINE);

// 第四个子流程
// ...

// 结束Renderpass
vkCmdEndRenderPass(commandBuffer);

在上述示例中,通过使用 vkCmdNextSubpass 命令在每个子流程之间切换。在每个子流程中,可以执行相应的渲染命令,比如绘制图元、设置管线状态等。切换子流程的目的是确保 Vulkan 在 GPU 上以正确的顺序和同步执行渲染操作。

通过记录这些命令,实际上描述了在 VkRenderPass 中定义的渲染过程的执行步骤。这种分离设计允许 Vulkan 提供更大的灵活性,允许开发者在渲染过程中的每个子流程中执行自定义的命令序列。

Vulkan被称为OpenGL的接班人,性能果然是霸气外漏,更能够承载下一个时代的图形渲染编程。 GPU高性能渲染的课题进入了一个新的阶段,对于计算细节的控制,多核CPU多线程渲染以及高性能算法的灵活设计需求日益旺盛。图形程序员需要有更加强力且灵活的工具,来“解锁”我们自身的控制能力,OpenGL的较高度封装性以及单纯的状态机模式显然已经无法适应现代化图形渲染的强烈需求。为什么要学习Vulkan?正如前言所说,Vulkan已经成为了下一个时代的图形渲染主流API,早已经被各大商业引擎(Unreal Engine、Unity3D)所支持。那么我们的同学就有如下问题需要明晰:1 作为游戏程序员我们只学会了UE或者Unity3D,那么就只能作为一个普通的程序员,如果能够结合Vulkan的学习对商用引擎理解更加深刻,就可以更好的发挥引擎威力甚至更改引擎的源代码,实现更多的可能,让你在各大公司之间都“蛇形走位,游刃有余” 2 作为自研引擎工作人员,你可能在工业软件领域从业、也有可能在影视渲染领域从业、也可能在其他的图形系统领域(军工、GIS、BIM)等领域,那么熟练的掌握Vulkan就可以针对自己公司的不同领域需求进行不同的引擎定制开发,从而获得牢不可破的地位,对于自身职业发展有着极大的优势! 总而言之,越早学习Vulkan,就越能与别人拉开差距,让Vulkan称为你升职加薪、壮大不可替代性的核武器! 课程简介:本课程详细讲解了Vulkan从小白到入门的基础理论+实践知识,对于每一个知识点都会带领学员通过代码来实现功能。其涵盖了计算机图形学基础理论,计算机图形学数学推导,Vulkan基础系统设计理论,基础单元(实例,设备,交换链), 渲染管线,RenderPass指令与多线程, 顶点描述与实验, Uniform与描述符, 图像与采样, 深度与反走样,模型与摄像机等内容;课程会对Vulkan复杂抽象的API进行一次包装层的封装,将相关的API都进行聚合与接口设计,作为游戏或者图形引擎来讲,这是至关重要的第一步。这一个封装步骤,也被称为API-Wrapper,经过包装后的类库,同学可以在此之上根据自己的具体需求进行扩展,从而得到最适合自己的类库内容。本课程为系列化课程,在铸造基石篇章之后,会继续使用本包装类库进行改良,并且实现Vulkan API下的各类效果以及高级特性的开发教学。 课程优势:1 本课程会从计算机图形学的基础渲染管线原理出发,带领0基础的同学对计算机图形学进行快速认知,且对必要的知识点进行筛选提炼,去掉冗余繁杂的教学内容,更加适合新手对Vulkan渲染体系入门了解。 2 本课程会对计算机图形学所涉及的数学知识及如何应用到渲染,进行深入的讲解,带领同学对每一行公式展开认识,从三维世界如何映射到二维的屏幕,在学习完毕后会有清晰的知识体系 3 本课程会带领同学认知每一个Vulkan的API,并且在代码当插入详细的注释,同学们在学习的时候就可以参照源代码进行一系列尝试以及学后复习 4 本课程所设计的包装层,会带领同学一行一行代码实现,现场进行Debug,对于Vulkan常出现的一些问题进行深入探讨与现场纠正  学习所得:1 同学们在学习后可以完全了解从三维世界的抽象物体,如何一步步渲染称为一个屏幕上的像素点。2 同学们在学习后可以完全掌握基础的Vulkan图形API,并且了解Vulkan繁多的对象之间相互的联系,从而可以设计更好的图形程序3 同学们在跟随课程进行代码编写后,可以获得一个轻量级的Vulkan底层API封装库(Wrapper),从而可以在此之上封装上层的应用,得到自己的迷你Vulkan图形渲染引擎当然,在达到如上三点之后,如果可以更进一步学习Vulkan的进阶课程,同学们可以获得更好的职业发展,升职加薪之路会更加清晰,成为公司不可替代的强力工程师 本课程含有全套源代码,同学购买后,可以在课程附件当下载 完全不懂图形学可以学习么?使用层面上来讲是没有问题的,老师在每个api讲解的时候,都会仔细分析api背后的原理,所以可以跟随下来的话,能够编程与原理相融,学会使用 数学不好可以学习么?学习图形类课程,最好能够入门级别的线性代数,具体说就是: 1 向量操作 2 矩阵乘法 3 矩阵的逆、转置 这几个点就足够 学习后对就业面试有什么作用?目前类似Vulkan渲染知识是一切引擎的基础,只要能够跟随每一节课写代码做下来,游戏公司、工业软件公司等都是非常容易进去的,因为原理层面已经通晓,面试就会特别有优势。同学可以在简历上写熟悉VulkanAPI并且有代码经验,对于建立筛选以及面试都会有很大的帮助,对于薪资也会有大幅度提升
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宗浩多捞

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值