从GPU硬件架构看渲染流水线

朱珠

1. GPU 硬件架构

以 nVidia MaxWell 为例,分析 GPU 的硬件架构

1.1 SP(Streaming Processor)

SP 是GPU 的最小运算单元,相当于一个微型 CPU,也叫 CudaCore。

1.2 SM(Streaming Multiple Processor)

MaxWell 架构中的 SM
1.2.1 SM 算是 GPU 真正的“核”,包含以下若干部分
  • 32 个 SP (不同型号 SM 中的 SP 数量不同,基本都是 32 的整数倍)
  • Instruction Cache 指令缓存
  • Warp Scheduler 进行 warp 的调度
  • Dispatch Unit 在warp 调度完成后将 Instruction Cache 中的指令发射给具体的 SP 处理
  • RegisterFile 寄存器堆,存放 SP 执行指令的上下文和寄存器数据
  • Load/Store 也就是 LD/ST 硬件单元,复杂数据加载和存储
  • SFU 特殊计算单元,设计来快速计算复杂函数如 sin, cos, sqrt
  • Tex 纹理采样单元,能够快速进行纹理采样
  • L1 Cache 一级缓存,存储顶点数据和片元数据等
  • Uniform Cache 缓存 shader 中的 uniform 参数
  • Texture Cache 纹理数据缓存
  • 固定渲染管线组件 PolyMorph Engine
1.2.2 PolyMorph Engine 的组成

PolyMorph Engine 是用来执行固定渲染步骤的硬件,一般包括这几个成部分

  • Vertex Fetch 作用是获取顶点数据到 L1/L2 Cache 中
  • Tessellator 执行曲面细分操作
  • Viewport Transform 进行顶点坐标的视口变换也就是屏幕映射,将顶点着色器输出的裁剪坐标转换为屏幕坐标
  • Attribute Setup 属性装配器,在顶点着色器计算完毕后,光栅化得到
1.2.3 warp 的调度

线程束(warp) 是 GPU 进行任务调度的基本单位,一个 warp 包含 32 个线程,也就是说 GPU 的调度,是以32个线程为单位的,即使只处理3个顶点,也会调度 32 个线程,占用 32 个 SP 进行计算,其中 29 个将会被 mask 为不可用状态

1.3 GPC (Graphic Processing Cluster)

maxWell 架构中

GPC 由多个 SM 和一个光栅化引擎 Raster Engine 组成
多个 GPU 共享 L2 Cache,且通过 Crossbar 进行连接
GPU 中还包含一些 ROP 模块(Render Output),专门针对片元做逐片元操作,并写入到 FrameBuffer

2. 逻辑流水线

2.1 从GPU的硬件组成看渲染流水线

逻辑管线

(1) 图形 API (OpenGL/DirectX/Metal) 发出DrawCall 时,指令被推送到驱动程序,对指令进行合法性检查后,指令被推送到 GPU 可以读取的 pushbuffer 中。

(2) 一段时间或显式调用 flush 指令时,驱动程序将 pushbuffer 中的指令发送给 GPU,GPU 中的主机接口 (HostInterface) 接受命令,通过 FrontEnd 进行处理。

(3) 图元分配器(Primitive Distributor) 处理 indexbuffer 中的顶点数据,产生三角形的批次,发送给多个 GPC 处理

(4) 指令到达 GPC 后,每个 SM 中 Poly Morph Engine 中的 Vertex Fetch 模块负责通过三角形索引取出三角形数据。

(5) 获取数据后,SM中的 Warp Scheduler 开始以 32 个线程为一组的线程束 warp 来调度,处理顶点数据。warp 是单指令多线程(SIMT, Single Intruction Multiple Thread) 的实现,32个线程同时执行同样的指令,但是各线程的数据不一样,比如 32 个顶点同时执行顶点着色器的指令。

以线程束为单位进行调度的好处在于,32个线程只需要一套逻辑对指令进行解码和执行,芯片可以更小更快。

(6) 单个 warp 中的线程会锁步(lock step) 执行指令,没有分配到数据的线程将会被打上掩码,线程不能独立调度,必须以 warp 为单位,但不同 warp 之间是独立的。

其它可能导致线程被打上掩码的情况还包括:
有 if-else 分支 指令,例如当前指令是 if (true) 分支,但是数据条件为 false,则线程会被 mask
循环语句中,线程对应的数据已经执行完毕,但 warp 中其它线程还在执行,线程也会被 mask
所以 Shader 中的分支和循环会比较耗时,以 if-else 为例,除非同一个 warp 中的所有数据都进入了 if 或 else 分支,否则相当于整个 warp 都走了两个分支

(7) 指令执行时间长短不一样,特别是内存加载比较耗时,warp 调度器可能会直接切换到另外一个没有内存等待的 warp 执行,GPU 因此能够克服内存读取延迟。warp 在寄存器堆 RegisterFile 中都有属于自己的寄存器。

