超强总结!GPU 渲染管线和硬件架构

超强总结!GPU 渲染管线和硬件架构

导语| 本文简述了 GPU 的烘托管线和硬件架构,对一些常见问题进行了评论和剖析。有以下几点中心内容:(1)移动渠道烘托管线 TBDR 的介绍;(2)GPU 缓存体系的介绍;(3)Warp 的履行机制;(4)常见的如 AlphaTest 或许分支对功用的影响。

超强总结!GPU 渲染管线和硬件架构

GPU 烘托管线

一、烘托管线简述

所谓烘托管线,便是 CPU 传送给 GPU 一堆数据(极点、纹路等),经过一系列处理,终究烘托得出来一副二维图像。有以下几个阶段。

  • 应用程序阶段

1.粗粒度除去、烘托状况设置、预备数据。

2.咱们在游戏引擎中所做的视锥除去、遮挡除去等,都是粗粒度除去,是依据模型等级的。

3.这一步是在 CPU 进行的,后边的步骤都是在 GPU 内部进行的。

  • 极点处理阶段

1.极点着色器、曲面细分、几许着色器、极点裁剪、屏幕映射。

2.这儿会做背面除去等裁剪,保证只要真实需求制造的图元才会进入光栅化。

3.极点处理是可编程的(Vertex Shader,Geometry Shader 和 Compute Shader)。

  • 光栅化阶段

1.三角形设置、三角形遍历、片元着色器。

2.光栅化引擎会将图元(Primitive)映射为与屏幕像素对应的片元(Fragment)。片元包括每个像素的坐标、色彩、深度、法线、导数、纹路坐标等信息。这个数据经过片元着色器的核算得到终究的色彩值。

3.像素处理是可编程的(OpenGL 中叫做片元着色器,Fragment Shader,DirectX 中叫做像素着色器,Pixel Shader)。这儿一般是功用瓶颈地点,所以现代 GPU 做了许多的优化来尽或许防止履行无效的像素处理,比方 EarlyZ、隐面除去等。

  • 逐片元操作

1.裁剪测验、深度测验、模板测验、混合。

2.光栅化阶段得到的色彩值经过一些列的测验、混合,终究写入到 FrameBuffer 中。

3.ROP (Raster Operations),是由一个独立的硬件单元来完结的。这个硬件单元的数量和功用约束了 GPU 每秒写入 FrameBuffer 的数据量。在一些低端机这个阶段也十分有或许成为功用瓶颈,即每秒 ROP 处理数据量。即使运用最简略的全屏半透明 shader,多叠加一两层就会因 overdraw 而严峻降帧,很有或许便是 ROP 瓶颈。

二、IMR: Immediate Mode Rendering

超强总结!GPU 渲染管线和硬件架构

上图展现了一个十分经典的 IMR 烘托管线,也是桌面端最常见的 GPU 架构。GPU 会在每个 drawcall 提交中,依照管线的次序处理每个图元。

  • 优势:
1.其优势是烘托管线没有中断,有利于进步 GPU 的最大吞吐量,最大化的运用 GPU 功用。一起从 vertex 到 raster 的处理都是在 GPU 内部的 on-chip buffer 上进行的,这意味着只需求很少的带宽(bandwidth),就能够存取(storing and retrieving)处理进程中的图元数据。
2.所以桌面 GPU 天然就能够处理许多的 DrawCall 和海量的极点。而移动端 GPU 则对这两者反常灵敏。这不仅仅是 GPU 功用差异,架构差异也至关重要。
  • 下风:

IMR 是全屏制造的。当时制造的图元或许存在于屏幕的任何方位。这意味着咱们需求一个全屏的 FrameBuffer。这个 buffer 的内存很大(比方 30MB,巨细跟屏幕分辨率和烘托方针的格局有关),所以只能放在体系内存(DRAM)中。而在光栅化之后的 fragment shading, depth testing, stencil testing, blending 等阶段,都会许多的与体系内存进行交互,耗费许多的带宽。GPU 的 L1/L2 缓存能够缓解这个问题,可是关于移动端,仍然是不行承受的开支。

三、TBR: Tile-based Rendering

超强总结!GPU 渲染管线和硬件架构

  • 为什么要运用 TBR 架构
1.对移动端设备而言,操控功耗是十分重要的。功耗高意味着耗电、发热、降频,这会导致咱们的游戏呈现严峻的卡顿或许帧率下降。带宽是功耗的榜首杀手,许多的带宽开支会带来显着的耗电和发热。

2.移动端 GPU 的带宽本来就跟桌面端 GPU 不是一个量级,又无法像独立显卡相同独占许多带宽,所以削减带宽开支变得反常重要。因而移动端 GPU 普遍运用的是 TBR/TBDR 架构。

  • TBR 架构的原理

TBR 跟 IMR 不同之处在于,它并不是依据全屏直接制造。而是把屏幕分成一个一个的 Tile,GPU 一次只制造一个 Tile。制造结束再将制形成果写入到体系内存的 FrameBuffer 中。

TBR 架构的制造进程分成两个部分

榜首步,处理一切极点,生成一个 tile list 的中心数据(FrameData)。这个数据保存了每个图元归归于哪个屏幕上的 Tile。PowerVR 一个 Tile 是 32×32 巨细,而 Mali 则是 16×16 巨细(行将发布的 Mali-G710 也修正为 32×32 巨细了)。

第二步,针对每个 Tile 履行像素处理进程(光栅化、片元着色器等),每个 Tile 处理结束,将成果一起写入到体系内存中。

  • 优势

1.TBR 架构最大的优势便是削减了对主存的拜访,也即削减了带宽开支。每个 Tile 足够小,其 framebuffer 是能够做到 on-chip memory 上的。on-chip memory 紧挨着 GPU 的 shader core,拜访速度极快。

2.不仅仅是 fragment shader,depth testing、blending 等操作也是在 on-chip memory 进行的。大幅削减像素处理阶段对体系内存的拜访。

3.还有一个显着优势是,depth buffer 和 stencil buffer 只在 Tile 处理内部有用。它们是不需求写回体系内存的,这进一步节约了带宽开支。

4.TBR 架构里,一些本来十分贵重的,耗费许多的带宽的算法,会变得高效起来。比方 MSAA (Multi-Sample Anti-Aliasing)。

  • 下风

1.GPU 要处理一切的极点,生成 tile list,然后才干够进行光栅化。跟 IMR 比较,这儿会有显着的“推迟(latency)”。

2.生成的这个 tile list 数据是要存到体系内存中的。这相同会有带宽开支。极点越多,核算的压力就越大,带宽耗费也会越多。像曲面细分(tessellation),在 TBR 架构下就反常的贵重。

3.所以移动端游戏对极点数量愈加灵敏。假如极点数量过大的话,会导致功用严峻下降。

四、TBDR: Tile-Based Deferred Rendering

超强总结!GPU 渲染管线和硬件架构

TBDR 和 TBR 方式基本相似,仅有的区别在于,多了一个 隐面除去(Hidden Surface Removal) 的进程。便是上图中 HSR & Depth Test 这个步骤。经过 HSR,不管以什么次序提交 drawcall,终究只要对屏幕发生奉献的像素会履行像素着色器。被遮挡的片元会被直接丢掉掉。

不同的 GPU 有自己的隐面除去技术,比方 PowerVR 便是 Hidden Surface Removal (HSR),Adreno 便是 Low Resolution Z (LRZ),Mali 便是 Forward Pixel Kill (FPK)。其原理和完结各不相同,不过终究意图都是为防止履行无效的像素着色器。

五、总结

IMR 是桌面端 GPU 的干流架构。NVIDIA 较新的显卡也部分支撑了 Tile based 的特性。不过这个 Tile 是较大的 Tile,而不是像 Mali 芯片这样 16×16 的小 Tile。

TBR 是移动端 GPU 的干流架构,经过拆分成一个个 Tile 制造,削减与主存的交互,然后削减带宽开支。

TBDR 一开端专指 PowerVR,其光栅化之后并不是当即烘托,而是多了隐面除去的进程。后来 Adreno 和 Mali 也别离提出了自己的隐面除去计划。所以以为现在的移动端 GPU 都是 TBDR 架构也不为过。

引荐阅览 GPU Framebuffer Memory: Understanding Tiling 这篇文章,愈加直观的了解不同架构的制造进程。

超强总结!GPU 渲染管线和硬件架构

GPU 硬件架构

一、GPU 和 CPU 的差异

超强总结!GPU 渲染管线和硬件架构

这张图展现了 CPU 和 GPU 的硬件差异。

CPU 中心数量少(核算单元少),每个中心都有操控单元。内存规划上是大缓存、低推迟。

而 GPU 则恰好相反,核算单元十分多,多个核算单元同享一个操控单元。内存规划上寻求高带宽,能够承受较高推迟。

所以 CPU 中习以为常的分支操控,逻辑运算,在 GPU 中成了奢侈品。而面对海量数据并发核算的场景,GPU 则比 CPU 快好几个数量级。

CPU 和 GPU 的差异能够描绘在下面表格中:

超强总结!GPU 渲染管线和硬件架构

二、CPU 的缓存体系和指令履行进程

尽管本文首要讲的是 GPU 架构,不过 CPU 和 GPU 有许多当地是相通的,一起又有一些规划方向是相反的。了解 CPU 能够协助咱们更好的了解 GPU 的履行进程。

  • 内存的硬件类型

1.SRAM(Static Random Access Memory,静态随机存取内存)具有静止存取数据的作用,可是断电后数据会消失,不需求改写电路就能够保存数据,速度较 DRAM 快许多。一般用作片内缓存(On-chip Cache),例如 L1 Cache、L2 Cache。

2.DRAM(Dynamic Random Access Memory,动态随机存取内存)需求不停地改写电路,不然内部的数据将会消失,因而被称为“动态”存储器。常用于内存,容量较 SRAM 大。一般用作体系内存(System Memory)。现在桌面端的内存都是 DDR (DDR SDRAM,Double Data Rate Synchronous Dynamic Random-Access Memory,简称 DDR)。

3.GDDR(Graphic DDR),用作显存。时钟频率更高,耗电量更少。前期显存也是运用 DDR 的,不过后边独立开展为 GDDR。DDR 现在还处于 DDR4 规范,而 GDDR 现已开展到 GDDR6 了。

4.LPDDR SDRAM,简称 LPDDR(Low Power Double Data Rate)。是移动设备常用的一种低功耗 SDRAM,以低功耗和小体积著称。FrameBuffer 便存放于此。芯片参数中说的 LPDDR4/LPDDR5,说的便是这个,代表了体系内存的功用。

5.UFS(Universal Flash Storage)。通用闪存存储,如 ufs2.1/ufs3.1 等。代表了移动设备“磁盘”的功用。

  • CPU 的缓存体系

CPU 的缓存有 L1/L2/L3 三级缓存。L1 缓存和 L2 缓存是在 CPU 中心内部的(每个中心都配有独立的 L1/L2 缓存),L3 缓存是一切中心同享的。缓存是 SRAM,速度比体系内存(DRAM)要快十分多。

超强总结!GPU 渲染管线和硬件架构

L1/L2 缓存是片上缓存,速度很快,可是一般比较小。比方 L1 cache 一般在 32KB256KB 这个等级。而 L3 cache 能够抵达 8MB32MB 这个等级。像苹果的 M1 芯片(CPU 和 GPU 等单元在一个硬件上,SoC),L3 缓存是给一切硬件单元运用的,所以也被称为 System Level Cache。

L1 缓存分为指令缓存(I-Cache)和数据缓存(D-Cache),CPU 针对指令和数据有不同的缓存战略。

L1 缓存不或许规划的很大。由于增大 L1 缓存尽管会削减 L1 cache missing,可是会添加拜访的时钟周期,也便是说下降了 L1 cache 的功用。

CPU 的 L1/L2 缓存需求处理缓存共同性问题。即不同中心之间的 L1 缓存之间的数据应该是共同的。当一个中心的 L1 中的数据发生变化,其他中心的 L1 中的相应数据需求符号无效。而 GPU 的缓存不需求处理这个问题。

CPU 查找数据的时分依照 L1–>L2–>L3–>DRAM 的次序进行。当数据不在缓存中时,需求从主存中加载,就会有很大的推迟。

超强总结!GPU 渲染管线和硬件架构

缓存对进步 CPU 的履行功用有着十分重要的意义。如上面的 Intel i7 die shot 所示,许多时分缓存会占据芯片中一半以上的晶体管和面积。苹果的 A14/A15/M1 芯片功用上碾压同层次的其他 SoC,跟超大缓存密不行分,其 L2/L3 缓存一般是其他 SoC 的两三倍。

  • CPU 指令的履行进程

经典的指令流水线有下面五个阶段

1.Instruction Fetch,取指令。从指令缓存(I-Cache)中取出指令,放在指令寄存器中。

2.Instruction Decode,指令解码。这儿还会经过寄存器文件(Register File)取到指令的源操作数。

3.Execute,履行指令。

4.Memory Access,假如需求存储器取数据(load/store 指令),则经过数据缓存(D-Cache)取数据。不拜访存储器的指令此阶段不做任何事情。

5.Register Write Back,将指令履行成果写入意图寄存器。

拜访主存,不管是 GPU 仍是 CPU 都会呈现较大的推迟。面对推迟,CPU 和 GPU 的解决计划不相同。CPU 是经过大容量高速缓存,分支猜测,乱序履行(Out-of-Order)等手段来讳饰推迟。CPU 缓存容量比 GPU 要大,比方 GPU 就没有 L3 缓存。并且 CPU 的缓存是以低推迟为方针规划的。GPU 是别的一种思路,经过切换线程 Warp 来躲避指令推迟带来处理单元的停顿,下文介绍 GPU 的 Warp 机制的时分还会说到这一点。

假如呈现分支,CPU 是经过火支猜测,来进步流水线的履行功用。现代 CPU 的分支猜测准确率很高,能够抵达 90%以上。当然一些糟糕的代码影响了分支猜测,就会呈现功用问题。GPU 没有分支猜测单元,所以并不擅长履行分支。

CPU 能够一起发射多条指令,让指令能够并行核算,而不是一条流水线串行核算,这样能够更好的运用核算单元。这个便是超标量规划(Super Scalar)。现代 CPU 基本都是超标量的。

假如依照本来的指令次序履行,或许指令之间有依靠无法并行履行,或许频频呈现高推迟指令。所以 CPU 会在保证履行成果正确性的根底上,修正指令的履行次序,让指令能够愈加高效的履行,然后削减履行等候,进步管线功用。这个便是乱序履行。

CPU 的线程切换会有显着的上下文(Context)切换开支。由于切换到其他线程需求将寄存器和程序计数器保存起来,等切换回来的时分还需求康复寄存器和程序计数器。所以 CPU 会尽或许防止频频的线程切换。而 GPU 由于寄存器数量许多,线程切换时不需求保存上下文,所以就能够经过零成本的切换线程来讳饰推迟。

Intel CPU 也有运用 GPU 的思路,预备两套寄存器,CPU 中心在两个线程之间切换的成本就十分低。这样一个中心就能够作为两个中心来运用。这便是超线程技术。不过 CPU 毕竟不像 GPU 有许多寄存器,中心在两个线程之间切换,并纷歧定能够保证下降推迟,一起不能准确操控每个线程的履行时刻。所以许多游戏以及高功用核算程序都是封闭超线程的。

三、GPU 烘托进程

具体烘托进程,其实便是经典的烘托管线的履行进程。能够跟上一部分的烘托管线流程图对照阅览。引荐阅览 Life of a triangle – NVIDIA’s logical pipeline 一文。

应用程序经过图形 API(DirectX、OpenGL、Vulkan、Metal)宣布烘托指令,经过驱动传输数据给 GPU。GPU 经过主机接口(Host Interface)承受这些指令,并经过前端(Front End)处理这些指令。

SM (Streaming Multiprocessor) 担任处理履行极点着色器。现代 GPU 都是共同着色器架构(Unified Shader Architecture),极点着色器和像素着色器运用相同的处理中心履行。这样 GPU 能够更好的做负载均衡,以习惯极点任务重或许像素任务重的不同作业情形。

处理过的三角形会被裁剪,然后分配给光栅化引擎。在光栅化阶段,会把三角形离散为与屏幕对应的栅格信息。

光栅化后的片元,经过 EarlyZ 除去,生成像素线程。32 个线程为一个线程束(Warp)。这是 GPU 核算中心的最小作业单元。

接下来是在 SM 中履行像素着色器。一个 Warp 中履行的指令是相同的,可是数据不相同(SIMD/SIMT)。

履行完像素着色器之后,数据会交给 ROP (Raster Operations)。由于像素着色器履行有快有慢,所以这儿会有排序进程,保证履行 ROP 的次序和原始 API 的调用次序是共同的。一个 ROP 内部有许多 ROP 单元,在 ROP 单元中处理深度测验,和 Framebuffer 的混合。深度测验和色彩写入有必要是原子操作,不然两个不同的三角形在同一个像素点就有或许会有抵触和过错。

四、桌面端 GPU 硬件架构

超强总结!GPU 渲染管线和硬件架构

上图展现的是 NVIDIA Fermi 架构的示意图。

不同的 GPU,架构差异较大,可是大体都包括下列中心组件:

1.SM、SMX、SMM (Streaming Multiprocessor)。GPU 的中心,履行 Shader 指令的当地。一个 GPU 由多个 SM 构成。Mali 中相似的单元叫做 Shader Core,PowerVR 中叫做 Unified Shading Cluster (USC)。

2.Core。真实履行指令的当地。NVIDIA 叫做 CUDA Core。Mali 中叫做 Execution Engine 或许 Execution Core。PowerVR 中叫做 Pipeline。当然由于硬件结构差异,Execution Engine、Pipeline 和 CUDA Core 并不等价。后边还会对此再做剖析。

3.Raster Engine。光栅化引擎。

4.ROP (Raster Operations)。depth testing, blending 等操作都是在这儿完结的。

5.Register File, L1 Cache, L2 Cache。寄存器和各级缓存。

五、Shader Core 的首要构成单元

1.32 个运算中心 (CUDA Core,也叫流处理器 Stream Processor)

2.16 个 LD/ST(Load/Store)模块来加载和存储数据

3.4 个 SFU(Special function units)履行特殊数学运算(sin、cos、log 等)

4.128KB 寄存器(Register File)3 万个 32-bit 的寄存器,大寄存器规划

5.64KB L1 缓存 (On-Chip memory)

6.纹路读取单元 (Texture Unit)

7.指令缓存(Instruction Cache)

8.Warp Schedulers:这个模块担任 warp 调度,一个 warp 由 32 个线程组成,warp 调度器的指令经过 Dispatch Unit 送到 Core 履行。

六、GPU 的内存结构

  • UMA (Unified Memory Architeture)

超强总结!GPU 渲染管线和硬件架构

这张图展现了桌面端和移动端的内存结构差异。

桌面端独立显卡是左边的分离式架构,CPU 和 GPU 运用独立的物理内存。而移动端是右侧的共同内存架构,CPU 和 GPU 共用一个物理内存。桌面端的集成显卡也是 UMA 架构。

UMA 并不是说 CPU 和 GPU 的内存就在一起了,实践上它们所运用的内存区域并不相同。物理内存中有一块儿专有区域由 GPU 自己办理。CPU 到 GPU 的数据通信仍然是有复制进程的。假如狭义上说,像主机渠道或许苹果 M1 芯片这样能够完结 CPU 和 GPU 的零复制数据传输的架构才是真实的 UMA,移动端这种架构只能算是同享物理内存。

移动芯片都是 SoC(System on Chip),CPU 和 GPU 等元件都在一个芯片上,芯片面积(die size)寸土寸金。自然不或许像桌面端相同给显卡装备 GDDR 显存,经过独立的北桥(PCI-e)进行通信。在移动端 CPU 和 GPU 运用同一个物理内存也愈加灵敏一些,操作体系能够决议分配给 GPU 的显存巨细。当然副作用便是 CPU 和 GPU 许多时分会抢占带宽,这会进一步约束 GPU 能运用的带宽。

GPU 运用独立的显存空间的优点是,GPU 能够对 Buffer 或许 Texture 做进一步优化,比方对 GPU 愈加友爱的内存排布。显存中存储的数据或许并不是咱们实践 Upload 的数据。所以即使在手机上,CPU 和 GPU 共用的是一块儿物理内存,咱们仍然需求经过 MapBuffer 的方法来完结数据的复制。反过来说,假如 CPU 和 GPU 直接运用相同的数据,那么 GPU 就无法对数据做优化,或许会下降功用。

CUDA 后边有推出共同虚拟地址(Unified Virtual Address,UVA)和共同内存(Unified Memory,UM)的技术,将内存和显存的虚拟地址共同。不过这个跟物理内存是兼并的仍是分离的没有关系。其意图是为了减化开发者写 CUDA 程序的内存办理的负担。

  • GPU 缓存的分类

GPU 缓存结构
L1 缓存是片上缓存(On-Chip),每个 Shader 中心都有独立的 L1 缓存,拜访速度很快。移动 GPU 还会有 TileMemory,也便是片上内存(On-Chip Memory)。

L2 缓存是一切的 Shader 中心同享的。归于片外缓存,离 Shader 中心略远,所以拜访速度较 L1 缓存要慢。

DRAM 是主存(体系内存,能够叫做 System Memory,Global Memory 或 Device Memory),拜访速度是最慢的。FrameBuffer 是放在主存上的。

内存拜访速度

内存的存取速度从寄存器到体系内存顺次变慢:

存储类型 Register Shared Memory L1 Cache L2 Cache Texture/Const Memory System Memory
拜访周期 1 1~32 1~32 32~64 400~600 400~600

寄存器拜访速度是最快的,GPU 的寄存器数量许多。

Shared Memory 和 L1 Cache 是同一个硬件单元,Shared Memory 是能够由开发者操控的片上内存,而 L1 缓存是 GPU 操控的,开发者无法拜访。部分移动芯片如 Mali,是没有 Shared Memory 的,这个首要影响 OpenCL 开发。

Local Memory 和 Texture/Const Memory 都是主存上的一块儿内存区域,所以拜访速度很慢。

NVIDIA 的内存分类

查材料的时分经常会看到这些概念,可是 NVIDIA 的内存分类是为 CUDA 开发服务的,与游戏开发或许移动 GPU 仍是有一些差异的。所以这儿只需求简略了解即可。

