什么是GPU?

"A graphics processing unit (GPU) is a specialized electronic circuit designed to manipulate and alter memory to accelerate the creation of images in a frame buffer intended for output to a display device."

意思是GPU是专用的电子电路,用于操纵和改变存储器,来加速在帧缓冲区中创建图像,该帧缓冲区用于输出到显示设备。也就是说,GPU是专门用于生成图像的计算单元。GPU 主要用于嵌入式系统、电话、pc、工作站和游戏机

为什么需要GPU?CPU不可以吗

CPU也可以生成图像,但它的效率不高。现在生成图像需要的计算量十分大,尤其是要做到实时的效果

先来看看GPU 和 CPU的架构的不同

如上图所示,CPU大部分由控制器寄存器构成,而GPU大部分为ALU,它具有高并行结构.所以 GPU 在处理图形数据和复杂算法方面拥有比 CPU 更高的效率

什么是ALU呢?ALU(Arithmetic Logic Unit)称为逻辑运算单元,用于数据处理,正因为GPU有大量的ALU,因此它十分适合对密集型数据进行并行处理

CPU执行指令时,一次只能处理一个数据,不存在真正意义上的并行,但GPU有多个处理器核,在一个时刻可以并行处理多个数据

GPU采用流式并行计算,可对每个数据进行独立的并行计算(流内任意元素的计算不依赖于其他同类型的数据).也就是说,一个数据单独执行的时间和多个数据并行一起运算的时间是一样的

既然GPU那么牛,不可以取代CPU吗?

虽然GPU这种并行处理的方式运算速度的确非常快,但因为任一元素的计算不依赖于其他同类型的数据,所以GPU需要依赖"知晓数据间相关性"的算法,这在CPU上实现十分便捷,但GPU很困难

而且,GPU在控制流方面不如CPU。GPU的控制器少于CPU,而控制器的功能就是取指令且指出下一条指令在内存中的位置,以此协调计算机各个部分按部就班的工作

CPU和GPU如何并行工作?

若没有渲染流水线,CPU需要等到GPU完成上一个渲染任务才能再次发送渲染命令,很显然这种方式渲染效率低下,并不是并行的。那么如何让他们进行并行工作呢?解决之道是使用命令缓冲区(Command Buffer)

命令缓冲区包含一个命令队列,由CPU向其添加命令,由GPU从中读取命令,这两个步骤是相互独立的。当CPU需要渲染对象时,便向命令缓冲区添加命令,而当GPU完成上次渲染任务后,便会从命令队列中再取一个命令执行它

命令缓冲区的命令有许多种,DrawCall是其中一种

GPU架构与内存架构

  • 目前的GPU架构存在两种,分别是分离式架构耦合式架构

    上图左是分离式架构,CPU和GPU各自有独立的缓存和内存,它们通过PCI-e等总线通讯。这种结构的缺点在于 PCI-e 相对于两者具有低带宽高延迟数据的传输是性能瓶颈。目前使用非常广泛,如PC、手机

    上图右是耦合式架构,CPU 和 GPU 共享内存和缓存。AMD 的 APU 采用的就是这种结构,目前主要使用在游戏主机中,如 PS4

  • 大多数GPU与CPU类似,也有多级缓存结构:寄存器、L1缓存、L2缓存、GPU显存、系统显存

    它们的存取速度从寄存器到系统内存依次变慢

目前流行的三种GPU架构

基于PC和移动端出现了两种类型的GPU架构:IMR(Immediately Mode Rendering)和 TBR(Tile-Based Rendering),也就是立即渲染和分块渲染。IMR用于PC端,而TBR用于移动端。TBDR(Tile-Based Deferred Rendering)相较于TBR还多一次基于硬件的Deffered,是目前主流移动端所使用的架构

名词解释

谈到GPU架构会出现许多新名词,这里对它们进行个总结

  • SOC(System On Chip)

    把CPU、GPU、内存、通信基带、GPS等模块整合在一起的芯片名

  • Sistem Memory

    SOC中GPU和CPU共用一块片内LPDDR物理内存,就是我们常说的手机内存,也叫SystemMemory

  • OnChipMemory

    CPU和GPU还分别有自己的高速SRAM的Cache缓存,读写速度非常快

  • Stall

    当一个GPU核心的两次计算结果之间有依赖关系而必须串行时的等待过程

  • FillRate

    像素填充率 = 总像素数 x Shader复杂度 x Overdraw

IMR架构

