0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Vulkan在移动端渲染中的带宽与同步详解

冬至子 来源:内核工匠 作者:ningfeili 2023-12-01 16:45 次阅读

1.移动端GPU架构

Immediate Mode Rendering Architecture(IMR)

image.png

早先PC端的GPU大多数采用的是IMR的架构。IMR架构的大体流程如图所示,值得注意的是对于每个像素,经过数次的color/depth的读写,最终绘制到framebuffer上。这当中的每一次读写,都是直接与内存交互。所以,频繁读写内存,会消耗大量的带宽,这个过程会大量发热。对PC来说,为了更高的画质和帧率,可以用更好的风扇和散热从外部解决发热问题,但是对于空间十分有限的手机来说,肯定不可能去加装散热了。

Tile-Based Rendering Architecture(TBR)

所以为了解决带宽引起的发热问题,现在的手机GPU普遍使用的是tiled-based架构,Adreno/Mali/PowerVR的GPU在这个基础上有自家独有的优化,比如PowerVR的TBDR架构中的HSR,可以完全消灭不透明物体渲染时的overdraw,这里就不展开讨论了。

不过它们的核心思想是一致的,都是为GPU开辟了一块on-chip memory,这块on-chip memory的特点就是相比于与内存的交互,GPU读写它的开销非常小。

Tile-based rendering将一个完整的framebuffer分为若干个tile,每个tile的内容完全绘制完毕之前,GPU只读写on-chip memory,一块tile完全渲染完毕之后才会将on-chip memory中的内容一次性写入内存,下图描述了整体的绘制流程。

image.png

这样,原本绘制每个像素都需要直接读写内存,现在改为全部读写开销极低的on-chip memory,从而节约了带宽,降低了功耗。

2.为什么用Vulkan?

这也是在移动端渲染开发必然要考虑的问题,要求引擎技术团队对自己的项需求和Vulkan API,以及相关手机硬件特性都有非常清晰的认知。Vulkan API主要有以下几点特殊的优势:

  1. 它的驱动更薄。Vulkan更贴近底层,驱动不会像GLES一样,进行大量的猜测和判断,所以用得好可以显著降低CPU的负载;
  2. Vulkan提供显式的同步和带宽控制指令,有效而正确地使用这些指令,可以提升GPU运行效率和降低GPU功耗。
  3. Vulkan 刻录并提交commandbuffer的架构,更好地支持了多线程渲染。例如Unity PC端实现了用secondary commandbuffer多线程同时渲染同一场景中的物体,UE也利用Vulkan的特性,从渲染线程中抽离出了RHI线程,平均了多核负载,提升了多核效率。
  4. 因为Vulkan标准较新,且受到重视,所以在Vulkan标准定制时,直接包含了很多最新的硬件特性的使用方法。

所以在合理调度的情况下,Vulkan可以享受驱动薄和多线程带来的CPU收益。而GPU端,在充分了解硬件架构和各个驱动的特点的情况下,手动控制同步和带宽可以有效提升游戏性能。但是,往往一些项目花了非常多的时间和精力去优化渲染算法,改善渲染流程,或者添加自定义渲染管线,可仅仅是因为对某个硬件特性的不了解,或者用错了某个API,导致性能下降非常严重或者导致优化了的算法反而是负提升,甚至包括早期的Unity、Unreal默认移动端的渲染管线,在这块都或多或少有些不足。

3.游戏中的带宽与功耗

先看一个非常简单的例子。

image.png

上图是用Unity的rendering commandbuffer实现的一个自定义后处理的渲染流程。当切换rendertarget的时候,如果我们只是使用了默认的接口去设置RT,传递到底层,可以看到Vulkan renderpass的load action是load,也就是保留前面的渲染结果。

而当我们使用下面这个接口显示地设定了rendertarget的loadaction,使Vulkan的load action变为dontcare,可以看到GPU bound的情况下,帧率提升了1帧多。

image.png

这就是因为,当我们load这个rendertarget的时候,在Tile-base架构上,rendertarget从内存中被加载到on-chip memory上,所以产生了额外的开销。而如果我们显式地设置了dontcare,就减少了这次加载的时间和带宽,在GPU bound的情况下,性能的提升直接体现在了帧率上。

Tile-based Rendering在延迟渲染中的应用

image.png

传统延迟渲染的方式,Gbuffer渲染完毕后,写入内存,然后lighting阶段再sample gbuffers。而因为在lighting阶段每个像素需要的是自己像素位置上的gbuffer信息,所以我们完全可以利用subpass,将gbuffer存放在on-chip memory中,后续lighting阶段直接从on-chip memory里去读取。省去了先存内存,再sample的两次带宽消耗。

下面是一个延迟渲染的demo,左边是sample的方式,右边是则是subpass的方式:

image.png

两种情况下,lighting的算法,分辨率,灯的数量,fps包括GPU频率都是固定不变的。唯一的区别就是是否与内存交互,左边是采样内存中存下来的Gbuffer;右边则完全不与内存交互,直接写入和读取on-chip memory。

image.png

测试结果可以看到,shader的计算量是一样的,但是内存频率和带宽都是subpass方案有很大提升,这里读写带宽一共减少了4.9Gb/s,内存频率也有所降低。

image.png

在帧率、GPU使用率和频率均没有差别的情况下,纯因为带宽的减少,产生了567mW的功率差,GPU平均温度也降低了5度。

image.png

这里有一个大致的参考,内存带宽所产生的功耗几乎与GPU的功耗持平,,每消耗1GB的内存带宽,大约会产生120mW的功耗,这也和之前5GB,567mW的测试结果吻合。

不仅仅是延迟渲染,只要渲染流程中想读取当前像素位置的历史颜色、深度,都可以用subpass。比如decal,某些粒子效果的半透明渲染、MSAA抗锯齿等等,合理利用subpass,在带宽上能得到可观的收益。

当然,tile-based架构还是有一些缺点的:

比如tiling阶段需要将vs全部计算完毕,并且所有的varying要回存内存,所以相比于IMR,除了共有的fetch geometry data的开销,Tiling阶段也有额外的延迟和带宽消耗。

image.png

所以vertex data的组织也是非常重要的。比如下图这个自定义的vertex data,aColor将最终传给fragment shader用作自定义的渲染,

image.png

注意到每个数据都是有效数字只有1位的整数,却用了32位的格式去存储。在模型复杂,顶点较多的情况下,这也是一笔不小的开销。实际上16位,甚至8位的格式就够了。所以在知晓自己项目每个顶点数据的用途的情况下,我们应当使用尽量小的格式。

Index-Driven Vertex Shading(IDVS)

下图是ARM上为了减少fetch geometry data的带宽所做的优化,严格意义上它不能算是专门为TBR架构而设计的优化,但它的收益确实很大。

image.png

IDVS的思路就是vs中只先做position相关的计算,等做完剔除之后,才会去fetch其他attribute的数据,做varying相关的计算。但是这个优化要求开发者将position和其他的attribute分buffer存放,这样GPU才能单独fetch。

现在市面上绝大多数的手游都是左边的这种情况,所有vertex data存在了同一个vkbuffer中;右边则是推荐的做法,一个vkbuffer仅存position data,剩下的varying存在另一个vkbuffer中,在某些vs较重的情况下,两者的功耗差距超过了30%。所以如果顶点数据复杂,我们还是值得去拆分一下vertex data的。

4.Vulkan中的同步

Vulkan同步中一个非常重要的元素就是pipeline barriers,只要有项目修改了引擎的原生管线,就一定会涉及到pipeline barrier的修改,而实际看下来几乎所有的项目都有一些使用不恰当的地方。并且,如果项目开发周期比较长,使用的是较早版本Unity、Unreal引擎,那么原生代码也或多或少有一些使用不当的地方。

以下三点都是Vulkan spec上面提到的pipeline barrier的作用。

第一,它可以控制执行顺序,GPU真正执行指令时,并不一定是按照我们提交指令的顺序去执行的,所以在指令之间添加一个pipeline barrier,可以保证barrier之前的指令先于barrier之后的指令执行。

只保证指令开始的顺序,在并行的情况下,并不能控制指令结束的顺序,牵涉到内存修改的情况下就会出现问题。

第二,pipeline barrier还保证了指令间内存的依赖关系,这个后面会详细解读。

第三点image的layout转换其实同样重要,有兴趣的读者可以查阅相关资料

Hazards

因为实际情况要复杂得多,这里我们把读写模型简化成GPU core - cache–内存三个部分:

image.png

写数据时,GPU core完成对cache的修改后要flush cache,将其中的数据拷贝到内存中,这个过程我们称之为make memory available。

而GPU core读取数据时,要先invalidate cache,将内存中的数据加载到cache中,这个过程,我们称之为make memory visible。

了解了这个简化模型以后,来看内存读写的三个hazards:写后读、写后写、读后写。

