本条记是学习B站视频GPU架构与渲染管线优化-上篇一些简单记录,方便自己后续工作中查阅。后续视频更新会在这里跟进。
GPU根本架构
关于这一块,教程中的资料有一些是来自知乎GPU架构和渲染一文,整理这篇条记的时候也参考了此中的内容,需要了解更深入的话可以参考阅读。
GPU包罗若干个GPC(Graphics Processing Cluster,图形处置惩罚簇)
GPC包罗若干个SM(Stream Multiprocessor,流多处置惩罚器)
SM是GPU处置惩罚的主要单位,下图为SM的结构图
SM架构
PolyMorph Engine:多边形变形引擎。负责处置惩罚和多边形顶点相干的工作,包括以下模块。
Vertex Fetch模块:顶点处置惩罚前期的通过三角形索引取出三角形数据。
Tesselator模块:对应着DX11引入的新特性曲面细分。
Viewport Transform模块:对应着顶点的视口变换,三角形会被裁剪准备栅格化。
Attribute Setup模块:负责顶点的插值运算并输出给后续像素处置惩罚阶段使用。
Stream Output模块:对应着DX10引入的新特性Stream Output。
Instruction Cache:指令缓存。存放将要执行的指令,通过Dispatch Units填装到每个运算核心(Core)举行运算
Warp Schedulers:Warp调理模块。Warp的概念其实就是一组线程,通常由32个线程组成,对应着32个运算核心。Warp调理器的指令通过Dispatch Units送到运算核心(Core)执行。
Register File:寄存器堆。存放将要处置惩罚的数据。
Core,运算核心,也叫流处置惩罚器(SP——Stream Processor)。每个SM由32个运算核心组成。由Warp Scheduler调理,接收Dispatch Units的指令并执行。
LD/ST:加载/存储模块(Load/Store)。辅助一个Warp(线程组)从Share Memory或显存加载(Load)或存储(Store)数据。
SFU:特殊函数单位(Special function units)。与Adreno GPU中的初等函数单位(Elementary Function Unit,EFU)雷同,执行特殊数学运算。由于其数量少,在高级数学函数使用较多时有明显瓶颈。特殊函数就是诸如幂次、对数、三角函数、反三角函数等计算。
Interconnect Network:内部链接网络。
下面的部门是存储模块:
L1 Cache:L1缓存。不同GPU架构不一样,有些L1缓存和Shared Memory共用,有的L1缓存和Texture Cache共用。
Uniform Cache:全局统一内存缓存。
Tex Unit和Texture Cache:纹理读取单位和纹理缓存。Fermi有4个Texture Units,每个Texture Unit在一个运算周期最多可取4个采样器,这时刚好喂给一个线程束(Warp)(的16个车道),每个Texture Uint有16K的Texture Cache,而且在往下有L2 Cache的支持。
GPU内存架构
GPU雷同于CPU也有自己的寄存器、L1 Cache、L2 Cache、显存,乃至必要时候还可以使用系统内存。
图中越往上,存取速度越快,越往下存取速度越慢。此中,Global Memory(全局内存)即我们通常所说的显存,通常放在GPU芯片的外部。L2 Cache是GPU芯片内部跨GPC而存在的(第一张图中心位置)。L1 Cache/Shared Memory、Uniform Cache、Tex Unit和Texture Cache以及寄存器都是存在于SM内部的(第二张图里都有)。
各存储结构访问速度:寄存器(Register File)>>共享内存/L1(Shared Memory/L1)>L2>>纹理缓存/常量缓存(Texture Cache/Uniform Cache)>显存(Global Memory)
存储容量巨细与之成反比
GPU的角度看渲染管线
这里的vertex fetch在SM中能找到。RasterrizerEngine在GPC中、ROP搜出来是Raster Operations Units,即光栅化单位,但是光栅化处置惩罚应该在像素着色器之前,加上这里ROP结果直接存入了显存中,应该按照视频理解为帧缓存。
紫色的部门都在SM中举行计算,能看到顶点流数据最开始都是从显存中抓取,后续着色器的处置惩罚计算结果都是在L1L2中完成,这也是为什么我们写着色器需要限制变量数量的缘故原由。
CPU和GPU之间的数据传输是一个异步的过程,雷同于服务器和客户端之间的数据传输。CPU和GPU构造了一种生产者/消耗者异步处置惩罚模子。CPU生产“命令”,GPU消耗“命令”,通过这种关系CPU就可以将数据和行为传输到GPU,GPU来执行对应动作。
CPU端通过调用渲染API(Graphics API),比如DX大概GL,将操作封装为一个一个的命令存放到命令队列中(FIFO Push Buffer),即上图中的PushBuffer。
在CPU准备渲染的时候,CPU需要准备好顶点/纹理数据、将操作全部放到队列中,并设置渲染状态,然后通知GPU举行渲染。整个在unity中我们称为一个draw call。这个准备过程相当费时,这也是为什么我们需要归并网格,尽量控制drawcall数量的缘故原由,否则会出现GPU不绝等候CPU完成这些工作,团体帧数严重下降的问题。(这部门是Unity Shader入门精要一书中的内容根据回忆写的,作为这里知识点的补充)
Shader中的内存限制
这张图反映了不同版本着色器模子中的内存限制,此中4.0根本对应DX10.0之后,此中VS是VertexShader,PS指PixelShader。
视频到这里就没有了。文章中另有一些比力有意思的地方,摘录如下:
SM的线程束机制:
比如一个SM总共有32768个寄存器,如果一个线程需要256个寄存器,那么一个SM可以支持32768/256 = 128个线程,假如这个SM只有32个计算核心(CORE),那怎样举行计算呢,我们将128个线程分为128/32=4个线程束,对线程束举行调理,当遇上比力费时的操作时,线程束会阻塞,切换其他的线程束来提前执行。
为什么要用线程束而不是对一个个线程独立举行管理呢,因为运算核心是以lock-step的方式执行的,线程执行的“步调”是一致的,每条指令对于全部线程来说都是“一起开始一起竣事”。所以线程调理器调理的单位是线程束(Warp)。
由于线程束的机制,可以推出以下结论。由于寄存器堆的寄存器数量是固定的,如果一个Shader需要的寄存器数量越多,也就是每个线程分配到的寄存数量越多,那么线程束数量就越少。线程束少,供线程调理器调理的资源就少,当遇到耗时指令时,由于没有更多线程束去灵活调配,全部线程就只能死等,倒霉于资源的充分利用,最终导致执行效率低下。
渲染优化相干
寄存器充分使用
对于Shader的语义也好,寄存器也好,都是作为矢量存在的。对于GPU的ALU来说,一条指令可以处置惩罚的数据一样平常是四维(4D)的,这就是SIMD。由于SIMD的特性,寄存器要尽可能完全利用。例如Unity里有一个宏用来缩放而且偏移图片采样用的UV坐标——TRANSFORM_TEX。按道理缩放UV需要乘以一个二维向量,偏移UV也需要加一个二维向量,这里应该是需要两个寄存器的。然而Unity将两个二维向量都装入同一个四维向量里面(xy为缩放,zw为偏移),这样就只用到一个寄存器了。总而言之,要充分利用寄存器向量的每一个分量。
逻辑控制语句
GPU和CPU由于其计划目的就有很大的区别,于是出现了非常不同的架构。
不要在Shader里写逻辑控制语句,包括if-else和for循环等逻辑。
可以参考CPU和GPU的运行逻辑:
CPU在执行一条指令时,需要经过以下四个步骤:fetch(获取指令)decode(解码指令)execute(执行指令)write-back(写回数据)
为了节约资源,对于每一条运算指令,会依次并行的开始执行,比如第一条指令到decode了,第二条指令就可以开始fech.那么当执行if时,这里就不能继承,需要等候后面的条件判定才能继承执行,CPU会直接把上一次的结果拿来用然后继承,猜测成功则节约性能,失败也不影响。
而对于GPU,假如多线程执行一个if语句,逻辑判定成功的继承执行,但失败的必须等候,直到8个线程都执行完毕才能继承,会浪费很多的执行周期。
在Shader中不是不能写逻辑控制语句,而是要思考一下有没有被浪费的资源。换句话说,Shader里不要用不固定的数值来控制逻辑执行。
淘汰调用费时指令
通常一些需要从缓存里,乃至内存里读取数据的操作会比力费时,例如贴图采样的指令。
从上文中可以了解到,一样平常GPU架构里SFU这种处置惩罚单位比力少,因此特殊数学函数尽量少调用,例如pow、sin、cos等。
及时clear大概discard
如果不消RenderTexture了就及时Discard掉。
例如有一张RenderTexture,渲染之前调用clear就能清空前一次的FrameData,不消这张RenderTexture了,就及时调用Discard(),以提高性能。
不要频仍切换RenderTexture
频仍切换RenderTexture会导致频仍将Tile数据拷贝到FrameBuffer上,增加性能消耗。
Early-Z
Early-Z可以很好的低沉Overdraw,但是某些操作会使Early-Z失效。
1.Alpha Test / Clip / discard:需要执行完 PS 后,才能确定该像素深度是否被写入。
2.手动修改GPU插值得到的深度。
3.开启透明混合(AlphaBlend)。
4.关闭深度测试。
因此要做到以下几点。
渲染物体时,渲染程序要按“Opaque → AlphaTest → AlphaBlend”的顺序渲染物体。
由于一样平常来说地形覆盖面积最大,“Opaque”的内部可以按“其他不透明物件 → 地形”的顺序渲染,最大化利用Early-Z优化Overdraw。
无论PowerVR照旧Mali/Adreno芯片,AlphaTest都会影响性能,尽量少使用AlphaTest技能。
不支持Early-Z的硬件,可以适当使用PreDepathPass多渲染一遍图元来优化Overdraw,但是会增加顶点绘制的负担,需要权衡。
避免大量drawcall和顶点数
FrameData里会储存当前帧变换过的图元列表,也就是顶点数据,FrameData数据会随着Drawcall数增加而增加,FrameData增大有可能会存储到其他地方,影响读写速度,因此在移动平台渲染上百万个顶点大概三四百Drawcall就比力吃力了。
视频其他分P中另有一些Unity渲染管线相干的基础知识,这里也简单记录一下
Unity的渲染管线
Unity默认管线,也就是Build-In Render Pipeline,不做任何设置时末了为此管线。
SRP,英文为Scriptable Render Pipeline,可编程渲染管线,留的接口相当多,但是如果自己重新实现,工作量也巨大。SRP中内置了URP和HDRP两个渲染管线,如果不重头写的话根本可以在这两个此中一条的基础上改。
URP,Universal Render Pipeline通用渲染管线,前身为LWRP(LightWeight Render Pipeline)轻量级渲染管线,适用于手机等移动装备,其实一样平常游戏,除非像影视/3A游戏等对光影效果要求极高的情况,URP根本上也够用了。
HDRP,High Definition Render Pipeline高清渲染管线,主要渲染最高质量的画面效果,性能开销较大,学习难度略高,通用性没那么强。
URP的基础使用
在unity的package manager中下载Core RP Library(SRP的基础包)High Defineition RP(HDRP的基础包,有两个)Universal RP(URP的基础包)。
以URP为例,有三种使用方法
1.添加RenderObject,可以举行一些基础设置
2.RenderFeature方式,主流方式,可控性较高
3.Renderer前两个都是渲染器的一部门,如果前两个都无法满足要求,可以对整个渲染器做进一步扩展
内置管线的缺点
这个流程会导致需要反复切换Pass,代价非常高,导致渲染过程被打断/合批被打断
可以考虑优化为,同一Shader同一Pass的多个物体,可以都先执行这个相同的Pass,再执行后面的Pass,改变这个流程
默认状态下URP的渲染流程
这里的Depth Prepass就相当于前面的Pre-Z,提前剔除看不见的不透明物体
URP管线的定制
RenderObject
自定义RendererFeatures,它可以在底层调用RenderBuffer
现实的项目案例
这里有一个现实的案例作为参考,可以看出根据现实需要将一些对象单列出来举行了处置惩罚,例如在需要渲染水体的时候,区分水上和水下需要一张深度图,所以需要CopyDepth这个环节。
包括末了RenderRefraction是处置惩罚水体的反射和折射,运行开销比力大,后处置惩罚的开销也比力大。所以在CopyColor的时候可以做一个低沉分辨率的操作,计算完后再提升分辨率加入叠加。
URP的其他利益
淘汰Blit操作
Blit就相当于屏幕内容的拷贝,比如后处置惩罚时,但是会耗费大量性能,由于URP的可定制性,可以淘汰不必要的Blit操作
便于殊效实现
例如火焰/空间扭曲等,可以控制它的渲染顺序,实现需要的效果
现实应用举例
这里给了一个沙盘描边的例子
最常见的描边方法是将全部顶点沿法线向外移动一定距离然后剔除正面,再继承正常渲染正面剔除背面,但是无疑这个做法的性能黑白常差的。
视频中给出了一个描边的方法,例如要给选中的城池描红框,可以考虑单独开一个buffer将这个城池绘制为红色色块,然后模糊,剔除城池再叠加到画面中。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |