图形学基础|移动端GPU架构

图形学基础|移动端GPU架构

文章目录

  • 图形学基础|移动端GPU架构
    • 一、前言
    • 二、移动端GPU架构
      • 2.1 为什么移动端选择TBDR
      • 2.2 FrameData
      • 2.3 PowerVR的HSR技术
    • 三、基于TBDR的渲染优化
    • 参考博文

一、前言

现代移动端GPU架构大多为TBDR(Tile-Base-Deffered-Rendering)。

本文摘录了一些相关博文的介绍。以下是笔者的笔记。

二、移动端GPU架构

2.1 为什么移动端选择TBDR

移动端优先考虑功耗

  • 功耗 -> 耗电量 -> 芯片大小

对功耗影响最大的是:带宽(BandWidth)

通常,GPU的On-Chip内存(也就是SRAM,或者L1、L2 Cache)很小,这么大的FrameBuffer要存储在离GPU相对较远的DRAM(显存)上。

将GPU想象成为你家,SRAM想象成为小区的便利店,DRAM想象成为市中心超时。

移动端的GPU想到了一种化整为零的方法:

  • 将巨大的FrameBuffer分解成为很多小块。
  • 使每个小块可以被离GPU更近的那个SRAM容纳。

这样,GPU可以分批次的一块一块地在SRAM访问FrameBuffer。等一整块都访问好了之后,整块转移到DRAM(显存)。

为什么这样可以减少带宽呢?

对FrameBuffer现在几乎全部的访问在渲染这一块的时候(test,read,write,blend…)都完全在SRAM上解决,只在最后把这一块整体渲染完了才整体搬回DRAM。

下图展示了TBR和传统IR(immediate-rendering)的对比:

图形学基础|移动端GPU架构_第1张图片

2.2 FrameData

为什么要在TBR中加入D

在TBR架构下,是不能够来一个CommandBuffer就执行一个的,这会造成GPU一直在搬迁所有的Tile。这样很慢而且会很耗电。

TBR的一般实现策略是:

  • 对于CPU过来的CommandBuffer,只对他们做Vertex Process,然后对VS的结果进行暂时缓存。
  • 等待非得刷新整个FrameBuffer的时候,才真正进行光栅化,进行tile-based-rendering。

那么,什么时候非得刷新整个FrameBuffer的时候呢?

比如说:SwapBuffer、glflush,glfinish,glreadpixels,glcopytexiamge,glbitframebuffer,queryingocclusion,unbind the framebuffer等。

下图对比了TBR和IR,其中FrameData是几何处理后的数据,被暂时存储起来。

图形学基础|移动端GPU架构_第2张图片

FrameData,是TBR特有的在GPU绘制时所需的数据,在PowerVR中叫做Arguments Buffer。

此时,又多了一个优化的可能。

现有的大部分TBR显卡都或多或少的优化。

例如:IOS的PowerVR,它多了一个叫做ISR的硬件,专门对这些FrameData做处理,找到这次渲染真正有可能会被写入到Framebuffer上的那些drawcall,而过滤掉大部分的drawcall。

例如:对于不透明物体,一些可能不能通过ZTest的,一些会被模板测试拒绝,他们在ISR处理framedata后根本都不会进入pixel shader。

和软件层面的延迟渲染不同的是:

  • 软件层面的延迟渲染是针对一个drawcall的,对于从后到前的不透明物体绘制是每次都会绘制的;
  • 而硬件层面的延迟渲染时对一批drawcall的,它会从这批绘制里面找到最终要绘制的物体。

2.3 PowerVR的HSR技术

HSR(Hidden Surface Removal) 技术的概念和原理其实真的非常简单,没有多深奥。

EarlyZ,绘制前先做深度测试,减少不必要的绘制。

  • 这也就是为什么我们在渲染不透明物体时要按照从近到远的顺序去绘制。

但基于EarlyZ是无法完全地避免OverDraw的。

这是因为对于一个真正复杂的场景,我们是不能在渲染的时候严格按照由远及近的方式来绘制的。

例如,一个面积很大的地块与远处的物体相近,那么谁应该算比较”近“呢?

又比如,一个有凹面的物体,其上每个图元的渲染顺序也会因视角不同而出现先绘制远处的三角形,再绘制近处的情况。

如果把物体拆分,那么就会增加DrawCall。

完全严格前后物体排序,那意味着Shader(渲染状态)切换的次数增加!

PowerVR的HSR技术,不需要在软件层面对物体进行排序,HSR在硬件上实现零OverDraw的优化!

原理也超简单,当一个像素通过EarlyZ准备执行PS进行绘制钱,先不画,只记录标记这个像素归哪个图元来画。

这个Tile上所有的图元都处理完了,最后再真正地开始绘制每个图元中被标记上的能绘制的像素点

这样每个像素实际上只执行了最后通过EarlyZ的那个PS,并且由于TBR的机制,Tile块内所有图元的相关信息都在片上,就可以通过极小的代价去获得,最终零OverDraw。

图形学基础|移动端GPU架构_第3张图片

那么,HSR如何处理AlphaTest和AlphaBlend呢?

