移动端GPU——渲染流程

移动端GPU——渲染流程_第1张图片
PattiSmith

1. IMR——桌面 GPU 的渲染流程

1.1 IMR 渲染特点

  • DrawCall 中的模型顺序执行 VS 和 PS

  • 每个DrawCall 完成后,PS 将所有像素颜色、深度等写入 FrameBuffer

  • 每个像素可以被多次写入,写入顺序和 DrawCall 执行顺序一致

1.2 作为写入目标的FrameBuffer ,对显存的性能要求

  • 访问速度:极高的访问速度

  • 容量要求:满足响应分辨率和格式(RGBA,HDR等)的容量

  • 功耗:电池供电设备的低功耗要求

在目前的移动设备硬件水平下,这三个目标通常最多只能满足两个。目前的主流选择:牺牲容量,换取更快的访问速度和更低的功耗。

2. 解决方案:TBR(Tile Based Rendering)

移动端GPU——渲染流程_第2张图片
引入TileBuffer

2.1 SIMD 核心不直接写到 FrameBuffer,而是写到TileBuffer,TileBuffer 的内容在恰当的时候写到 FrameBuffer

  • 小容量、高速 On chip 的 TileBuffer 作为 SIMD 的写入目标

  • FrameBuffer 会分块依次渲染

  • 单Tile上所有任务,即每个 Tile 上的 DrawCall 都完成后才一次写入显存

移动端GPU——渲染流程_第3张图片
TBR渲染流程

2.2 TBR 中的深度测试

移动端GPU——渲染流程_第4张图片
TBR中的深度测试
  • Early-Z:在 PS 之前执行,测试失败的像素不执行PS
  • Late-Z:在 PS 之后执行,测试失败的像素不写入 Color Buffer
  • 可见 Early-Z 能显著降低 PS 的性能压力,最好在无法应用 Early-Z 的时候再启用 Late-Z

什么情况下 Early-Z 失效?

因为 Early-Z 在PS 之前执行,所以需要必须在 PS 之前就确定深度,也就是说 执行PS时像素深度不能发生变化

  • Alpha Test / Clip:需要执行完 PS 后,才能确定该像素深度是否被写入
  • Custom depth,PS中改写深度值,硬件能够感知,Early-Z 阶段无法获取最终的深度值,失效。

2.3 移动端 GPU 的 Early-Z

移动端GPU——渲染流程_第5张图片
移动端 GPU 的 Early-Z
  • TileList 保存当前 Tile 上的所有三角形列表
  • 隐藏面剔除HSR(Apple/PowerVR), LRZ(Adreno), FPK(Mali)
  • 原理:硬件层面先对 Tile 中的三角面进行 Depth Prepass,相当于先进行了一边深度测试,剔除掉看不到的 fragment,可以显著降低 overdraw 程度,减少 PS 调用次数
  • 移动端 Early-Z 失效:打断了硬件的 Depth Prepass

2.4 性能关注点

2.4.1 顶点开销

  • TBR 中顶点会存在 TileList 中,过多的顶点会使得 TileList 过大,影响访问性能和内存开销
  • 移动端 Early-Z 虽然会降低 PS 开销,但是会增加 VS 开销(额外做一遍 Depth Prepass,有些厂商的GPU实际上是执行了良次 VS,其中一次会优化掉和位置计算无关的部分),因此优化 VS 复杂度(特别是位置计算)尤为重要

2.4.2 Tessellation

  • 移动 GPU 中通常没有专门的 Tessellation 单元,效率更低
  • Tessellation 产生更多顶点,需要执行更多次 VS,增加开销

2.4.3 充分利用 TileBuffer

TileBuffer 和主显存之间消耗大量带宽,针对这个消耗进行如下的优化

  • 每一帧对 TileBuffer 执行Clear ,避免上一帧的 Color Buffer/ Depth Buffer 又被从主存加载一边
  • 渲染完成后 Discard 掉不需要的 Render Target 或 Depth Buffer,避免会写到显存,节省大量带宽。

3. TBDR:基于 TileBuffer 的延迟渲染

3.1 延迟渲染传统流程

移动端GPU——渲染流程_第6张图片
延迟渲染传统流程
  • 需要提供较多的显存空间,支持多张 RT(MRT),也就是GBuffer,写入材质各种属性
  • 独立的一个 LightPass,读入每个像素的GBuffer属性,并进行光照处理,得到最终的 ColorBuffer
  • 移动平台上难以实现的原因:延迟渲染因为 MRT 的存在,大量消耗显存读写的带宽

3.2 移动设备上的延迟渲染(TBDR)

移动端GPU——渲染流程_第7张图片
TBDR示意
  • 像素的属性(MRT)不被写入主显存,只存在于 TileBuffer中,最终只复制 Color 信息到主显存中,降低了带宽消耗
  • 几乎不会占用额外带宽

4. 移动设备上的 MSAA

4.1 传统 MSAA 流程

移动端GPU——渲染流程_第8张图片
传统MSAa
  • 需要一个4倍RT来获得4倍的fragment,最后再 resolve 会1倍的 RT

4.2 移动设备 MSAA 流程

移动端GPU——渲染流程_第9张图片
移动端MSAA
  • 多倍采样的 RT 仅在 TileBuffer 中存在
  • Resolve 发生在 TileBuffer 中,逐 Tile 执行
  • 仅单倍采样的 RT 被写回显存

5. GPU 和 CPU 协作(Frame Pacing)

5.1 基本的 FramePacing 流程

移动端GPU——渲染流程_第10张图片
FramePacing
  • CPU 和 GPU 异步运行
  • Command Buffer 作为 CPU 和 GPU 的协议包
  • CPU 填充 CommandBuffer,在提交后(DrawCall)后 GPU 才开始执行 CommandBuffer

5.2 对 FramePacing 进行优化

目标:最大程度的并行化

  • 尽早提交渲染命令,让 GPU 尽早开始工作,但避免将 CommandBuffer 拆分过于细碎,提交本身也有消耗
  • 非渲染相关的 CPU 任务不必要等待上一帧渲染完成(不要等 GameUpdate)
  • 保存数据单向流动(CPU到GPU),避免单帧内 CPU 依赖 GPU 执行结果

CPU 帧内依赖于 GPU 执行结果的一个例子是 “硬件遮挡查询”机制,它需要 CPU 创建遮挡查询命令提交给 GPU,等待 GPU 的运算结果,再根据结果执行渲染。

5.3 垂直同步

渲染结果只能在固定的时间点显示到屏幕,比如移动设备的屏幕刷新率为 60Hz,意思是手机屏幕每秒最多进行 60 次的缓冲区交换(渲染刷新)
问题:

  1. 若某帧渲染太快,到了第一个同步点就刷新到屏幕了,而下一阵太慢,第二个同步点才刷新,则帧率不稳定,有卡顿感
  2. 若刷新率慢而帧渲染率快,未开启垂直同步的情况下,可能画面撕裂,上一个 FrameBuffer 才显示一半,下一帧 FrameBuffer 已经提交,再刷新就变成了下一帧的画面
  • 移动设备上始终开启垂直同步,保证最小的帧间隔时间
  • 做帧率限制时,保持最大帧率和屏幕刷新率整除的关系,如60Hz设备可限制 30FPS,20FPS,但不要限制 25FPS
  • 使用图形层 API 接口来限制

你可能感兴趣的:(移动端GPU——渲染流程)