由于本人才疏学浅,翻译难免有误,望各位不吝惜指正。
感谢两位作者,今天为大家分享使用Vulkan的renderpass加速移动端延迟渲染。
内容
- multipass是什么?
- multipass可以做什么?
- 驱动可以做什么
- 开发者可以做什么
- 临时图像和LAZILY内存分配
- 案例
- Baseline
- Sponza
- Lofoten
multipass是什么?
- 一个renderpass可以有多个subpass
- subpass之间可以设定依赖关系
- render pass的关系可以用图表示
- subpass可以只使用附着的一个子集
Vulkan对延迟渲染的提升
- 经典的延迟渲染算法包含两个render pass
- G-Buffer pass,渲染4张纹理
- Lighting pass,读取G-Buffer,进行光照着色
- Lighting pass只读取了gl_FragCoord位置处的G-Buffer数据
- 考虑使用Vulkan的multipass来实现延迟渲染
- 需要两个subpass
- 需要使用的依赖
- COLOR | DEPTH -> INPUT_ATTACHMENT | COLOR | DEPTH_READ
- VK_DEPENDENCY_BY_REGION_BIT
Tile-based GPU
- Tile-based GPU会将一个render pass的所有primitive按tile打包
- 在片段处理阶段,一次渲染一个tile
- 硬件具有覆盖这一tile的所有primitive信息
- framebuffer的数据在更快速,但也更小的SRAM中
- 让framebuffer数据保持在on-chip SRAM好处很大
- 读写这一区域的代价极小,不占用外部带宽
- 当一个tile渲染完后,才会被写入主存
针对Tile-based GPU的subpass融合
- subpass在渲染前已经确定
- VkRenderPass
- 驱动会查找具有下列属性的subpass
- BY_REGION依赖
- 没有影响subpass融合的外部因素
- 融合G-Buffer pass和Lighting pass
- 将对G-Buffer和Light的draw call放进同一个renderpass
- G-Buffer的内容会被保持在on-chip SRAM
- 在Lighting pass读取G-Buffer数据只读取了tile的buffer
- vkCmdNextSubpass实质上变成了一个空操作
Vulkan GLSL subpassLoad()
- 读取input附着需要特别处理
- SPIR-V中的特殊image类型
- 调用vkCreateGraphicsPipelines,需要下面这些信息
- render pass
- subpass index
- subpassLoad()会变成
- texelFetch(),如果subpass没有被融合
- 这也是我们需要特殊的VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT的原因
- magicReadFromTilebuffer(),如果subpass被融合
- texelFetch(),如果subpass没有被融合
- shader编译器可以在创建管线时进行预处理
- 避免在最后一刻为shader打补丁带来的不必要的性能损耗
临时附着
- Lighting pass后,G-Buffer中的数据不再需要
- G-Buffer中的数据实际上只需要保持在on-chip SRAM
- render pass开始时的清除操作,不需要读取主存
- storeOp设置为DONT_CARE,不会将G-Buffer写到主存
- Vulkan可以设定分配的内存类型和用途
- imageUsage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT
- memoryProperty = VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT
- 在tile-based GPU上,不会将这类数据写回主存
multipass的优点
- 统一了移动和桌面平台的延迟渲染设置
- 相同的Vulkan代码
- 相同的shader代码
- VkRenderPass包含的信息有利于:
- 桌面平台可以更好地进行调度处理
- 桌面平台地GPU有向tile-based转变地趋势
- 最差情况下,它就是经典的MRT
- G-Buffer的布局可能会进行微调
Baseline测试
- 基本的multipass采样
- 一个renderpass
- 几何体打光
- 8个光源
- 简易的着色
- 基准指标
- multipass(subpass融合)
- MRT
- 整体性能表现
- 带宽
Baseline测试数据
- 测试在Galaxy S7 (Exynos)上进行
- 4096x2048分辨率
- 垂直同步与1440p相同
- ~30%的fps提升
- ~80%的带宽减少
- 只使用了albedo和normal
- 节约了对于移动平台GPU至关重要的带宽
Sponza测试
- 对于移动端来说的中高复杂度的模型
- 每帧~200k三角形绘制
- ~610个点光源
- 完整的PBR
- 对部分光源生成阴影贴图
- 8个反射探头
- 方向光和阴影
- 完整的HDR管线
- 自适应tonemapping
- Bloom
Clustered stencil culling
- 经典的light stencil culling算法包含大量切换状态的操作
- 即使使用Vulkan仍然代价非常大
- 在GPU上表现不佳
- 将局部光源沿相机Z轴进行划分
- 每个光源按depth被归属于一个cluster
- 在一个pass为所有局部光源写入stencil信息
- 使用stencil额外的2位depth buffer进行depth peel
- 使用double sided depth test渲染光源
Sponza测试数据
- 远远超过移动平台能力的数据量
- 1440p
- 过度的压力
- ~50%-60%带宽减少
- ~18%帧率提升
Lofoten
- 包含~2.5百万个图元的城市场景
- 重度依赖CPU端的场景剔除来减少负载
- 100个聚光灯和点光源以及它们的阴影贴图
- 反射探针
- 太阳光以及它的CSM
- 大气散射
- 海水模拟
- GPU计算的FFT
- 独立的折射pass
- 屏幕空间反射
- Bloom和自适应色调后处理
实现细节
- G-buffer pass
- 对于tile-based GPU来说,填充速率不等于带宽
- 光照材质信息直接写入light buffer
- 使用模板标记反射影响范围
- 用于clustered stencil culling的depth peeling
- Lighting pass
- 使用混合添加光照
- 用于局部光源的Clustered stencil culling
- 透明物体
- 着色后的雾处理
- 只需要提交light buffer到内存
Render pass
- 高层接口显式定义render pass
- OpenGL等也可以作为后端实现
- BeginRenderPass()
- NextSubPass()
- EndRenderPass()
整合临时资源
- 添加虚拟附着支持
- 建立以TRANSIENT_ATTACHMENT_BIT标记分配的图像资源池
- 实际的图像处理对API用户不可见
Lofoten测试数据
- 比Sponza更大的数据量
- 1440p
- 过度的压力
- ~50%带宽节约
- ~12%帧率提高
- ~25%GPU资源节约
tile-based GPU的性能考虑
- 因为on-chip SRAM的存在,每个像素的buffer大小有限制
- G-Buffer大小也因此受限
- 对于目前的Mali硬件,每个像素可以有128位空间大小
- 可能会因GPU的不同而有所不同
- 较小的tile可以使用更大的G-Buffer
- 但会有较大的性能损失
- 更少的活动线程,着色器核心使用较少
- 需要更多次地扫描tile列表
引擎对多种API的整合
- 引擎需要有render pass的概念
- 否则无法表示多个subpass
- subpassLoad()是Vulkan GLSL特有的
- 方案1,将Vulkan GLSL作为主要的着色语言
- SPIRV-Cross可以将subpassLoad()调用映射到MRT风格的texelFetch
- 方案2,使用HLSL或者类似着色语言
- 定义自己的指令,引发Vulkan执行subpassLoad())
- 方案1,将Vulkan GLSL作为主要的着色语言
- 将multipass展开来满足其它API的要求
- 在NextSubpass()改变渲染目标
- 将subpass使用的input附着绑定到纹理单元
- 在shader中静态地映射input_attachment_index到纹理单元
处理图像布局
- G-Buffer图像的使用是临时的
- 不需要知道使用什么布局
- 程序不应该直接访问G-Buffer图像
- 可以使用VK_SUBPASS_EXTERNAL作为subpass依赖
最后放个图