如果不做同步,比如说写后读,很可能读取指令执行时,前面的写命令还没来得及修改内存,从而导致渲染错误,这类情况就是需要通过同步指令去避免的。

Pipline Barrier 处理WAR、RAW、WAW的具体流程

三个问题当中最好解决的是读后写,对于它来说,只要通过pipeline barrier限制了指令执行顺序,就能保证数据读取的正确性。因为读取指令先执行,那么数据就已经被加载到了cache中,即使最坏的情况,后面的写入操作立刻完成,它修改的也仅仅是内存中的内容,无法影响到我们正在读取的已经被加载到cache中的数据。

接下来我们来看其他两个稍微复杂一点的同步。

Pipeline stage mask和access mask决定了这个pipeline barrier是对哪段内存生效。那么如果对于同一段内存进行先写后读,读取操作一定要在所有数据都写入内存之后才能进行。

image.png

所以,在pipeline barrier的保护下,整个同步流程如右上所示:

首先,读取指令会被block住,等待写指令执行完毕;srcAccessMask意味着将这段内存make available,保证data写入内存完毕。dstAccessMask意味着将这段内存make visible,也就是保证刚才被写入内存的data成功加载到cache中。

最后,执行读取指令。这样,整个内存同步就完成了。

写后写也是差不多的原理,先阻塞第二次写操作,等待第一次写操作完成,且data已经flush到了内存中,再执行后一次的写操作。

image.png

上面是一个典型的写后读的同步例子,这是一个用采样方式读取Gbuffer的延迟渲染demo,模拟是游戏中最常见的一种流程,就是先渲染一张RT,然后后续的某个fragment shader采样这个RT的结果,除了延迟渲染,常见的还有shadowmap,sss,一些实时的风场,脚印的前置流程等等。

右边是完全正确的写法,先block住lighting阶段的fs,然后把之前Gbuffer阶段color output stage的color attachment写入的这段内存make available,然后再make visible同一段要被shader读取的内存,再unblock fs,这样lighting阶段的fs就正确读取到了Gbuffer。

左边唯一的不同,是把本应等待的fragment阶段改成了vertex,这样就导致GPU会在vertex shading阶段提前block住流水线。

这个问题非常隐蔽,首先Vulkan的validation layer不会报错,因为它只是效率低,而不是一个错误。如果粗略地去测试alu,读写带宽,渲染时间,可以看到这些数据在图上的测试结果几乎没有区别。所以这个问题很容易就被忽略过去了。

测试分析工具的使用

各个Soc厂商都提供了自家GPU的分析测试工具,例如 Mali芯片的Mali streamline,PowerVR的芯片的PVRTune,高通的snapdragon profiler。在这个例子中,我们用的手机是Mali的GPU,所以用Mali的测试工具进行分析:

image.png

如上图,计算量,读写带宽,都没有差别,唯一有变化的是这个External Bus Read Latency和External Bus Stall。

image.png

具体来看,内存总线读取数据的阻塞周期有6倍的差距,这就是由于我们的同步指令让该等待的stage提早到了vertex shading阶段。但是由于GPU频率足够高,场景也不够复杂,没有达到GPU bound,这些延迟并没有高到足以影响帧率的地步。

现在,让我们人为地限制GPU频率,模拟GPU bound的情形,这时内存总线读写延迟无法被覆盖,问题就暴露出来了。

image.png

可以看到,Bus Stall对帧率的影响还是很明显的,frametime相差了3毫秒,也就是8帧如果没有进行足够深入的测试,就很容会忽略掉这个问题,随着项目向后期推进,渲染压力逐渐变大,当问题暴露出来的时候,可能已经无法找到最初的原因了。

综上,同步问题非常重要,我们要完全理解core-cache-memory这个模型,清楚地知道管线遇到的是哪种hazard,正确地使用pipeline barrier,从而充分发挥手动控制同步带来的性能收益。

其他同步指令

当然Vulkan中还有很多其他的同步指令,例如subpass dependency,和barrier的用法几乎一样,但是只能同步renderpass内attachment相关的内存。Semaphore用于队列之间的同步,fence用于同步GPU和CPU,Event现在手游上用的比较少,目前除了一两款游戏自己对引擎进行了改动,只有unreal最新的版本在mobile shading renderer的occlusion query部分使用了它。

5.总结

本文首先介绍了移动端渲染架构及其特点,接着阐述了Vulkan API的优势,再结合实际测试结果分析了Tile-based rendering的优缺点,最后重点介绍了Vulkan的显式同步控制,结合具体场景和实测数据,给出了优化方案并分析了根因。