IMR架构的流程和网上经常看到的渲染管线类似,从系统内存中将mesh data、texture data提交到显存中,后续对depth buffer、frame buffer的读写都位于显存中。当顶点经过vertex shader处理后,会进入一个先进先出队列并依次处理,处理完后的顶点数据输入到fragment shader中

  • 优点
    • 每个图元直接提交渲染,无需任何额外处理,可以极大地提高GPU的吞吐量,充分利用GPU性能
    • 顶点数据被填充到FIFO队列,即使几何数据再多,也会累积在On-Chip上。如此GPU使用很少的外部内存带宽来存储和检索中间几何结果。使得IMR架构可以处理海量的DrawCall和顶点
  • 缺点
    • Overdraw。当A图元已经被渲染,但是B图元完全遮挡了A图元,A图元的渲染就毫无意义,这就是OverDraw。在IMR通常通过Early-Z(Early Visibility Test)避免OverDraw

    • IMR需要处理大量数据,造成消耗大量的内存带宽。因为IMR在全屏的FrameBuffer上渲染,导致FrameBuffer有很大的内存占用,所以一般放在显存中。然而IMR架构下会和FrameBuffer的多次读写,并且在像素着色器之后的Depth Test、Stencil Test和Blend等操作都会有多次读写,这会消耗大量的带宽,同时还会产生较高的能耗

    优化:GPU设立了L1、L2Cache来优化每次渲染后的Color和Depth数据写回到Frame Buffer和 Depth Buffer产生的带宽消耗

TBR架构

为什么移动端不使用IMR架构?

在移动端最注重的是发热量,而发热量的直接元凶便是带宽,因此在移动端的GPU架构中需要着重考虑带宽。但看了IMR架构可以发现,该架构下带宽消耗非常高,不适于手机端这样带宽可怜的平台。因此便衍生了TBR架构

TBR架构与IMR架构的区别

和IMR在全屏FrameBuffer上直接渲染不同,TBR的思路是将整个屏幕划分为多个Tile来进行渲染,整个Tile在fragment后才会将渲染结果写入FrameBuffer,所以TBR拆分为两个阶段

可以看到上图中使用了On-chip Memory,原因是手机端GPU并没有显存。这是因为SOC中CPU、GPU被集成在一起,它们使用的内存在同一个物理内存颗粒上。但GPU有逻辑上独立的内存区间,这块内存区间是GPU驱动来管理的。再提醒一句,在以前openGL ES规范中CPU和GPU之间的内存是不能共享的,vertex和texture的buffer是需要拷贝的,即使是在同一物理内存上;但在当下的图形API中,已经支持共享内存让CPU和GPU都可以直接读取。而On-chip Memory类似于L1、L2缓存,读写速度非常快

因为On-chip Memory类似于L1、L2缓存,而L1、L2缓存的特点是非常小,为了充分利用这一特性,IMR就提出了分块处理(Tile)。整个光栅化和像素处理会被分为一个个Tile进行处理,通常为16×16大小的Tile。TBR使用On-Chip Buffers储存Tiling后的Depth Buffer和Color buffer减少了最影响性能的系统内存传输的开销

与IMR零一点不同,TBR并不像IMR一样来一个渲一个,而是先进行顶点数据的处理(Tile)后,将其传回物理内存中,等到需要刷新整个FrameBuffer时,再将其从物理内存传回来,并进行光栅化

TBR的优缺点

  • 优点
    • TBR减少对FrameBuffer频繁的读写,大大减少带宽损耗。若每个Tile足够小,可以将FrameBuffer也都放在On-Chip上(也就是L1,L2Cache),因为对On-Chip来说读取和写入速度都非常快,但本身特别小
    • FrameBuffer、Depth Test、Color Blend等都在On-Chip上完成,进一步减少对显存的读写,减少了带宽损耗
    • 一般情况下短暂存在的缓冲区如Depth Buffer、Stencil Buffer等,若后续不需使用丢弃便可,无需回存到显存中,进一步减少带宽损耗
    • 对于某些技术有一定优势,比如MSAA,因为可以在On-Chip上实现Multisampling,不会带来大量的访问主存的开销,也不会大幅增加显存占用。所以移动平台MSAA是比较高效的。但是开启MSAA,因为处理的数据要变多,On-Chip内存是固定的,所以Tile尺寸会对应缩小,会导致Tile整体数量增加
  • 缺点
    • 延迟相较于IMR要高些。因为Tile后,数据需要放入物理内存,等要用时再存入system memory渲染,这一来一回的性能损耗,和IMR相比来说这将是一个明显的延迟
    • 移动端曲面细分和几何着色器的开销很大。对于处理完的顶点数据和Primitive List还需要放入显存中,如果顶点数量特别多的话,这将有一个明显的带宽开销
    • 如果某些三角形叠加在数个Tile上,需要被绘制数次(每个Tile都会处理一遍),某些图元会被重复计算,这意味着总渲染时间将高于IMR
    • Tile大小的选择是一个Trade Off点。如果Tile选择的过大,则Tile数量相对较少,意味着重复计算的图元会相对较少,但对应Tile内覆盖的图元的数量也会增加,On-Chip需要更大容量。所以Tile的大小相当重要,现在大部分厂商都是使用32X32的大小

