OpenGL-ES 学习(12) ---- GPU 系统结构
目录
- 图形流水线
- GPU内部构成
- Job Manager
- Tiler
- ShaderCore
- No-Fragment Front-end
- Fragment Front-end
- ExcutionCore
- Warp Manager
- ExcutioEngine
- Load Store unit
- Varying Unit
- Texture unit
- Fragment Back-end
- Memory-port
图形流水线
一个图形应用是以流水线的方式运行,如下图所示:

CPU 负责向 GPU 提交 Render Operation,包括设置管线状态,准备渲染数据,提交绘制命令(也就是draw call)
GPU 负责对渲染数据进行计算,对于不同的 Render pass,可以以流水线的方式并行执行
OpenGL-ES 的渲染管线流程如下图所示:

顶点着色器:顶点着色器的输出称为可变变量(Varying),顶点着色器还可以实现矩阵变换位置、计算照明公式来生成逐顶点颜色,生成或者变换纹理坐标等基于顶点的操作
图元装配:图元是指三角形,线段,或者点等几何对象,图元的每个顶点被发送到顶点着色器的不同拷贝,在图元装配期间,这些顶点被装配成图元;每个图元,还需要确认图元是否位于视锥体内,如果没有完全位于,就需要进行裁剪,或者直接剔除,裁剪和剔除之后,顶点位置就被转换为屏幕坐标,传递给管线的下一阶段进行光栅化
光栅化:光栅化是将一组图元转换为一组二维片段的过程,输出屏幕坐标,颜色,纹理坐标等然后,这些片段由片段着色器处理,这些二维片段代表着可以在屏幕上绘制的像素
片段着色器:片段着色器输出一个颜色值
渲染流程如下:
- 首先图形应用输入顶点的值,再将这些顶点构成成基本图元(三角形)
- 三角形的每个顶点经过
VertexShader处理,被变换到Clip space中,然后再做透视投影被投影到View space中, - 经过视锥裁剪被背面剔除之后,剩下的三角形都被
Tiler处理,得到每个Tiler上的polygon list(polygon:多边形), - 然后就是光栅化(
Rasterzation)、early-Z、FPK、FragmentShader、z test、blend等一系列流程,执行完成之后就可以得到一个有用的 pixel,并且被写入FrameBuffer
GPU内部构成
Job Manager
CPU提交的绘制命令,会首先由JobManager进一步拆分,将VertexShader和FragmentShader分别提交到Non-FragmentSlot和FragmentSlot这两个队列中(Vulkan中有直接获取队列的操作),绘制命令中也有可能包含Compute Shader,同样也是进入Non-FragmentSlot中进行处理- 然后
ShaderCore的FrontEnd就会创建对应的Warp来执行任务 Warp: 一组锁步执行的线程(实际就是SIMT的实现),在Valhal架构中,一个Warp 包含十六个线程,Mali-G52 GPU一个Wrap包含8个线程
Tiler
Tiler 是 TBR(Tiler based rendering) 架构中固定的功能,用于将 Framebuffer 切成小tile,为每个 Tile 生成相应的 polygon list. Tile 是将一个完整的FB切分形成16x16 大小的小型FB,Tiler 之后的操作都是在这个 16x16 大小的Buffer 上进行的,直到这一帧所有的 Tile 都绘制完成,原本的 FB 才会拼装完成,送到显示系统
PC上常用的 IMR(Immediate Mode Rendering),流程如下

好处是基本数据被填充到 FIFO里,随取随用,即使几何数据再多,也不会累计在片上内存,缺点也是显而易见的,这些几何数据可能被光栅化到 FB 的任何位置,所以在执行 FragementShader 的时候读取 FB 容易造成 cache 失效导致很大的 DDR 带宽,比如 blend,depth test,对于移动设备,访问 DDR 的代价是非常昂贵的
TBR (Tile based Rendering) 使用小型 FB 优化了这个过程

因为 Tile 很小,可以在 GPU上分点地方放它,对于FB的频繁访问,不再产生DDR带宽,而是变成了低时延,低功耗的 Tile 访问;同时在写出之前,对上次 Tile 中的结果进行 CRC 校验,使得相同的内容不再写,又可以节约更多的带宽
ShaderCore
Shader Core 是GPU中最核心的部分,是执行 Shader 的器件,之前的 GPU 部件,我们能编程控制的很少,但是 Shader Core 基本完全掌握在我们手中,ShaderCore 包含在处理之前进行处理的 Front-End,执行 Shader 指令的 ExcutionCore ,对 FragmentShading 结构做进一步操纵的 Back-end 三个部件