HSR的前提是:假定了前面的物体会挡住后面的物体,因此这对于AlphaTest和AlphaBlend物体是没有作用的。(但他们仍可以被EarlyZ拦下)。

不仅没作用,反而会被其中断硬件的Defer流程,导致渲染性能下降(相比不透明物体)。

如果在HSR处理不透明物体的过程中,突然来一个AlphaTest的图元,那么为了保证渲染结果正确,HSR就必须要终止当前的Defer。把已经标记好的像素绘制出来,再进行后面的绘制。这显然严重影响了渲染的效率。

这就是为什么官方文档特意强调要避免AlphaTest的原因。

而AlphaBlend同样会中断HSR的Defer,强制开始绘制,但比起AlphaTest好那么一点就是它不影响后续图元继续执行HSR处理。

总结下:

  • 在PowerVR上渲染,因为有HSR的存在我们只需要把所有不透明物体放到一起扔给GPU画就行了;
  • 在其他移动GPU上,同样也是要把所有不透明物体放到一起,但是还要先做个排序再交给GPU。

三、基于TBDR的渲染优化

了解了移动GPU的设计架构,就能明白:移动GPU相对于桌面GPU来说最大的区别就是带宽的开销成本很高

因此任何有高带宽开销成本的操作都具有高性价比优化的潜力。

降低Shader的复杂度固然是通用的降低GPU开销的方式,但如果还考虑到耗电或发热问题的话,带宽开销应该是最为重点的关注

针对TBDR,总结一些重要的优化特性,有些甚至与PC端是南辕北辙的。

贴图格式能压缩就压缩

尽可能的提高在gpu内部cache上访问的到的可能性,减少对内存访问。

贴图内存越小,片上命中率就越高,总的传输量就越少。

关于mipmapping

mipmap会增大内存,但是在之前我们更多的关注它的好处是增强了远处物体的渲染效果,不会因为小像素采样大贴图导致的闪烁。

但是对于移动端带宽功耗严重的情况下,mipmap还可以极大的增强Cache的命中率,减少对显卡内存的实际直接访问,减少带宽资源。

注:会增加总贴图的内存占用大小,需要在内存开销带宽开销上做一个平衡。

随机纹理寻址相对于相邻纹理寻址有显著开销

GPU内存和CPU内存一样存在时间局部性和空间局部性,相邻纹理寻址可提高片上命中率。

使用LUT(look up texture)很可能是负优化

需要对比权衡带宽占用+texture fetch操作增加与ALU占用增加降低并行效率,另外还很可能涉及到美术工作流和最终效果,所以是个不是很好进行操作的优化。

腾讯的技术分享将引擎中Tonemapping那步的3DLUT(UE4和Unity都是这样的)替换为函数拟合的优化,理论上应该是会提升不少性能,但是要想真正应用到生产环境,保证效果,还要做好拟合工具链,是得费不少力气的

通道图能合并就合并,减少Shader中贴图采样次数

一种常用的优化方法。

不使用FrameBuffer时Clear或Discard

前面提到了FrameData会一直积累下来直到等到一些必须要绘制到framebuffer上的时候。

这意味着TBDR架构下CommandBuffer,只是积攒到一定的时间点。

而glclear这类操作是可以把当前的FrameData清空,从而节约很多不必要的绘制。

设想这样一种情况:你一直往一个RenderTarget上绘制,过了一会儿当你不再使用这张RT时(即unbind)也会触发这些FrameData的绘制。

如果在你不再使用这张图之前能够调用一次clear,那么unbind的时候FrameData就是清空的,可以减少很多不必要的绘制。

在每帧渲染之前尽量clear

在一些渲染技巧里面可能提到:每帧渲染前不Clear当前的Buffer,例如认为下一帧一定会把整个FrameBuffer都覆写,没有必要先Clear。

这对于PC端显卡来说是可以的。

但对于TBR架构,不进行Clear是噩梦!

因为如果不Clear,那么每个TIle在初始化的时候都要从DRAM上的FrameBuffer把一块一块的内容完整拷贝过来,而Clear则会使得初始化过程变得简单,无需不必要的拷贝。

不要在一帧里面频繁的切换FrameBuffer

在TBDR架构,GPU会尽可能地只在不得不绘制的时候才渲染FrameBuffer。

在移动端每次使用FrameBuffer都会触发一个所有tile的绘制。

同样的我们看到游戏中的全屏后处理被大量的使用,这在手机上性能不高!

主要是因为这些后处理在1帧内触发了很多不同的FrameBuffer之间的bind和unbind。

每次bind和unbind都需要对整个FrameBuffer的一次绘制,从TileMemory和DRAM之间进行搬迁。

因此,切换FrameBuffer在TBDR的架构是性能瓶颈。

关于Early-Z

因为TBDR有FrameData队列,很多GPU会很聪明地尽量去筛除不需要绘制的FrameData。

所以在TBDR上的Early-Z或者Stencil-Test是非常有益处的。

例如:

  • 你定义了一个stencil,GPU有可能在对FrameData处理的过程中就筛掉了那些不能通过stencil的drawcall了。
  • 或者通过scissor test可能一整块tile都不需要绘制。