1.全局内存(Global memory)。主存,Device Memory。

2.本地内存(Local memory)。Local Memory 是 Global Memory 中的一部分。是每个线程私有的。首要用于处理寄存器溢出(Register spilling,寄存器不行用了),或许超大的 uniform 数组。拜访速度很慢。

3.同享内存(Shared memory)。Shared Memory 是片上内存,拜访速度很快。是一个 Shader 中心内的一切线程同享的。

4.寄存器内存(Register memory)。拜访速度最快。

5.常量内存(Constant memory)。Constant Memory 和 Local Memory 相似,都是 Global Memory 中的一块儿区域,所以拜访速度很慢。部分 GPU 会有专门的 Constant cache。

6.纹路内存(Texture memory)。与 Constant Memory 相似,也在主存上。部分 GPU 有 Texture cache。

  • Cache line

1.GPU 和缓存之间的内存交流是以字节为单位,而是以 Cache line 为单位的。Cache line 是固定的巨细,比方 CPU 的 Cache line 是 64 字节,GPU 是 128 字节。

2.Cache line 不仅仅是为了字节对齐。也有现实意义。想要知道是否缓存命中,是否写入主存,必定要有符号位。所以一个 Cache line 便是符号位+地址偏移+实践数据。

3.缓存命中与否功用差异巨大。对一块儿内存进行次序拜访比随机拜访,功用或许要好许多。咱们纹路运用 Mipmap 能够是进步纹路的缓存命中率然后进步功用。Unity 的 ECS 体系也是希望经过 Cache 友爱的数据布局来进步功用。

  • Memory Bank 和 Bank Conflict

为了进步对内存的拜访功用,取得更高带宽,Shared Memory/L1 Cache 被规划为一个个的 Memory Bank(L2 cache 或许也有相似规划)。bank 数量一般与 warp 巨细或许 CUDA core 数量对应,例如 32 个 core 就把 SMEM 划分为 32 个 bank,每个 bank 包括多个 cache line。Bank 能够了解为 Memory 的对外窗口,有 10 个窗口能够拜访,必定要比只要 1 个窗口要高效。

假如同一个 warp 中的不同线程,拜访的是不同的 bank,那么就能够并行履行,最大化运用带宽,功用最高。

假如拜访的是一个 bank 中的同一个 cache line,那么能够经过广播机制同步到其他线程。一次拜访即可取得数据,也不会有功用问题。

假如拜访的是同一个 bank 中的不同的 cache line,那么就有必要堵塞等候,串行拜访。这个会严峻阻止 GPU 的并行性,发生显着的功用下降。这个堵塞等候的状况,被称为Bank Conflict。

假如不同的线程,对同一个 cacheline 有写操作,那么也有必要要堵塞等候。有必要等上一个线程写结束,才干履行后边的读取或许写入操作。

七、GPU 的运算体系

  • SIMD (Single Instruction Multiple Data) 和 SIMT (Single Instruction Multiple Thread)

在游戏引擎内,咱们常会运用 SSE 加快核算(比方视锥体裁剪的核算)。这儿运用的便是 SIMD,单个指令核算多个数据。

而 GPU 的规划是为了满意大规模并行的核算(其处理的任务便是天然并行的)。因而 GPU 是典型的 SIMD/SIMT 履行方式。在其内部,若干相同运算的输入会被打包成一组并行履行。

在介绍 SIMT 之前,咱们需求先介绍下Vector processor和Scalar processor的概念。前期 GPU 是Vector processor(对应 SIMD)。由于前期 GPU 处理的都是色彩值,便是 rgba 四个分量。在此架构下,编译器会尽或许把数据打包成 vec4 来进行核算。可是跟着图形烘托以及 GPGPU 的开展,核算变得越来越杂乱,数据并纷歧定能够打包成 vec4,这就或许会导致功用的糟蹋。所以现代 GPU 都改善为Scalar processor(对应 SIMT)。后边介绍 Mali 的架构演进的时分还会说到这一点。

现代 GPU 都是 SIMT 的履行架构。传统 SIMD 是一个线程调用向量处理单元(Vector ALU)操作向量寄存器完结运算,而 SIMT 往往由一组标量处理单元(Scalar ALU)构成,每个处理单元对应一个像素线程。一切 ALU 同享操控单元,比方取指令/译码模块。它们接收同一指令共同完结运算,每个线程,能够有自己的寄存器,独立的内存拜访寻址以及履行分支。

传统的 SIMD 是数据级并行,DLP (Data Level Parallelism)。而 SIMT 是线程级并行,TLP (Thread Level Parallelism)。更进一步的超标量(Super Scalar)是指令级并行,**ILP (Instruction Level Parallelism)**。

Mali 的 Midgard 是 VLIM(超长指令字,Very long instruction word)规划。它能够经过 128bit-wide 的核算单元并行核算 4 个 FP32 或许 8 个 FP16 等类型的数据。编译器和 GPU 会兼并指令以充分运用处理器资源。这也是一种指令级并行(ILP)。

PowerVR、Adreno 的 GPU,以及 Mali 最新的 Valhall 架构的 GPU 都支撑Super Scalar。能够一起发射多个指令,由闲暇的 ALU 履行。也便是说,同一个 Pipeline 内的多个 ALU 元件是能够并行履行指令序列中的指令的。

不管运用哪种架构,GPU 的核算单元都是并行处理许大都据,所以有的文章也会直接把 GPU 的核算单元称作 SIMD 引擎,或许简称为 SIMD。

超强总结!GPU 渲染管线和硬件架构

如上图所示,左侧是 Vector 处理器,而右侧是 Scalar 处理器。关于 Vector 处理器而言,它是在一个 cycle 内核算(x,y,z)三个值,假如没有填满的话,就会发生糟蹋。假如是 Scalar 处理器,它是在 3 个时钟周期内别离核算(x,y,z)三个值,不过它能够 4 个线程一起核算,这样就不会糟蹋处理器功用。

兼并单个核算为向量核算,在 Scalar processor 上没有优化作用。由于处理器核算的时分仍是会把向量拆散。之前是一个 vec4 在一个 cycle 内完结核算。现在是一个 vec4 在 4 个 cycle 内完结核算,每个 cycle 核算一个单位。假如是 vec3 的话,便是 3 个 cycle。

  • Warp 线程束

Warp 是典型的单指令多线程(SIMT)的完结。32 个线程为一组线程束(Warp)。这 32 个线程一起履行相同的指令,仅仅线程数据不相同,这便是锁步(lock step)履行。这样的优点便是一个 Warp 只需求一个套操控单元对指令进行解码和履行就能够了,芯片能够做的更小更快。Mali 前期的 GPU 并不是依据 warp 的,功用表现欠安。由于每个中心都有操控单元,占用了过多的晶体管,发生了许多的 overhead,然后导致功耗的添加。

Warp Scheduler 会将数据存入寄存器,然后将着色器代码存入指令缓存,并要求指令分配单元(DispatchUnit)从指令缓存中读取指令分配给核算中心(ALU)履行。

Warp 中的一切线程履行的是相同的指令,假如遇到分支,那么就或许会呈现线程不激活履行的状况(例如当时的指令是 true 的分支,可是当时线程数据的条件是 false),此刻线程会被讳饰(masked out),其履行成果会被丢掉掉。shader 中的分支会显著添加时刻耗费,在一个 warp 中的分支除非 32 个线程都走到 if 或许 else 里边,不然相当于一切的分支都走了一遍。

线程不能独立履行指令而是以 warp 为单位,而这些 warp 之间才是独立的。warp 是 GPU 履行的最小单位。假如一个 shader 对应的像素数量填不满 32 个线程,它也会占用一个 warp 来履行。这种是显着 warp 运用率低的状况,部分中心在作业,部分中心在陪跑。

一个 Warp 中的像素线程能够来自不同的图元。只不过其 shader 指令是共同的。

Warp 中的线程数量和 SM 中的 CUDA core 数量并纷歧定是共同的。彻底能够 Warp 为 32,可是 CUDA core 只要 16 个。这种状况下,每个 core,两个 cycle 完结一个 warp 的核算就行了。PowerVR 便是相似的规划。

  • Stall 和 Latency Hiding (推迟躲藏)

指令从开端到结束耗费的 clock cycle 称为指令的 latency。推迟一般是由对主存的拜访发生的。比方纹路采样、读取极点数据、读取 Varying 等。像纹路采样,假如 cache missing 的状况下或许需求耗费几百个时钟周期。

CPU 经过火支猜测、乱序履行、大容量缓存等技术来躲藏推迟。而 GPU 则是经过 warp 切换来躲藏推迟。

对 CPU 而言,上线文切换是一个有显着开支的行为。所以 CPU 是尽或许防止频频的线程切换的。而 GPU 在 Warp 之间切换简直是无开支的,所以当一个 Warp stall 了,GPU 就切换到下一个 Warp。等之前的 Warp 取得需求的数据了,再切换回来持续履行。

超强总结!GPU 渲染管线和硬件架构

之所以能够完结这个机制,得益于 GPU 的大寄存器规划。GPU 中的寄存器数量远超 CPU。比方前文说到的费米架构的 GPU,每个 SM 的寄存器是 3 万个。而每个线程能够运用的最大寄存器数量约束在 255。所以即使每个线程都占满寄存器,也只耗费了总寄存器数量的四分之一。

每个 SM 会被一起分配多个 warp 来履行,warp 一旦进入 SM 内就不会再脱离,直到履行结束。每个线程会在一开端就分配好所需的寄存器和 Local Memory。当一个 Warp 发生了 Stall,GPU 的 Core 会直接切换到别的的 warp 来履行。由于不需求保存和康复寄存器状况,所以这个切换简直没有成本,能够在一个 cycle 内完结。

SM 中 warp 调度器每个 cycle 会挑选 Active warp 送去履行,一个被选中的 warp 称为 Selected warp。没被选中,可是现已做好预备被履行的称为 Eligible warp,没预备好要履行的称为 Stalled warp。warp 适宜履行需求满意两个条件:32 个 CUDA core 有空以及一切当时指令的参数都预备就绪。

假如 Shader 里边运用的变量越多(Shader 写的很长),占用的寄存器数量就越多,留给 Warp 切换的寄存器就会变少。分配给 SM 的 Warp 数量就削减了,也便是 Active warp 下降了。这会下降 GPU 躲藏推迟的才干,会下降 GPU 的运用率。

关于 GPU 的履行进程,知乎上洛城的这篇答复十分风趣,生动形象的展现了 GPU 的硬件构成和常见概念。假如对其还不了解的同学,强烈引荐阅览。

  • Warp Divergence

由于 Warp 是锁步履行的,Warp 中的 32 个线程履行的是相同的指令。当咱们的 shader 中有 if-else 的时分,假如 Warp 内有的线程需求走 if 分支,有的线程需求走 else 分支,就会呈现Warp divergence。GPU 对此的处理方法是,两个分支都走一遍,经过 Mask 遮蔽掉不要的成果。

假如 Warp 内一切线程都走的是分支的一侧,则没有太大影响。所以动态分支就相当于两条分支都走一遍,对功用影响较大,而静态分支则还好。当然,实践状况或许还会愈加杂乱一些,后边会再具体评论。

超强总结!GPU 渲染管线和硬件架构

八、其他重要概念

  • Pixel quad

光栅化阶段,栅格离散化的粒度尽管终究是像素级,可是离散化模块输出的单位却不是单个像素,而是 Pixel Quad(2×2 像素)。其中原因或许是单个像素无法核算 ddx、ddy,然后在 PS 傍边判别选用贴图的 mipmap 层级会发生困难。进行 EarlyZ 断定的最小单位也是 Pixel Quad。

超强总结!GPU 渲染管线和硬件架构

如上图所示,能够看出像素点网格被划分成了 2X2 的组,这样的组便是 Quad。一个三角形,即使只覆盖了一个 Quad 中的一个像素,整个 Quad 中的四个像素都需求履行像素着色器。Quad 中未被覆盖的像素被称为”辅佐像素”。比较小的三角形在烘托时,辅佐像素的比例会更高,然后形成功用糟蹋。

请留心,辅佐像素其实仍然在管线内参与整个 PS 核算,只不过核算成果被丢掉罢了。而又由于 GPU 和内存之间有 cache line 的存在,cache line 一次交流的数据巨细是固定的,所以这些被丢掉的像素许多时分也不会节约带宽。他们会原样读入原样写出,带宽耗费仍是那么多。所以,尽或许防止许多小图元的制造,能够更有用的运用 Warp。

  • EarlyZS

Depth test 和 Stencil test 是一个硬件单元(ROP 中的硬件单元)。Early depth test 的阶段相同是能够做 Early stencil test 的。所以许多文档会描绘这个阶段为 Early ZS。Early-Z 技术能够将许多无效的像素提早除去,防止它们进入耗时严峻的像素着色器。Early-Z 除去的最小单位不是 1 像素,而是像素块(Pixel Quad)。

传统的烘托管线中,depth test 在像素着色器之后进行。进行深度测验,发现自己被遮挡了,然后丢掉掉。这显然会呈现许多的无用核算,由于 overdraw 是不行防止的。因而现代 GPU 中运用了 Early-Z 的技术,在像素着色器履行之前,先进行一次深度测验,假如深度测验失利,就不必进行像素着色器的核算了,因而在功用上会有很大的进步。

AlphaTest 会影响 EarlyZ 的履行。一方面是自身不能履行 EarlyZ write 操作,由于只要当像素着色器履行结束之后才知道自己要不要丢掉,假如提早写入深度会有过错的履行成果。别的一方面只要当自己履行完像素着色器,写入深度之后,相同方位的后续片元才干持续履行,不然就有必要堵塞等候其回来成果,这会堵塞管线。关于这一点后边还会再做具体剖析。

其他如在像素着色器里边修正深度,或许运用 Alpha to coverage 等,也会影响 EarlyZ 的履行。

  • Hierarchical-z 和 Tile-based Rasteration

这两个是硬件供给的优化。

Hierarchical Z-culling,也称为 Z-cull。是 NVIDIA 硬件支撑的粗粒度的裁剪计划。有点像 Adreno 的 LRZ 技术,经过低分辨率的 Z-buffer 来做除去。不过它只准确到 8×8 的像素块,而非像 LRZ 相同能够准确到 Quad(2×2)。移动渠道 GPU 有其他技术做裁剪除去,所以猜想是没有运用这个技术的。别的,不要把它和 EarlyZ 弄混,也不要把它和咱们引擎完结的 Hi-Z GPU Occlusion Culling 弄混。

Tile-based Rasteration 技术。光栅化也是能够 Tile-based,这相同是硬件厂商的优化技术。光栅化阶段一般不会成为功用瓶颈。不过游戏功用优化杂谈中介绍了一个风趣的案例,原神中对树叶运用 Stencil,希望经过抠图完结半透明作用来进步功用。可是却由于影响了 Tile-based Rasteration 的优化,反而导致功用下降。在 PC 渠道本来会有一些优化习惯,比方经过 discard 或许其他手段除去掉像素,防止其进入到像素着色器(削减核算)或许 ROP(削减拜访主存)阶段,以此来进步功用。不过这些习惯在移动渠道一般都是负优化。

许多小三角形制造是 GPU 十分不擅长的作业情形。GPU 对极点着色器和光栅化的优化手段有限,又由于光栅化的输出是 PixelQuad,那么许多像素级的小三角形就必然会导致 warp 中的有用像素大大削减。所以 UE5 的 Nanite 会运用 ComputeShader 自己完结软光栅,来代替硬件光栅化处理这些像素级的小三角形,以此取得几倍的功用进步。相关细节能够参阅UE5 烘托技术简介这篇文章。

  • Register Spilling 和 Active Warp

GPU 中心的寄存器尽管许多,可是数量仍是有限的。GPU 中心履行一个 Warp 的时分,会在一开端就把寄存器分配给每条线程。假如 Shader 占用的寄存器过多,那么能够分配到 GPU 中心来履行的 Warp 就更少。也便是Active Warp下降。这会下降 GPU 躲藏推迟的才干,然后影响 GPU 的功用。比方,本来在一个 Warp 加载纹路发生 Stall 的时分,会切换到下一个 Warp,假如 Active Warp 过少,就或许一切 Warp 都在等候纹路加载,那么此刻 GPU 中心就真的发生 Stall 了,只能空置等候成果回来。

寄存器文件会用多少,在 shader 编译完就承认了。每个变量、临时变量、部分契合条件的 uniform 变量,都会占用寄存器文件。假如 Shader 运用的寄存器文件过多,比方超越 64 或许 128,会发生愈加严峻的功用问题,便是Register Spilling。GPU 会将寄存器文件存储到 Local Memory 上,之前咱们介绍过,LocalMemory 便是主存的一块儿区域,拜访速度是很慢的,所以 Register Spilling 会大大下降 Shader 的履行功用。

Shader 占用的寄存器文件多少,指令数多少,是否发生 Spilling,都能够运用 Mali offline compiler 检查。

  • Mipmap

咱们传递给 GPU 一个带 Mipmap 的纹路,GPU 会在运行时经过(ddx, ddy)偏导选取适宜的 Mipmap Level 的纹路。

Mipmap 有利于节约带宽,并不是说咱们传递给 GPU 的纹路数据变小的(相反是添加了)。而是终究烘托的时分相邻的像素更有或许在一个 CacheLine 里边,这就进步了 Texture cache 的命中率。由于削减了对主存的交互,所以削减带宽。

前面咱们介绍 GPU 内存的时分有说到,当需求拜访主存的时分,需求耗费几百个时钟周期。这会发生严峻的 Stall。进步 Texture Cache 命中率就能够削减这种状况的呈现。咱们经过一些 GPU 功用剖析工具优化游戏功用的时分,Texture L1/L2 Cache Missing 是一个十分重要的方针,一般要操控在一个很低的数值才是合理的。

Mipmap 自身是会多耗费 1/3 的内存的(多了初等级的 mipmap 图),不过咱们是能够决议纹路 Upload 给 GPU 的最高 mipmap level。咱们经过引擎动态操控纹路的最高 mipmap level,反而能够有用的操控纹路的内存用量,这便是 Unity 引擎的 Texture Streaming 机制。依据 Texture Streaming,纹路的内存总量是固定的,把不重要的纹路换出成高 level 的 mipmap 就能够削减纹路的内存占用。当然假如从头切换到 mipmap0,或许会有纹路加载的进程,不过这个是引擎内部完结的,上层开发者是无感知的。咱们看到许多 3D 游戏图片会有从模糊到明晰的进程,有或许便是 Texture Streaming 在起作用。

关于纹路的内存占用这儿能够再做补充说明。前面介绍移动渠道 GPU 内存的时分咱们有说到,尽管 CPU 和 GPU 是共用一块儿物理内存,可是其内存空间是分离的。所以纹路提交给 GPU 是需求 Upload 的。当纹路 Upload 给 GPU 之后,CPU 端的纹路内存就会被释放掉。这种状况下,咱们将显存中的纹路的内存释放掉,也就相当于释放掉纹路内存。

在 Unity 中,还有一部分纹路是需求 CPU 端读写数据的,或许编辑器下某个纹路导入选项中勾选了 Read/Write Enabled。对这些纹路而言,CPU 端的内存就不会被释放掉。此刻该纹路就占用了两份内存,CPU 一份,GPU 一份。

  • 纹路采样和纹路过滤

纹路过滤有几种方式:

1.临近点采样 Nearest Point Sampling

2.双线性插值 Bilinear Interpolation

3.三线性插值 Trilinear Interpolation

4.各向异性过滤 Anisotropic Filter

现代 GPU 都支撑一个 cycle 内完结一个 Bilinear 的采样。从功用上说,Point Sampling 和 Bilinear Filtering 是相同的。较新的高端 GPU,如 Mali-G78 能够在 0.25 个 cycle 完结一个 Bilinear 的采样。也便是说它能够在一个 cycle 内完结一个 Quad 的采样。

Trilinear Filtering 需求采样两层 mipmap 做插值,所以耗费是 Bilinear 的两倍,也便是两个 cycle 一个采样。

N 倍各向异性便是 N 倍开支。

九、从硬件角度了解 GPU 的履行逻辑

  • GPU 中的可编程元件和固定管线元件

1.极点和像素处理是可编程,在 Shader Core 中履行着色器指令。

2.光栅化是不行编程的,由光栅化引擎担任。

3.EarlyZ、LateZ、Blend,是固定管线,由 ROP 单元担任。

4.固定管线的单元担任特定作业,硬件制造愈加简略,功用更好,功耗更低。

  • 从硬件角度看 EarlyZ

咱们在游戏引擎等级看,每个 drawcall 有它对应的 RenderState,以此来决议是否是 AlphaBlend、是否要写深度、是否是 AlphaTest 等等。可是关于硬件而言,每个图元并不知道自己是不是 AlphaBlend。当时 RenderState 是 AlphaBlend 的话,那么图元就依照 AlphaBlend 制造。当时的 ZWrite 是 Off 的,那么 LateZ 就不写深度。

履行 EarlyZ 的是硬件单元(ROP),所以不应该用代码的思维去了解 EarlyZ 的履行进程,更恰当的比方应该是流水线上的阀门,它能够操控片元是否经过。有一些咱们用软件完结起来清楚明了的算法,在硬件上却是十分贵重,难以完结的计划。

  • GPU 中心的乱序履行和保序

GPU 的核算中心是乱序履行的,不同 Warp 履行耗时不共同。受分支、cache missing 等因素影响。GPU 会尽或许填充任务到核算中心。

可是同一个像素的写入次序是能够得到保证的。先履行的 drawcall 的像素必定是先写入到 Framebuffer 中的。不同像素的写入次序一般也是有序的。

GPU 在每个阶段的输出成果其实都是有序的。不同阶段之间,经过 FIFO 行列,保证次序。跟着技术的开展,或许运用的技术不限于 FIFO,可是终究意图都是保序。

这个机制是有现实意义的。关于半透明物体,假如 ROP 是乱序的,那么得到的是过错的成果。而关于不透明物体,尽管有 Depth Test 的机制,乱序也能够保证成果正确,可是保序对功用有优点,且能够缓解 Z-fighting。

超强总结!GPU 渲染管线和硬件架构

移动渠道 GPU 架构

一、PowerVR 架构

  • PowerVR GPU 管线

超强总结!GPU 渲染管线和硬件架构

A10 之前(iPhone7),都是 Imagination PowerVR 的 GPU,GPU 架构能够参阅 Imagination 的文档。A11 (iPhone8/iPhoneXR)开端运用的是苹果自研 GPU,苹果应该是得到了 Imagination 的授权,所以 HSR 等特性仍然是保存的。苹果自研的 GPU 相关材料较少,暂时了解为是 PowerVR 的增强衍生版本。

TBDR 的榜首步 Tiling 的成果存放在 Parameter Buffer 中。Parameter Buffer 是 System memory 上的一块儿数据区。它的巨细是有限的。所以或许会呈现 PB 现已被填充满,可是还有 drawcall 未履行的状况。当这种状况呈现的时分,硬件会进行 Flush,以便后续的烘托能够持续履行下去。这带来的问题是 Flush 前和 Flush 后的对象是两次 HSR 处理,即使存在遮挡也无法合理的进行除去,导致 overdraw 添加。当 PB 填充满的时分,或许会导致功用急剧下降。所以应该简化场景,防止呈现这种状况。

GPU 经过 ISP 单元进行 HSR (Hidden Surface Removal,隐面除去)处理。ISP 相同会处理深度回读(Visibility feedback)的状况。HSR 是 EarlyZ 的彻底代替品。能够像素级的除去被遮挡的片元。HSR 处理成果存放在 TagBuffer 中,TagBuffer 在片上缓存里,经过 TagBuffer 就能够得到终究需求制造的片元。只要终究对屏幕发生奉献的像素才会被制造。

关于 AlphaTest 对 HSR 的影响能够参阅后边专门对 AlphaTest 的评论简略说便是 AlphaTest 不会影响自身的除去断定,可是会卡管线。它会打断 ISP 处理覆盖同一像素的几许体。ISP 要得到 PS 履行后的成果才干正确进行 HSR,在这一进程中,一切覆盖了带有 discard 操作像素的几许体悉数都要等候。

  • PowerVR GPU 硬件架构

超强总结!GPU 渲染管线和硬件架构

PowerVR Rouge 架构的 GPU 包括了 N 个Unified Shading Cluster,这个 USC 便是 GPU 的中心。每个 USC 包括 16 条 Pipeline。每个 Pipeline 包括 N 个 ALU。ALU 便是真实履行指令的当地。ALU 的数目是 GPU 功用的重要方针。

从上图咱们能够看到 PowerVR 是两个 USC 同享一个 Texture Unit。

图中的 MCU 便是 L2 缓存。每个 USC 和 TU 都装备独立的 L1 缓存。每个 USC 中还都有一块儿 Tile Memory 也便是咱们之前说的 On-chip memory。

超强总结!GPU 渲染管线和硬件架构

Rouge 架构中,每个 Pipeline 包括 4 个 FP16 的 ALU,2 个 FP32 的 ALU 和 1 个 SFU。能够看到 FP16 和 FP32 的 ALU 是分离的。尽管这会占用更多的芯片面积,可是能够大幅削减功耗。FP16 速度更快,占用带宽更小,功耗更小。

PowerVR 是 Scalar ALU。支撑超标量(Super Scalar),能够一起发射多条指令,让闲暇的 ALU 来履行。所以一个 Pipeline 内部的多个 ALU 是能够被充分运用起来的。

每个 Pipeline 内多个 ALU 的规划跟其他 GPU 也有些不相同。比方 NVIDIA 的 CUDA Core 就只要两个 ALU,一个 FP32 ALU 和一个 INT ALU。所以尽管 PowerVR GPU 的中心(USC)数量尽管不多,可是 ALU 数量反而会比同层次 GPU 要多。

PowerVR 的 Warp 是 32 巨细。可是它的 Pipeline 是 16。所以它是两个 cycle 处理一个 Warp。

二、Mali 架构

  • Mali GPU 管线

超强总结!GPU 渲染管线和硬件架构

超强总结!GPU 渲染管线和硬件架构

Mali GPU 中有两条并行的管线,Non-Fragment(处理 Vertex Shader、Compute Shader)和 Fragment(处理 Fragment Shader)。

上面的图中能够承认,FPK (Mali 的隐面除去) 是在 EarlyZ 之后的。

Execution Core 中,有 WarpManager 担任 Warp 的调度。指令履行单元有 FMA(fused multiply-accumulate,混合乘加,根底浮点核算)、CVT(convert,类型转化)、SFU(special functions unit,特殊函数核算)三个元件。在 Valhall 架构下支撑 SuperScalar,这三个元件是能够并行履行指令的。

  • Mali GPU 四代架构演变

Mali GPU 的架构演变十分直观的展现了移动 GPU 的进化进程。再加上 Mali 的开发材料比较多,所以这儿别离介绍了 Mali 的四代架构。这儿能够和上文介绍的 GPU 管线和硬件架构的理论构成参阅和对照。

Utgard (2007)

这是 Mali 的榜首代架构。对应 Mali-4xx 系列的 GPU。

这代 GPU 并非 Unified shader core,Vertex 和 Pixel 运用不同的核算单元。在市面上简直看不到了。

Midgard (2012)

超强总结!GPU 渲染管线和硬件架构

Midgard 是 Mali 的第二代 GPU 架构,见于 Mali-T8xx, Mali-T7xx 和 Mali-T6xx。市面上并不多见了,或许在智能电视芯片中还可见到。
Midgard 的 Shader 中心现已是 Unified shader core。指令履行单元叫做 Tripipe,内部包括三个单元:
1.ALU(s) — Arithmetic Pipeline,履行指令的当地。或许有 2~3 个。
2.Texture Unit,配有 L1 缓存。
3.Load/Store Unit,配有 L1 缓存。
Midgard 是 Vector processor,经过 SIMD 完结并行核算的。此刻并没有 Warp 机制。运用 128-bit wide 的 ALU 进行核算。能够混合处理不同类型的数据,比方 4 个 FP32,8 个 FP16 或许 16 个 INT8。
网上或许会见到的在 shader 中做 vector 处理兼并数据来进步功用,对应的便是 Madgard 这种 Vector 处理器。这种优化措施对后边的 Scalar 处理器现已不再适用。
Midgard 的 Shader Core 是以指令级并行(ILP,Instruction Level Parallelism)为主的规划,采用的是超长指令字(VLIW)指令格局。为了最大程度地运用 Midgard 的 Shader Core,需求提取尽或许多的指令(4 条 FP32 并发指令),以便填充 Shader Core 中的一切槽。这种规划十分适宜基本的图形烘托作业,由于 4 种色彩分量 RGBA 十分适宜 VLIW-4 规划的 4 条通道。
跟着移动 GPU 技术的开展,解决计划逐步向标量处理转移,即以线程级并行(TLP,Thread Level Parallelism)为中心的体系结构规划。这也正是其下一代 Bifrost 架构的开展方向。指令向量化纷歧定能够完美履行,或许有的标量无法向量化,导致时钟周期的糟蹋。新的规划不会从单个线程中提取 4 条指令,而是将 4 个线程组合在一起并从每个线程中履行一条指令。以 TLP 为中心规划的优势在于它无需花费许多精力即可从线程中提取指令级并行性。一起对编译器也愈加友爱,编译器能够完结的愈加简略。
Bifrost (2016)

超强总结!GPU 渲染管线和硬件架构

Bifrost 是 Mali 的第三代架构 GPU,见于 Mali-G71、G72、G76 和 Mali-G5x。
Mali 的着色器中心数量是可变的。从上面的 die shot 能够看到,Mali-G76MP10 包括 10 个 Shader core,MP 代表了中心数量。不同中心数量功用差异十分大。所以相同是 Mali-G72 架构,Mali-G72MP12 能跑规范画质,而 Mali-G72MP3 就只能跑流畅画质了,其功用乃至还不如 Mali-G71MP8。

超强总结!GPU 渲染管线和硬件架构

超强总结!GPU 渲染管线和硬件架构

Bifrost 每 Shader core 包括 3 个 Execution Engine(指令履行单元),中低端的 Mali-G5x 或许每个 Shader core 只包括 2 个 EE。
Bifrost 的履行中心不再是 Tripipe 结构。Bifrost 把 TextureUnit 和 L/S Unit 从 Execute Engine 中拆分开了。变成 Shader core 中的独立单元。这样能够防止负载不均衡导致 TU 的才干被糟蹋,一起也更简略扩展 ALU,增强 GPU 的核算才干。
从这一代开端,Mali GPU 从 Vector 处理器转变为 Scalar 处理器。对应的也参加了 Warp 机制。一个 Warp 是 4 个线程,Mali 称其为 Quad。比较于 NVIDIA 或许 PowerVR 的 32 线程 Warp,Bifrost 的 Warp 要小许多。Warp 小,那么呈现上文介绍的Warp Divergence的时分就能够防止糟蹋,也便是说 if-else 分支对其影响较小。不过 Warp 小,意味着需求更多的操控单元,比方 32 线程的 Warp 只需求 1 个操控单元,而到 Mali 这边就需求 8 个操控单元。操控单元过多,会占用更多的晶体管和芯片面积,约束了 ALU 的数量。一起也意味着更多的功耗。

Mali-G76 在此架构根底上添加了一条通路,每个 EE 能够一起处理 8 条线程,也便是说 Warp 巨细扩展为 8 了。架构没有改变,把 Warp 进步一倍,就带来了大幅的功用进步。可见之前 4-wide warp 的规划并不是正确的挑选。

Mali GPU 的 ALU 是不区别 FP32 和 FP16 的。像 Midgard 相同,GPU 会做指令的分解和融合。其 ALU 每个时钟周期能够处理 1 个 INT32、2 个 INT16 或 4 个 INT8。像 PowerVR,FP32 和 FP16 ALU 是独立的,这也是在空间和功率功率之间进行权衡的挑选。独立 ALU 会占用更多的芯片面积,可是会削减功耗。从成果上看,PowerVR 的规划愈加合理,后边会介绍的 Mali-G78 重写的 FMA(Fused-Multiply-Add)也修正为 FP32 和 FP16 独立 ALU 的规划了。

Valhall (2019)

超强总结!GPU 渲染管线和硬件架构

超强总结!GPU 渲染管线和硬件架构

这是 Mali 最新的 GPU 架构,Mali-G77、G78 以及最新推出的 G710 都是这个架构。对应的中低端架构为 G5XX。
从这一代开端,不再是每个 Shader core 三个 Execution Engine 了。而是一个 Execution Engine。不过 EE 改善为两个 16-wide 的结构。也便是说从 Mali-G77 开端 Warp 巨细修正为 16 了。一起这代开端的 GPU,都是 Super Scalar 规划,能够更好的运用 ALU 闲暇单元,进步流水线功用。

超强总结!GPU 渲染管线和硬件架构

前面有说到,G78 这一代,重写了 FMA 引擎,其 ALU 也变为 FP32 和 FP16 独立元件了。
现在衡量 GPU 功用的一个重要方针是 Floating-point Operations 的才干。结合 GPU 中心的时钟频率就能够得到 FLOPS(Floating-point Operations Per Second),也便是咱们在跑分软件里边看到的 GFLOPS。需求留心的是,FMA(Fused-Multiply-Add,a x b + c)或许叫 MAD,也便是乘加,一次履行记做两个 FLOP。

下面罗列不同架构,单中心每个时钟周期的 FP32 operations 数量(FP32 operations/clock)。

1.Mali-G72 是 3 x 4 x 2 = 24

2.Mali-G76 是 3 x 8 x 2 = 48

3.Mali-G77 是 16 x 2 x 2 = 64

4.Mali-G710 是 16 x 2 x 2 x 2 = 128

能够看到,Mali-G77 尽管只要一个 EE,可是核算才干比较 G72 和 G76 却大幅进步。一起由于操控单元就更少,其操控单元的 overhead 就更少。履行相同的运算的功耗就更低。最新的 Mali-G710,架构不变,EE 扩展为两个,功用再次大幅进步。

上述数据能够在这儿[Arm Mali GPU datasheet](developer.arm.com/-/media/Arm… Community/PDF/Mali GPU datasheet/Arm Mali GPU Datasheet 2021.2.pdf?revision=82e2cd30-98cd-4a10-bbe9-70ab4ce1e7d3&hash=35BF874DD8C0F48BCBE09AA50719CDEB933DA4BE) 下载到。这个文档包括 Mali 每个 GPU 的具体数据,比方 Warp 巨细,L1 cache 巨细等等。

  • Mali GPU 其他技术
Forward Pixel Kill

FPK 是 Mali 的隐面除去技术。经过 EarlyZ 的片元会进入到一个 FIFO 的 FPK 行列。假如后边的片元发现前面的片元被遮挡住了,那么就能够将其停止掉。正常状况下一个片元经过 EarlyZ 断定之后,就发生了 pixel thread,会提交给 Shader core 来履行,这个 thread 是无法停止的。不过 Mali 的 FPK 技术却能够停止掉不需求制造的 thread,然后防止 overdraw。

比较 PowerVR 的 HSR,FPK 更像是一个 EarlyZ 的硬件补丁,来补偿 EarlyZ 的缺乏。当 EarlyZ 失效的时分,FPK 必定也是失效的。FPK 并不能保证被遮挡的不透明像素必定不会被制造(比方行列中的 Fragment 现已被处理了)。所以 Mali 引荐仍是运用排序的方法以充分运用 EarlyZ 进行除去。这篇官方文档的优化主张中也说到了排序的问题。尽管有 FPK,可是不要过火依靠它,该排序仍是要排序的。

IDVS: Index-Driven Vertex Shading

Vertex shading 被拆成两个部分,Position Shading 和 Varying Shading。核算完 position shading 就能够进行裁剪,只要经过裁剪的图元才会履行 varying shading。这样就被裁掉的图元就不用 fetch 各种特点乃至纹路了。

超强总结!GPU 渲染管线和硬件架构

所以关于 Mali GPU 而言,把 Mesh 的 position 独自拆分一个 stream 能够有用节约带宽。其他 GPU 应该也有相似的技术。而对高通的 Adreno 而言,由于 LRZ 需求先跑一遍 VertexShader 中的 Position 部分,得到低分辨率深度图,所以对其而言拆分 position 能够取得更大的收益。

AFBC: Arm Frame Buffer Compression

AFBC 是 FrameBuffer 的快速无损紧缩。能够节约带宽,也能够下降显存占用。这个对开发者是无感知的。其他渠道也都有相似的紧缩技术。

Transaction Elimination

Transaction Elimination 也是一种很有用的下降带宽的办法。在有些状况下,只要部分 Tile 中的内容会变化(例如摄像机不动,一个 Tile 中只要静态物体)。此刻经过比较该 Tile 前一次和本次的烘托成果的 CRC 值,可得到 Tile 是否变化,假如不变,那么就没有必要履行 Tile 到 System Memory 的写回操作了,有用地下降了带宽占用。

Hierarchical Tiling

依据图元的巨细挑选适宜的 Hierarchy Level 的 Tile。下降 Tiling 阶段对主存的读取和写入开支。

Shared Memory

Mali GPU 没有 Shared Memory。一切对 Shared Memory 的操作其实都是寄存器或许主存。不过这个影响比较大的是 OpenCL 核算或许 ComputeShader。一般的 shader 也操作不了 Shared Memory。

Adreno GPU 有 Shared Memory。

三、Adreno 架构

Adreno3xx, 4xx, 5xx, 6xx 是市面上常见的型号。都是 Scalar 架构。

1.3xx 在一些十分低配的手机上还能够见到。

2.5xx 一般见于中低配手机。这一代开端参加了 LRZ 技术。

3.6xx 是近几年新出的型号。630~660 是高配,如骁龙 888 装备是 Adreno660 芯片。

Adreno 一个十分显著的特点是它的中心数量很少,可是每个中心装备一个十分大的 GMEM,这个 GMEM 是 On-chip 的,巨细能够抵达 256k~1M。比方只要 Adreno630 只要 2 核,GMEM 巨细为 1MB。

超强总结!GPU 渲染管线和硬件架构

Adreno 上的 Bin(也便是 Tile)并不是固定巨细。而是依据 GMEM 巨细和 RenderTarget 格局决议。其巨细一般比 Mali 的 16×16 要大十分多。因而,假如烘托方针假如敞开 HDR+MSAA 的话,Bin size 会小许多,也就意味着更多的与主存的交互,显着添加功耗。

Flexable Render

Adreno GPU 一起支撑 IMR 和 TBR 两种方式,并且能够依据画面的杂乱度,在两者之间动态切换,这便是 Flexable Render 技术。当然,在移动渠道 TBR 仍然是更为高效的方法。

Low Resolution Z

LRZ 是 Adreno5xx 系列开端参加的隐面除去技术。经过先跑一遍 Vertex Shader 的 position 部分,生成低精度的深度图,进行裁剪除去。相当于硬件等级做了 Hi-z。这个除去是能够准确到 Pixel Quad 的。

四、总结

1.Mali 的 Warp 是 16,PowerVR 是 32。Adreno 没有找到相关材料,大概率是 32。

2.PowerVR、Adreno 和 Mali 最新的 Valhall 都是 Scalar 架构,支撑 SuperScalar,能够更好的运用 ALU 并行核算。

3.隐面除去技术,PowerVR 是 HSR,Adreno 是 LRZ,Mali 是 FPK。

4.把 Mesh 的 position 独自拆分一个 stream,有利于节约带宽。

5.Shader 中运用 mediump,半精度浮点数,能够更好的运用 FP16 的 ALU,功用更好。

6.Tile 巨细跟 RenderTarget 格局有关。Adreno 的 GMEM 很大,Tile 也要比 Mali 和 PowerVR 要大许多。

超强总结!GPU 渲染管线和硬件架构

常见问题的剖析与评论

一、DrawCall 对功用的影响

超强总结!GPU 渲染管线和硬件架构

GPU 作业在内核空间(Kernel Space),咱们只能经过驱动与其打交道。所以咱们应用层设置一个烘托指令或许给 GPU 传输数据,需求经过图形 API 和驱动的中转,才干终究抵达 GPU。并且驱动调用会有用户空间(User Space)到内核空间的转化。当 DrawCall 十分大的时分,这儿的 overhead 就会很高。

以 DX 为例,程序提交一个 DrawCall,数据需求经过 App->DX runtime->User mode driver->dxgkrnl->Kernel mode driver->GPU,然后才干抵达 GPU。在抵达 GPU 之前,悉数是在 CPU 上履行的。这也是新的 DX12 企图下降的开支。

主机渠道的硬件是固定的,能够对其硬件和驱动做专门的优化。没有了驱动的层层中转,CPU 和 GPU 交互的开支是很低的。所以即使主机的硬件功用不如 PC,可是实践游戏功用却远超 PC。

当然,单纯的 DrawCall 指令(比方 DrawPrimitive)开支也不会很大。更大的开支在于 DrawCall 顺便的绑定数据(buffer、texture、shader),设置烘托状况的开支。在 RenderDoc 能够看到一个 DrawCall 实践上或许会有十几条指令。

在 Unity 中,绑定 Vertex buffer 记做一个Batch。CPU 和 GPU 的交互方式,愈加擅长一次传输许大都据,而不是多次传输少量数据。Unity 的 StaticBatch 和 DynamicBatch 的意图就在于此。现在 Unity 都是以 Batches 数目代指 DrawCall 数目。

原料切换记做一个SetPassCalls。原料切换会面对许多的特点同步、shader 的编译和绑定、纹路绑定等等,不管在引擎层面仍是 GPU 交互层面都是巨大开支。现代引擎经过排序,让相同烘托状况的物体连续制造,意图便是削减这一部分的开支。

假如制造许多小物体,很有或许许多时刻耗费在 CPU 和 GPU 的交互上,而实践 GPU 自身的负载并不高。所以咱们一般以为DrawCall 过高或许会导致 CPU 呈现瓶颈。

二、AlphaTest 和 AlphaBlend 对功用的影响

事实上,本文呈现的初衷便是答复这个问题。由于要测验 PreZ 对功用的影响(PreZ 便是针对 AlphaTest 和 AlphaBlend 的优化计划),连带着要测验下 AlphaTest 和 AlphaBlend 对不同渠道的功用影响。看了网上的一些评论和文章,成果变得愈加困惑了。许多疑问无法答复,比方 EarlyZ 的完结机制,什么时分 EarlyZ 会失效等等。终究促使我花了许多的时刻精力去学习 GPU 的硬件架构,这才有了这篇文章。

  • 桌面渠道

桌面渠道的 IMR 架构上,AlphaBlend 操作的是 DRAM(读+写),假如运用过多,会有显着的 overdraw 和带宽开支。比较起来,AlphaTest 假如 discard 了,PS 中不会有后续核算,也能够防止对 FrameBuffer 的写操作。假如制造次序合理(从前往后制造),未被 discard 掉的部分也能够有用遮挡住后续部分,能够减轻 overdraw。

所以在桌面渠道,许多时分会主张运用 AlphaTest 代替 AlphaBlend,这或许会带来功用的进步。

当然假如考虑到其对 EarlyZ 或许 Hierarchical Z 的影响(堵塞乃至失效,与具体硬件完结有关)。节约的带宽开支是否比上述优化带来的价值更大,是需求实践测验才干得出结论的。这或许会因 GPU 不同而发生不同的成果。

  • 移动渠道

比较有参阅价值的是下面两个知乎上的评论

1.再议移动渠道的 AlphaTest 功率问题

2.试说 PowerVR 家的 TBDR。文中摘引是Alpha Test VS Alpha Blend这儿的评论,算是比较官方的答复。

Alpha tested primitives will do the following: ISP HSR: Depth and and stencil tests (no writes) Shading: Colours are calculated for fragments that pass the tests Visibility feedback to ISP: After the shader has executed, the GPU knows which fragments were discarded and which where kept. Visibility information is fed back to the ISP so depth and stencil writes can be performed for the fragments that passed the alpha test When discard is used, pixel visibility isn’t known until the corresponding fragment shader executes. Because of this, depth and stencil writes must be deferred until pixel visibility is known. This reduces performance as the pixel visibility information has to be fed back to the ISP unit after shader execution to determine which depth/stencil positions need to be written to. The cost of this can vary, but in the worse case the entire fragment processing pipeline will stall until the deferred depth/stencil writes complete.

以 PowerVR 的 HSR 为例。不透明物体片元是在 HSR 检测经过就写入深度。而 AlphaTest 片元在 ISP 中做 HSR 检测的时分是不能写入深度的。由于只要像素着色器履行结束之后它才知道自己会不会被丢掉,假如被丢掉则不能写入深度。而假如没有被丢掉,则会将深度信息回写到 ISP 的 on-chip depth buffer 中。在深度回写结束之前,相同像素方位的后续片元都不能被处理。这就导致堵塞了管线的履行。

EarlyZ 也是相似的问题。或许前期 EarlyZ 和 LateZ 是共用硬件单元,读和写有必要是原子操作。AlphaTest 导致不能进行 EarlyZ write,也就不能进行 EarlyZ test。所以前期一些文档会描绘为 AlphaTest 导致 EarlyZ 失效,直到 Flush。现代 GPU 不存在这个问题。AlphaTest 物体不能做 EarlyZ write,可是仍然能够做 EarlyZ test。当然由于深度回读导致卡管线,是不行防止的。

独自一个 AlphaTest 和 AlphaBlend 比较,AlphaBlend 或许会比较快。由于它不存在的深度回读的进程,也不会堵塞后续图元制造。不过这个影响很有或许只在特定状况下才会比较显着。而愈加常见的状况是多层半透明叠加的状况。此刻 AlphaBlend 由于不写深度,彻底无法做除去,会导致 overdraw 很高,在移动渠道上很简略呈现功用问题。而 AlphaTest 尽管会由于写深度而堵塞管线,可是也由于会写深度,后续被遮挡的图元(不管是不透明仍是半透明)是能够被除去掉的。所以这种状况下 AlphaTest 或许功用会更好一些。而假如参加 Prez,AlphaTest 功用优势会愈加显着。所以草地烘托运用 PreZ + AlphaTest +(alpha to coverage)是比较合理的挑选。一般会比运用 AlphaBlend 有更好的功用表现。

不同的测验用例或许会得到不同的测验成果,而一般咱们的测验用例很有或许是利好 AlphaTest,所以会得出 AlphaTest 功用比 AlphaBlend 好的结论。当然,咱们过于深究 AlphaTest 和 AlphaBlend 的功用差异并没有太大意义。由于大都状况下这两者作用不同,不能互相替换。下面做一些总结。

1.不管是 AlphaTest 仍是 AlphaBlend,都不会影响其自身被不透明物体遮挡除去。RenderPass 中有 AlphaTest 物体,也不会导致后边不透明物体之间的遮挡除去。

2.关于比较小的特效,不要尝试用 AlphaTest 代替 AlphaBlend,这很有或许是负优化,或许会堵塞管线。

3.关于草地、树叶等交叉遮挡严峻的情形,运用 AlphaBlend 功用很低,应该运用 PreZ+AlphaTest。

4.Opaque–>AlphaTest–>Transparent 是合理的烘托次序,打乱这个次序或许会形成显着功用问题。

三、不透明物体是否需求排序

按上面的介绍。Opaque–>AlphaTest–>Transparent 是合理的制造次序。Opaque 和 AlphaTest 都是不透明物体行列,Transparent 是半透明物体行列。

特别要留心,AlphaTest 物体不能频频的和 Opaque 物体交叉制造(指的是烘托次序上,而不是物体坐标上),不然会严峻堵塞烘托管线。半透明物体不能说到不透明物体行列里边,即半透明物体不能交叉到 Opaque 物体制造,相同会导致严峻的功用问题,比方写深度的半透明物体假如在不透明物体之前制造,会导致 LRZ 整体失效。

对半透明物体而言,由于要进行混合,所以需求从远到近来制造(画家算法),不然会得到过错的制形成果。

对不透明物体而言,在没有隐面除去功用的芯片上(Adreno3xx),需求保证物体是从近到远进行制造,能够更好的运用 EarlyZ 优化,也便是说需求进行排序。而有隐面除去功用的芯片上(PowerVR、Areno5xx、Mali 大部分芯片),不关心物体的制造次序,不需求排序,不透明物体不会有 overdraw。

前文介绍 Mali 的 FPK 的时分有说到,FPK 并不能像 HSR 或许 LRZ 相同,对屏幕无奉献的像素必定会被除去。FPK 或许存在没有即时 kill 掉的状况。所以关于 Mali 芯片,引荐仍是在引擎层做排序。Unity 引擎中断定是否需求排序的代码:

bool hasAdrenoHSR = caps->gles.isAdrenoGpu && !isAdreno2 && !isAdreno3 && !isAdreno4;
caps->hasHiddenSurfaceRemovalGPU=caps->gles.isPvrGpu||hasAdrenoHSR;

这儿还需求留心,所谓排序,对半透明物体而言,便是依据物体与相机的间隔排序的。这是为了得到正确的烘托成果。当然即使依据物体排序,也仍是会有半透明物体烘托次序过错导致抵触的状况,比方较大物体互相交叉,或许物体自身部件之间互相交叉等等。

而关于不透明物体,则是分区块排序。在一个区块儿内部,物体制造次序跟与相机的间隔无关。这么做首要由于严格依照间隔排序,不利于合批,合批需求优先考虑原料、模型是否共同,而不是与相机间隔的远近。

四、PreZ pass/Depth prepass 是否有必要

PreZ pass 便是预先运用十分简略的 shader(敞开 ZWrite,封闭色彩写入)画一遍场景,得到终究的 Depth Buffer。然后再运用正常 Shader(封闭 ZWrite,ZTest 修正为 EQUAL,不履行 clip),来进行制造。这样只要终究显现在最上面的像素会制造出来,其他像素都会因 EarlyZ 被除去掉。

PreZ 的优点是下降 overdraw。坏处是多画了一遍场景(尽管运用的是最简略的 shader),DrawCall 翻倍,极点翻倍(极点着色器履行两遍)。由于一般咱们游戏的瓶颈都在于像素着色器,所以大大都状况下 PreZ 都是有优化作用的。

PC 渠道运用 PreZ pass 或许是个很好的挑选。一方面由于它没有移动渠道的各种隐面除去技术,别的依据 IMR 烘托,对 DrawCall 和极点数量不那么灵敏。所以 PC 渠道运用 PreZ pass 是很好的下降 overdraw 的手段。

移动渠道则恰好相反。移动渠道 GPU 都有各种隐面除去技术,不透明物体自身就不存在 overdraw。而 TBR 架构对 DrawCall 和极点数量反常灵敏,许多极点会导致更多的主存拜访,乃至会呈现主存 ParameterBuffer 放不下,发生 Flush 的状况。所以移动渠道不需求 PreZ pass。

假如实践测验发现,游戏的功用瓶颈在于 DrawCall 和极点着色器,那么就不要运用 PreZ,这会进一步增大极点压力。

回到咱们序言中说到的问题,咱们是否有必要对草地这样的物体运用 PreZ 呢?答案是有必要。草地是 AlphaTest,且有许多的交叉,不行防止的会有许多的 overdraw。经过 PreZ 能够很好的下降草地的 overdraw。一起,由于运用 PreZ 终究制造草地的时分是不写深度的,也没有 clip,那么就能够作为不透明物体来制造,不会像一般 AlphaTest 相同影响烘托管线的履行。

后边跟联发科工程师团队进一步沟通了解到,他们主张去掉 PreZ pass 的原因是在测验咱们游戏的进程中发现,在特定场景下 vs 很简略呈现瓶颈,而 ps 反而留有余地。咱们上面说到,PreZ 会添加极点数量,愈加简略呈现 vs 的瓶颈。当然,实践游戏运行进程会十分杂乱,或许略微换个视角或许换个手机就又是 ps 瓶颈了。怎么取舍仍是要以真机测验成果为准。

网上有一些文章说到,某团队运用 AlphaTest 代替 AlphaBlend 制造草地,又或许某 2D 游戏,运用 AlphaTest 制造角色,都取得了功用的大幅进步。其原因就在于咱们上面所剖析的,当游戏的 overdraw 很重,或许 ps 是瓶颈的时分,运用 AlphaTest 能够运用 EarlyZ 做除去,进步功用。当然,这儿要再次重申,必定要保证不透明、AlphaTest、半透明,这样的制造次序,假如 AlphaTest 或许半透明物体交叉到不透明物体之间制造的话,会严峻影响功用。

五、Shader 中的分支对功用的影响

  • 分支对功用的影响

同一个 warp 内履行的是相同的指令,当呈现分支(if-else)的时分,假如一切线程都走分支的一侧,则分支对影响很小。可是假如有些线程走 if 分支,有些线程走 else 分支。那么 GPU 的处理方法是,两条分支都走一遍,然后经过履行掩码(execution mask)丢掉不要的履行成果。这就带来了许多无意义的开支。这种状况便是咱们前面介绍的Warp Divergence。

超强总结!GPU 渲染管线和硬件架构

常量做分支条件,编译器会做优化,简直不会影响功用。

uniform 做断定条件,大都时分能够保证不会呈现 Warp Divergence,对功用也不会有太大影响。留心,并不能将不会有太大影响作为没有影响。运用分支的功用危险有许多,下文还会具体说明。

动态分支,如运用纹路采样的值做判别条件,大概率会发生 Warp Divergence,会严峻影响功用,尽或许防止。

  • 编译器对 shader 的优化

Unity 会运用 glsl optimizer 对 shader 做优化。大都时分会显着进步 shader 的履行功用。不过关于 if-else,它的默认处理方法是将分支打开,悉数核算一遍,依据判别条件取其中一个成果。除非分支中的指令很杂乱,或许有许多纹路采样,它才会保存分支代码。这个行为是能够经过 branch 和 flatten 关键字来操控的。Unity 中的 UNITY_BRANCH 和 UNITY_FLATTEN 就对应这两个关键字。

branch,shader 会依据判别语句只履行当时状况的代码。

flatten,shader 会履行悉数状况的分支代码,然后再依据判别条件取得成果。

unroll,for 循环是打开的,直到循环条件停止。

loop,for 循环不打开。

  • 分支的功用危险

许多 if-else 会导致 shader 指令数比较多,占用的寄存器就更多。这会导致 GPU 的 Active warp 数量下降,下降 GPU 躲藏推迟的才干。也有或许由于寄存器占用过多,发生 Register spilling(将寄存器写入主存)。

还有或许由于 shader 指令比较多,导致编译后的 bin 文件比较大,不利于缓存。

别的有的时分,咱们有一些核算或许纹路采样是无意中写在分支之外的,而核算成果只要分支中才会用得上。这种状况假如是 multi_compile,则编译器会自动做优化,精简掉不必要的代码。可是假如是 if 分支,则编译器不会优化这块儿的代码。这或许导致履行了比预期要多的指令或许进行了不必要的纹路采样。

动态分支(包括 uniform 分支),或许会不利于驱动进行优化。具体驱动完结过于黑盒,并且跟着驱动的迭代更新或许会不断改善。可是一般来说,假如咱们经过火支来替换 multi_compile,都会添加 shader 的履行开支。假如是常用 shader,或许游戏的 GPU 现已跑的比较满了,则分支的副作用不行忽略,特别是在低端机上。

某些驱动(常见于低端机),或许会在驱动等级对分支做“优化”,假如分支指令较少,会强制打开。可是分支的别的一个隐性开支在于参数传递导致的带宽添加。即使分支指令很少,带宽的添加也或许会成为压死骆驼的终究一根稻草。

