【学习】GPU架构与Unity渲染管线

本笔记是学习B站视频GPU架构与渲染管线优化-上篇一些简单记录,方便自己后续工作中查阅。后续视频更新会在这里跟进。

GPU基本架构

关于这一块,教程中的资料有一些是来自知乎GPU架构和渲染一文,整理这篇笔记的时候也参考了其中的内容,需要了解更深入的话可以参考阅读。
图示标出的为GPC
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将这个城池绘制为红色色块,然后模糊,剔除城池再叠加到画面中。
在这里插入图片描述

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值