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
对一些无效的 Fragemen
t 进行剔除,剩下的被 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
的读写