Adreno3xx、4xx 部分驱动存在兼容性问题,运用 half 参数做断定条件履行 if 指令,永远无法断定为真。修正为 float 做断定条件即可。所以,兼容性测验也需求多加重视。

  • multi_compile 的副作用

假如不运用 if-else,那么别的一个挑选便是 multi_compile。遗憾的是,运用 multi_compile 相同会有显着的副作用。

1.添加了 keyword 的数量,keyword 自身是有限的。

2.添加了变体的数量,不同的变体其实是不同的 shader,这会导致 SetPassCalls 添加,影响运行时功用。

3.变体增多会发生更多的内存占用。并且 Shader 实践的内存占用或许会比咱们在 UnityProfiler 中看到的 ShaderLab 的内存占用更多。由于驱动会耗费两三倍的内存去办理 ShaderProgram。比较添加许多变体,挑选 if-else,并运用 const 或许 uniform 作为其断定条件有的时分是愈加抱负的挑选。

  • 关于分支的主张

1.尽量不要运用分支,如有必要运用的话,优先挑选常量的断定条件,其次挑选 uniform 变量作为断定条件。

2.最糟糕的状况是运用 shader 内部核算的值作为断定条件,尽或许防止。

3.尽或许防止在常用 shader,或许给低端机运用的 shader 中运用分支。

4.终究承认要运用分支,请保证两条分支不存在许多重复代码。许多重复代码会导致 shader 占用寄存器文件显着增多,削减 active warp 数量,终究导致功用下降。

5.假如分支指令较多,不要忘记添加 branch 关键字。

6.挑选分支仍是 multi_compile,必定要以实践游戏功用测验为准。特别要留心其对低端机 GPU 占用率的影响。

六、Load/Store Action 和 Memoryless

  • Load/Store Action

从 SystemMemory 复制数据到 TileMemory 是 Load Action。

从 TileMemory 复制数据到 SystemMemory 是 Store Action。也称为 Resolve。

OpenGLES 中能够经过 glInvalidateFramebuffer 来躲避上述 Load 和 Store。

Metal 中能够经过 RenderPass 的 loadAction 和 storeAction 的设置来操控 Load/Store。

loadAction 有三种,dontCare,load,clear。

storeAction 有四种,dontCare,store,multisampleResolve, storeAndMultisampleResolve。

比方后处理履行结束之后深度就没用了,那么就能够设置 depthTexture 的 RT 的 storeAction 为 dontCare。这样能够防止深度写回主存的带宽开支。

在 XCode 中能够看到烘托的 L/S bandwidth 的开支。经过抓帧能够清楚的看到每个 RenderPass 的 load/store action。这能够很便利的协助咱们优化烘托管线的功用。

  • Memoryless

像 Depth/Stencil buffer,只在 Tile 制造中有用,不需求存到主存中。所以其 storeAction 能够声明为 dontCare。这样能够节约带宽。

Metal 下,这样不需求 Resolve 的 RT,能够设置为 Memoryless,这样能够下降显存开支。

  • Render Target 切换

超强总结!GPU 渲染管线和硬件架构

上图能够看到,RenderTarget 的切换是十分慢的。在咱们游戏的烘托流程中,应该尽或许的防止频频的 RT 切换。

由于移动渠道 TBDR 的特性,切换 RT 在移动渠道上会有更大的开支。它会严峻堵塞烘托流水线的履行。每次切换 RT 都需求等候前面的指令悉数履行结束,把数据写入主存。切换到新的 RT 后,还需求把数据从主存 Load 到 TileMemory 中。频频的与主存交互不仅很慢,并且耗费许多带宽。所以相似后处理这样有必要运用 RT 的,应当把多个 Pass 尽或许兼并成一个 Pass。

当咱们运用 RenderTexture 的时分,必定要慎重。一方面它耗费了过多的内存。别的方面 RenderTexture 的制造更新不行防止的会有 RT 切换。假如过多运用,或许过于频频的更新,会呈现显着的功用问题,特别是在低端机上。当承认要运用 RenderTexture 的时分,必定要严格操控其巨细。

一个咱们常见的功用,将场景或许角色模型制造到一个 RenderTexture 上,然后将这个 RenderTexture 制造到 UI 上。这个进程其实就存在显着的功用危险。别的,有一些 UI 框架会做优化,将静态不变的 UI 制造到一个 RenderTexture 上来削减 DrawCall。假如不能保证 UI 真的彻底静止不动,在移动渠道上这么做一般是负优化。原因相同在于内存耗费和 RT 切换开支。

  • 防止 CPU 回读 GPU 数据

CPU 回读 GPU 数据(比方 glReadPixels)会严峻阻止 CPU 和 GPU 的并行。当 CPU 要读取 FrameBuffer 中的数据的时分,有必要要保证 GPU 现已悉数写入结束。

部分解决办法是,在下一帧的时分读取上一帧的数据。这样能够躲避等候的开支,不过毕竟是两帧数据,所以成果或许会有偏差。

  • Pixel local storage

Mali 和 Adreno 是供给了 API 来获取 TileMemory 中的数据。这样就能够高效的完结一些特殊作用,比方软粒子或许一些后处理作用。详情能够参阅 Pixel Local Storage on ARM Mali GPUs 。

iOS 渠道由于运用了 Memoryless,framebuffer_fetch 无法获取深度数据,可是能够经过一些其他手段,比方 MRT(Multiple Render Targets),或许把深度写入到 color 的 alpha 通道来完结相似功用。

  • 移动渠道推迟烘托优化

传统推迟烘托的 GBuffer 会带来许多的带宽开支,移动渠道上相同能够运用 OnChip Memory 来完结依据移动渠道的高功用推迟烘托。能够参阅移动端烘托管线完结与优化: 带宽和功耗优化。

而原神貌似直接运用传统的推迟烘托计划,并没有针对移动端做功用优化,所以它只能在高配手机上才干跑得动。带宽的开支可见一斑。这儿能够参阅探秘《原神》的移动端烘托技术。

七、MSAA 对功用的影响

  • MSAA

移动渠道的 MSAA 能够在 TileMemory 上完结 Multisampling,不会带来许多的拜访主存的开支,也不会大幅添加显存占用。所以移动渠道 MSAA 是比较高效的。经过指定 FrameBuffer 格局就能够敞开 MSAA,不需求经过后处理等计划来自己完结 MSAA。

可是这并不是说 MSAA 在移动渠道便是免费的了。它仍然是有必定开支的,所以也只能在高配手机敞开 MSAA。

在 Adreno 上,GMEM 巨细是固定的(256k~1M),而 Tile 巨细跟 RenderTarget 的格局有关。假如开了 MSAA,Tile 会对应缩小,这就导致发生更多的与主存的交互。敞开 HDR 会有更大开支也在于此。具体能够参阅移动端 GPU 的运作特性与 UE4 半透特效功用优化计划。

Mali 和 PowerVR 由于 TileMemory 有限,打开 HDR 与 MSAA 需求更多空间来保存烘托成果,GPU 只能够经过缩小 Tile 的尺度来习惯 On-Chip Memory 的固定巨细。进行烘托的 Tile 数量会因而而添加。比方 PowerVR 本来 Tile 是 32×32,假如敞开 MSAA 或许就变为 32×16 或许 16×16。下面的表格显现了 Mali Bifrost GPU,bits/pixel 和 Tile 巨细的关系。

Family 16×16 Tile 16×8 Tile 8×8 Tile
<= Bifrost Gen 1 128 bpp 256 bpp 512 bpp
>= Bifrost Gen 2 256 bpp 512 bpp 1024 bpp
  • Alpha to coverage

Alpha-to-coverage 简略了解便是依据 MSAA,运用 AlphaTest 来模仿 AlphaBlend 的作用。本来的 AlphaTest 不行防止的会有比较硬的边缘,经过 Alpha-to-coverage,像素的透明度是由 4 个像素插值核算得来的,边缘就会柔软许多。

运用 Alpha-to-coverage 的优点是,由于它的实质是 AlphaTest 也便是不透明物体,所以不会有烘托半透明物体那样简略呈现深度过错的状况。

运用 Alpha-to-coverage 也会影响 EarlyZ 的履行(不管是 AlphaTest 影响仍是 MSAA 影响),在移动渠道上功用不高,所以主张只在必要的当地运用。

由于 Alpha-to-coverage 是依据 MSAA 的,所以不运用 MSAA 的时分,启用 Alpha-to-coverage,其成果是无法猜测的。不同的图形 API 和 GPU 对这种状况有不同的处理方法。

  • Shader 的优化主张

MAD(乘加)是一条指令。将核算转化为 (a x b + c) 的方法,能够节约指令。

saturate, negation, abs 是免费的。clamp, min, max 不是。不要进行负优化。

sin, cos, log, sqrt, pow, atan, atan2 运用 SFU 进行核算,一般需求花费几个 ALU 乃至几十个 ALU,尽或许防止。

CVT (类型转化,如 half–>float,vec3–>vec4)并纷歧定是免费的,或许需求占用一个 cycle。需求削减无意义的类型转化。

先核算 scalar,再核算 vector,功用更好。一般编译器会做这个优化,可是编译器纷歧定能优化到极致。在保证代码明晰的前提下,尽或许编写高效代码是个良好习惯。

优先运用半精度的浮点数(half),速度更快。lowp 和 mediump 是 FP16,highp 是 FP32。一般极点坐标需求 FP32,其他如色彩都能够是 FP16。

不要单纯为了功用把标量兼并为向量,特别是以下降代码可读性为价值。这对现代 GPU 而言一般不会有优化作用,GPU 履行的时分仍是拆散为多个标量来履行。手艺兼并数据很简略由于代码混乱发生负优化,比方引入了隐式类型转化发生了更多的开支。

是否能够运用分支,参阅上面关于分支功用的剖析。一个保险的主张是,要阅览编译后的代码,要在不同机型上在真实游戏环境下做测验。保证修正不是负优化。

在 PC 渠道,运用 discard 除去像素或许有优化作用,能够防止 ROP 的开支。在移动渠道则不要这么做,会影响 HSR 和 EarlyZ,是负优化。

削减寄存器的运用,防止 Register Spilling(寄存器超越约束写入主存)。这儿首要便是削减 uniform 变量、临时变量的数量(简略说便是尽或许精简 shader 代码)。检查编译后的代码能够比较明晰的看到这些。运用 Mali offline compiler 也能够定量化的取得寄存器数量和指令数据。

结语

了解 GPU 硬件架构和运行机制对咱们的功用优化作业有指导意义,能够协助咱们更快的剖析出游戏的功用瓶颈。比方下图是 SnapdragonProfiler 中的 Trace 截图。假如用 Mali 的 Streamline 的话能够看到愈加具体(杂乱)的参数方针。假如对 GPU 不行了解的话,这些参数就毫无意义。

超强总结!GPU 渲染管线和硬件架构

粉丝福利,后台回复“GPU”取得本篇作者引荐相关学习材料

腾讯工程师技术干货直达:

  1. 快收藏!最全GO言语完结规划方式【下】

  2. 怎么成为优秀工程师之软技术篇

  3. 怎么更好地运用Kafka?

  4. 从鹅厂实例出发!剖析Go Channel底层原理

超强总结!GPU 渲染管线和硬件架构

阅览原文