TBDR

TBDR全称是Tile-Based Deferred Rendering,相比于TBR来说还增加了一个Deffered阶段——如下图所示的HSR阶段

与软件层面的Deffered不同,HSR是属于硬件层面的Deffered,由硬件自行进行

为什么需要新增HSR

TBN架构的设计目的是优化功耗,但它存在一个问题:无法避免overdraw。虽然GPU管线中对于不透明物体可以用到Early-Z来优化渲染个数,但对于复杂场景不可能进行严格的由近到远的绘制,也就是说Early-Z不能完全避免overdraw

Early-Z本身来说是没问题的,问题在于每次Early-z通过后就绘制该像素。因为TBRTile后会将顶点数据存到缓冲区中,所以在HSR中就可以做一次Deffered,在Deffered缓冲区中记录通过了Early-Z且标记该pixel是属于哪个图元的pixel(注意这里虽然通过了Early-Z,但并没有立马就画)。待Tile中所有图元都记录完毕后,再根据像素和图元的映射关系来确定运行指定的fragment shader。这样就能实现0次overdraw的渲染,且Tile块是比较小的,在On-chip Memory中读写的代价也是极小的

基于TBDR的渲染优化

不使用Framebuffer时clear或discard

在移动端下,都是等到Tile完后才把数据送进FrameBuffer,因此很多时候FrameBuffer是空闲的,若FrameBuffer是空闲的建议discard它

在PC端下,因为是来就渲染,所以无需discardFrameBuffer

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

最具代表性的便是后处理,在后处理中会多次切换FrameBuffer,每一次切换都需要对整个framebuffer的走一遍绘制流程。也就是说,切换framebuffer在tbdr的架构是性能瓶颈

Early-z的抉择

因为tbdr有framedata队列,很多gpu会很聪明的尽量筛去不需要绘制的framedata。所以在tbdr上early z,或者stencil test这些是非常有益处的

需要注意的是powervr的gpu不需要eralyz,因为它内部的HSR硬件会自动处理这批framedata最终绘制到屏幕的drawcall,甚至也不需要对不透明的物体做从前到后的绘制排序。至于大部分android设备,预先的一遍early-z可能就可以大大减少overdraw,但early-z会增加cpu的提交负担,尽管很多android显卡对于zpass有特殊的优化。总之如果带宽瓶颈严重,那么early-z是有必要开的。但明确的是,移动端的early-z不是在光栅化和ps之间,而是在更早的对framedata的处理过程中

Alpha Blending和MSAA的效率较高,Alpha-test效率较低

Alpha Blending、MSAA的效率高。原因是Alpha Blending、MSAA需要读写on-chip上的缓冲区,但每个Tile非常小且on-chip的读写速度是极快的,因此移动端上Alpha Blending、MSAA的效率是比较高的

Alpha-test效率很低。原因在于开启Alpha-test后Early Z便不再支持,那么可能会造成overdraw

但其实Alpha Blending和Alpha-test的开销差不多

若要使用Alpha-test,使用Z-prePass

第一个Pass即Z-Prepass只写入深度,不计算颜色

第二个Pass关闭深度写入并且将深度比较函数设为相等

渲染队列:opaque->alpha-test->blending

原因是alpha-test不会阻挡到更多的不透明物体的z预剔除

避免大量的drawcall和顶点量

传统的pc上看,drawcall以及顶点数量对gpu没有太大影响,但对于移动设备,结论就不是这样的。tbr渲染的时候在Tile后会存储framedata队列,这个framedata数据会随着drawcall、顶点量而增大

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

避免gpu上的copy-on-write

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

贴图格式能压缩就压缩

贴图内存越小,Tile命中率就越高,总的传输量也少

reference

针对移动端TBDR架构GPU特性的渲染优化

移动设备GPU架构知识汇总

浅谈移动端GPU架构

深入GPU硬件架构及运行机制

IMR, TBR, TBDR 还有GPU架构方面的一些理解


他们曾如此骄傲的活过,贯彻始终