Vulkan进阶-输入附件和Subpass以及One Pass Defer技术

原文链接:https://zhuanlan.zhihu.com/p/648162775

        inputattachment和subpass是vulkan中比较有特色的模块,inputattachment可以用于管线的描述符中用来表示缓冲区的图像视图,而subpass讲的是renderpass中的子渲染流程,切换subpass会改变渲染管线但是不会切换渲染缓冲区(framebuffer)。

        inputattachment可以被用来按像素点读取framebuffer中的内容,也就在一个renderpass内当执行到某个subpass的Fragment阶段,可以读取前一个subpass在与当前着色像素相同位置的前一个subpass的颜色值,如下图所示,第二个subpass渲染了UI,并叠加到了前一个subpass的颜色缓冲上。

编程实现

设置描述符

vulkan中提供了相应的描述符来描述输入附件,对应的在描述符集合中也要设置好描述符的类型和对应的imageInfo。

std::array<VkDescriptorImageInfo, 2> descriptors{};
descriptors[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
descriptors[0].imageView = attachments[i].color.view;
descriptors[0].sampler = VK_NULL_HANDLE;

std::array<VkWriteDescriptorSet, 3> writeDescriptorSets{};
writeDescriptorSets[0].dstSet = descriptorSets.attachmentRead[i];
writeDescriptorSets[0].descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
writeDescriptorSets[0].descriptorCount = 1;
writeDescriptorSets[0].dstBinding = 0;
writeDescriptorSets[0].pImageInfo = &descriptors[0];

同时要注意的是创建图片的时候需要设置使用为输入附件,如下:

imageCI.usage = usage | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;

shader读取

layout (input_attachment_index = 0, set = 0, binding = 0) uniform highp subpassInput scene_input_color;
layout (input_attachment_index = 1, set = 0, binding = 1) uniform highp subpassInput ui_output_color;

layout (location = 0) out highp vec4 color;

void main()
{
    highp vec4 scene_color = subpassLoad(scene_input_color);
    highp vec4 ui_color = subpassLoad(ui_output_color);

    highp float blend_weight = ui_color.a;
    color = mix(scene_color, ui_color, blend_weight);
}

在shader中按照描述符集合中绑定的下标声明subpassInput,然后使用subpassLoad读取,这里并不需要像sampler那样输入坐标采样颜色,因为input attachment和当前操作的缓冲区在同一个renderpass的framebuffer里边,可以按对应位置的像素读取。

如上述片段着色器代码所示,后处理之后的颜色缓冲是scene_input_color,UI渲染结果保存在ui_output_color,使用subpassLoad逐像素点读取颜色,并根据ui_color的透明度混合。

Subpass作用机制深入研究

        如果只需要按图像像素对其读取input attachment,并不一定要设计subpass这一机制,比如OpenGL ES中有Framebuffer Fetch就可以按像素对其读取framebuffer,这样同样可以避免采样的开销。但是为什么要使用subpass而不是使用多个renderpass,然后通过Framebuffer Fetch读取呢?

        原因在于使用subpass设计渲染管线可以利用上Tile-Based GPU架构来节约内存带宽,同一个renderpass中的subpass之间,framebuffer是完全一样的,区别在于graphic pipeline和render command不一样。站在GPU的视角来说,对于每个subpasse而言,只需要分别执行一次vertex shader后,将生成的triangle list存入主存,然后再执行fragment shader时,先对framebuffer切Tile,然后逐个Tile执行所有的subpass,用伪代码描述如下:

for(auto &tile in tiles)
{
	for(auto &subpass in subpasses)
	{
		FragmentShaders[subpass](tile);
	}
}

        这样的好处在于,执行FragmentShader的时候,可以将Tile的Framebuffer数据存储在In-Chip Cache上面,只有当这个Tile所有的subpass都执行完了之后,才会把渲染结果写回dram,这样subpass对于framebuffer的读写带宽开销可以省略很多,有效的利用了tile-based架构节约内存带宽。特别是对于延迟渲染管线而言,将gbufferpass和lightpass以subpass的形式放在同一个renderpass中执行,可以实现one pass defer,能够让Gbuffer完全只存在于片上缓存中,极大地节省了系统带宽,如下图:

传统的延迟渲染管线

但是使用inputattachment也会导致一些问题,因为要提高subpass执行的效率,输入附件被设计为了只能按照对应像素点读取,而不能读取其他位置的,那么在一些后处理操作中,比如bloom特效就无法使用这一技术,因为要对像素点每个邻域内亮度做一次高斯滤波,只能用多个renderpass进行迭代,或者干脆用Compute Shader搞定。

One Pass Defer

        但是使用inputattachment也会导致一些问题,因为要提高subpass执行的效率,输入附件被设计为了只能按照对应像素点读取,而不能读取其他位置的,那么在一些后处理操作中,比如bloom特效就无法使用这一技术,因为要对像素点每个邻域内亮度做一次高斯滤波,只能用多个renderpass进行迭代,或者干脆用Compute Shader搞定。

 

参考资料:

Vulkan input attachments and sub passes

https://stackoverflow.com/questions/43632903/why-do-input-attachments-need-a-descriptor-set-to-be-bound

https://community.khronos.org/t/specialization-constant-for-input-attachment-index/108503

本文使用  Zhihu On VSCode 创作并发布
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值