希望读者可以通过本文更加深入地了解Vulkan API以及移动端渲染架构,结合具体的开发场景,合理利用测试分析工具,改善移动端渲染中的功耗、带宽和同步问题。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • RAW
    RAW
    +关注

    关注

    0

    文章

    20

    浏览量

    3752
  • cache技术
    +关注

    关注

    0

    文章

    41

    浏览量

    987
  • Vulkan
    +关注

    关注

    0

    文章

    28

    浏览量

    5646
  • GPU芯片
    +关注

    关注

    1

    文章

    292

    浏览量

    5692
收藏 人收藏

    评论

    相关推荐

    [8.1.1]--Vulkan介绍_clip001

    Vulkan
    jf_75936199
    发布于 :2023年02月23日 20:39:40

    [8.1.1]--Vulkan介绍_clip002

    Vulkan
    jf_75936199
    发布于 :2023年02月23日 20:40:22

    不能在表面4窗口10上运行vulkan

    还有另一个vulkan-1-999-0-0-0.dll,版本相同,另一个同名的exe也返回上面的结果。我刚下载了适用于Windows *的下载英特尔®系统支持实用程序。我附加了完整的输出
    发表于 11-12 11:51

    Vulkan同步机制和图形转换的风险

    Vulkan同步机制和图形-计算-图形转换的风险(一)
    发表于 01-21 06:17

    Vulkan如何使用barrier

      本篇文章,我们将提到Vulkan 图形处理过程夹杂计算任务时遇到的各式问题。为更准确地了解我们的话题,可查看文章第一部分。  第一部分概述了
    发表于 01-29 06:55

    一文详解渲染管线

    渲染管线简单梳理
    发表于 02-03 07:13

    Vulkan的基本类型

    Vulkan基本类型
    发表于 02-19 07:08

    ARM服务器准备如何解决服务渲染的问题

    针对云手机、视频流云游戏行业,ARM服务器准备如何解决服务渲染的问题?目前的状况了解,PCIE显卡对安卓游戏的支持还不够成熟
    发表于 09-13 14:58

    HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制if/else条件渲染

    ; { this.count--; }) } } } if语句的每个分支都包含一个构建函数。此类构建函数必须创建一个或多个子组件。初始渲染时,if语句会执行构建函数,并将生成的子组件添加到其父组件
    发表于 08-21 14:29

    分布式塔台模拟机中同步渲染算法的研究

    基于分布式的塔台模拟机中,难点是多机渲染同步问题。文中提出一种使用双网结构的塔台模拟机体系结构,并从渲染空间同步、动态数据同步、帧
    发表于 12-14 15:11 16次下载

    畅快开黑 一加6T开通王者荣耀Vulkan模式

    更加极致的游戏体验。 图:一加6T现已开通Vulkan模式 Vulkan是Khronos 标准组织退出的新一代图形处理接口(API)标准,其充分考虑到移动平台多核CPU和GPU协同处理任务的特点,降低功耗的同时还能进一步提高
    的头像 发表于 11-09 09:30 1821次阅读

    Vulkan编程接口的特征

    Vulkan 简介 Vulkan是一个用于图形和计算设备的编程接口。Vulkan设备通常由一个处理器和一定数量的固定功能硬件模块组成,用于加速图形和计算操作。通常,设备中的处理器是高度线程
    的头像 发表于 03-15 17:17 1802次阅读

    FFmpeg将初步支持Vulkan

    近期 Vulkan 势头不小,游戏引擎 Godot 计划年中发布的 4.0 大版本中将支持 Vulkan,Raspberry Pi 也即将迎来 Vulkan 的支持。现在我们还可以看到 FFmpeg 也将支持
    的头像 发表于 03-16 09:38 2215次阅读

    Vulkan API 基本类型介绍

    Vulkan 基本类型,Vulkan 开发需要设计的类型非常多,整理其基本类型如下,主要包含设备、队列、命令缓冲、队列家族、渲染通,管线等……
    的头像 发表于 02-12 16:19 1510次阅读

    Vulkan API 基本类型 小结

    Vulkan 基本类型,Vulkan 开发需要设计的类型非常多,整理其基本类型如下,主要包含设备、队列、命令缓冲、队列家族、渲染通,管线等……
    发表于 02-23 06:02 5次下载
    <b class='flag-5'>Vulkan</b> API 基本类型 小结