No-Fragment Front-end
No-Fragment Front-end 负责处理非Fragment 执行之前的操作,负责创建 Warp,包含 Vertex Shader 和 Compute Shader,创建 Wrap 的过程包含寄存器分配
Fragment Front-end
Fragment Front-end 的流程比较复杂,在 FragmentShader 执行之前,数据还是保存在 DDR 里的 PolygonList,距离变成需要执行 Shader 的Fragment,还需要经过下面流程:

首先必须从 DDR 中将一个 Tile 对应的 Polygon List 读取到 GPU上,然后再进行光栅化,对三角形进行采样,以及对顶点属性继续插值,之后为了缓解 overdraw(像素被之后的像素阻挡),进行了early-Z 和 FPK 对一些无效的 Fragement 进行剔除,剩下的被 FragemnetShader 处理
这里可以看出
//Non-fragment queueActive = Non-fragemnt jobslot active + Tiler Active + Non-fragment frontend Active + Excution Core Active//Fragment queueActive = fragemnt jobslot active + fragment frontend Active + ExcutionCore Active + fragment backend active//Non-fragment Active = Non-fragment frontend Active + Excution Core Active//fragment Active = fragment frontend Active + ExcutionCore Active + fragment backend active

注意 GPU 的中断分为 MMU 中断和 Job 中断,上图中 JobManager 的
Non_fragment slot 和 fragment slot 执行完成和 Non-fragment,fragment 执行完成都会产生中断
ExcutionCore
GPU 最开始被发明出来就是用于图形加速的。在光栅化的渲染框架下,对顶点做变换以及对 Fragment 着色是一种高度独立的任务,两个顶点或者像素之间谁都不需要关心对方的属性,并且它们之间的行为也几乎完全一致,比如说对一个三角形进行平移,则对它的每一个顶点加上相同的向量,区别只有输入:三个顶点的位置数据不同,如果对指令有一定了解的话,会发现这和 SIMD(Single Instruction stream, Multiple Data stream)好像一模一样 事实上,GPU 的 ShaderCore就是 SIMD 处理器
Excution Core 的内部结构:

现在我们重点关注 SIMD 指令和操作数
SIMD 的特点就是,使用一条指令,完成多组数据的同一个运算。用一条伪指令来举例:ADD x, x, y 代表x = x+y。当ExcutioEngine中每一个线程对应的x,y寄存器(在 Front-end 中分配)都可以访问的时候,GPU中的程序计数器取出ADD指令,并在每一个线程上执行,只不过对于不同的线程x,y的内容不同
Warp Manager
WarpManager从指令缓存中取出指令,并在数据准备完成的时候,提交给 Excution engine执行,指令缓存就是之前说的,Job Manager分解 CPU 传来的Draw Call 后放入的那两个队列
ExcutioEngine
ExcutioEngine 是 SIMD 指令的执行单元,每个 ExcutioEngine 包含1个程序计数器(以下简称PC),8/16个线程,每个线程可以使用 32 个bit寄存器
Load Store unit
负责所有非纹理采样的内存访问,包括数组,结构体,buffer,image等的读写(uniform buffer和编译器可以确定下标的小型数组访问,会被尽可能的优化为寄存器访问),以及寄存器溢出时,读写保存和加载溢出的寄存器。LS unit拥有自己的L1 Cache,Compute shader 中使用的 Shared memory 就来自这一部分
这里 image 的读写,是指 imageLoad/Store这样的访问函数,和 imageTexture 这种类型的采样函数严格区分,读取图像的时候,使用 texture unit 的 ImageTexture 会更快。
Varying Unit
用于计算插值的固定功能单元
Texture unit
针对纹理采样优化的硬件,双线性采样具有很好的性能功耗。TU 拥有自己的 L1 Cache,并且有专门为Texture优化的机制:优先缓存空间上邻近的像素,而不是地址上邻近的像素
Fragment Back-end
Back-end 用于 Fragment Shading完成后的操作,包括late z test、blend、把像素写到 Tile Memory,当Tile Memory触发写回条件时,把它写回到 DDR里。如果Tile内容和上一帧一样就抛弃,降低写出带宽。当所有的Tile都处理完毕,一个完整的 Framebuffer就绘制完成了,可以用于显示
Memory-port

CPU向 GPU 上传和更新数据,访问纹理,访问超大数组,buffer或者texture回读,访问 SSBO 等都需要用到 MemoryPort 的读写