关于Early-z也一样,很多程序会通过预先渲染一遍简单的zpass来占住z,这样就可以在对FrameData处理阶段就reject掉那些不能通过z的drawcall了。

但是,要注意的是powervr的gpu不需要eralyz,因为它内部的ISR硬件会自动处理这批FrameData最终绘制到屏幕的drawcall,甚至也不需要对不透明的物体做从前到后的绘制排序。

至于大部分android设备,预先的一遍early-z pass可能就可以大大减少你的overdraw了,不过还是需要慎重,因为预先的一遍early-z pass又会增加cpu的提交负担,尽管很多android显卡对于zpass有特殊的优化。

总之,如果带宽瓶颈严重,那么early-z是有必要开的。

但这里要明确的概念是,移动端的early-z发生在哪个阶段,不是在光栅化和ps之间,而是更早的对framedata的处理过程中

Blending和MSAA的效率其实很高,AlphaTest效率很低

Blending(对FrameBuffer的读与写)、MSAA(增加FrameBuffer的读取次数)在TBDR硬件架构上是非常快速的。

当然MSAA除了会造成FrameBuffer访问增多,还会带来渲染像素的数量增多。

而对于AlphaTest:

很多文献说,AlphaTest这种技术在IOS的显卡上效率较差,不如AlphaBlending。

道理是什么呢?

因为AlphaTest会导致Early-Z失效,它对Depth的写入是不能预先知道的,必须等到PS执行,从而增加了渲染量。

而Alpha-Blending无需写入Depth,可以通过Early-Z提前剔除。

Blending更高效,alpha-test是性能瓶颈这些都是因为tbdr的gpu会对FrameData做预先的优化处理带来的。

避免大量的drawcall和顶点量

在传统的PC上,DrawCall以及顶点数量对GPU是没有太多严重的影响的。

但放在TBR的移动设备上,结论就并非如此。

因为TBR渲染时会存储FrameData数列,这些数据会随着DrawCall和顶点数量的增加而增加,极端情况下,增大到内存放不下,那么就需要暂存到其他地方。这样对FrameData的访问速度就奇慢无比。

虽然没有找到这个framedata究竟在不同的设备上可以存放多大,但是测试来看百万的顶点量不管你的drawcall多少,shader多简单,在大部分机器都肯定会触发这个瓶颈了。

避免gpu上的copy-on-write

因为tbdr的渲染是延迟的,想象这样一种情况,你当前帧内对一个mesh做个顶点动画,传递给gpu绘制,然后后面又改变了它的顶点动画,又提交给gpu,这样前面一个的vb还绑定在gpu上没有被处理,处于framedata队列状态,这一份同样的vb(改变过的)又要过来了,这时gpu会对这个vb做一个新的拷贝,以存储vb的多份不同的数据。显然这又增加了framedata的存储,会触发上面3.7的瓶颈。

尽量使用direct rendering模式

direct rendering应该是指直接渲染到场景的FrameBuffer中。

很多游戏使用了很多后期处理,需要先渲染到贴图上,然后再一遍遍blit到屏幕上,blit操作是一个昂贵的操作。

它涉及到对当前framelist data 的立即绘制(理论上framelist data越晚绘制越好,越利于延迟渲染),然后额外的FrameBuffer又增加了显存带宽的读取和写入。

关于DepthBuffer

Depth一般有:16位、24位、32位。

非32位的深度在GPU的Cache性能不好,尽量考虑32bit。

为什么drawcall这么慢?

drawcall优化是优化cpu瓶颈,而不是gpu瓶颈。

在几何数据量不变的情况下,分多个drawcall提交和少个drawcall提交对于cpu的性能损耗更大。

一个drawcall涉及到渲染状态的设置和绘制数据的提交。

  • 在渲染状态的设置中,CPU端既有可能要发生:内存分配(API在CPU一侧的分配),数据拷贝(把数据从CPU一侧到GPU一侧),组装CommandBuffer,然后发给GPU;
  • 这个过程可能还要涉及到同步和等待,例如一个vbo正在gpu被处理而我现在想改变它就需要等GPU处理完。

所说的drawcall是开销,不是说绘制次数多了成瓶颈,而是伴随着drawcall带来的cpu一侧的内存分配,数据拷贝,api调用,等待,同步,以及对gpu供给数据的低效造成的。

渲染是一个流水线,在这个流水线上先后发生的会影响性能的依次可以分为:

  • CPU,API(DrawCall),几何处理(Vertex Geometry Shader)、 三角形建立、像素处理、带宽

有时候我们需要屏蔽其中几块去定位性能瓶颈。

例如:通过Profile工具把FrameBuffer设置到极小,可以屏蔽Pixel处理,采样一个虚假的极小的贴图可以屏蔽带宽的因素等。

参考博文

  • 针对移动端TBDR架构GPU特性优化
  • 针对移动端TBDR架构GPU特性优化(续)
  • 移动设备GPU架构知识汇总
  • Early-Z

你可能感兴趣的:([图形学基础],ios)