(8) 当 warp 执行完了顶点着色器的指令后,运算结果会传递给 Poly Morph Engine 中的 Viewport Transform 模块进行处理,通常顶点着色器输出的是裁剪空间的坐标,Viewport Transform 对顶点进行裁剪并进行 视口变换,也就是屏幕映射,将顶点坐标变换为屏幕坐标

(9) 得到屏幕坐标后,就可以进行光栅化了,三角形被分割,分配给多个 GPC(通常按照屏幕分 Tile 进行分配),三角形的范围决定了将会被分配给哪一个 GPC 的 Raster Engine,每个 Raster Engine 覆盖了屏幕的若干 Tile。

(10) GPC 上的 Raster Engine 对三角形数据进行光栅化,得到每个三角形所覆盖的像素信息,这里通常会进行背面剔除和 early-z 剔除操作。

(11) 一个三角形的三个顶点,每一个顶点都会执行一次顶点着色器和 Viewport Transform,处理后的信息传递给 Raster Engine 进行光栅化,得到若干个片元,那么每个片元的数据(位置、颜色、法线等)是怎么得来的呢?SM 上的 Attribute Setup 会根据顶点数据进行 插值 得到片元数据,L1&L2 缓存用来存放这些数据以确保 片元着色器 能够进行处理。

数据在L1/L2缓存和显存中流转

(12) 8个 2x2 的片元块(共32个)将会被 SM 中的 Warp Scheduler 分配到一个 warp 中执行片元着色器的指令。

(13) 片元着色器执行指令,完成片元颜色和深度计算,此时需要基于三角形的原始 API 提交顺序,将数据移交给渲染输出单元 ROP (Render Output Unit),一个 ROP 内部有很多 ROP 单元,ROP 单元中会处理逐片元操作 如深度测试、与 FrameBuffer 中片元的混合等。

(14) ROP 拿到片元数据,通过访问 FrameBuffer 进行逐片元操作后,通过 Crossbar 将结果写入到 FrameBuffer,渲染流程结束。

2.2 内存访问

GPU 中的内存分为若干类型,不同类型的内存读取速度相差比较大


内存访问速度

Shader 中直接使用的寄存器内存速度很快,纹理和常常量内存以及全局内存的速度相对比较慢。

3. 移动端渲染流程对比

3.1 GPU 架构上的差异

上述流程是 MaxWell 桌面 GPU 架构的渲染详细过程。然而移动端 GPU 的架构和桌面 GPU 是不同的。

  • 桌面显卡包含 GPU芯片和显存,高速且大带宽数据接口提供 GPU 对显存的快速访问,且桌面电脑通常是插电运行,不需要考虑数据传输的功耗问题。

  • 移动端基本都是 SoC 芯片结构(System On Clip),CPU 和 GPU 集成在同一张芯片,且共享主存,主存距离 GPU 较远,带宽很低,访问一次时间较长且功耗较大。

3.2 移动端的 TBR 架构

3.2.1 TBR 架构和桌面 IMR(立即渲染模式) 的区别

移动端渲染流程是基于 Tile-Based 架构的,也叫 TBR(Tile-Based Render),针对一帧中的所有 DrawCall,先全部执行顶点着色器 VS,然后根据屏幕进行分块(Tile),基于 Tile 进行片元着色器(FS)的执行。

桌面版 GPU 是针对每一个 DrawCall 顺序执行 VS-FS,桌面版的 FS 也是按照 Tile 模式来执行的,只不过桌面版都是一次 DrawCall 立即 FS。

3.2.2 TBR 架构为什么能够加速

用一个例子来看。假如某一帧提交了两个DrawCall,每个DrawCall 包含了一个三角形,且两个三角形覆盖了全部屏幕。

  • 桌面 IMR 时,执行第一个DrawCall时对第一个三角形顺序进行VS-FS,对显存中的 FrameBuffer 进行了一次操作;第二个 DrawCall 还要执行同样的处理,需要对 FrameBuffer 再进行一次操作。(至少进行一次读的操作,如果第二个三角不覆盖第一个三角就少一次写的操作)。
  • TBR 时,两个 DrawCall 都先执行 VS,不操作FrameBuffer,然后在屏幕的 Tile 上处理两个三角形(此时数据是在 On Clip 的缓存中,读写非常快),处理完这两个三角形的 FS,最终的像素也就确定了。此时再把数据写入到 FrameBuffer 中就可以了。全程只有一次 FrameBuffer 的操作。
  • 此外,TBR 中还可以提前进行 z-test,可能使得 FS 只进行一次就足够了。减少了颜色混合等操作,这些操作都是在 onClip 的缓存中执行,不会访问 FrameBuffer。

你可能感兴趣的:(从GPU硬件架构看渲染流水线)