原文:深入GPU硬件架构及运行机制
CPU与GPU的交流通过MMIO(Memory Mapped IO)进行。CPU 通过 MMIO 访问 GPU 的寄存器状态。
任何命令都是由CPU发出。命令流(command stream)被提交到硬件单元,也就是GPU Channel。
有,某些GPU最多包含5层结构,分别为寄存器、L1缓存、L2缓存、GPU显存、系统显存。
以2010年推出的NVidia Fermi架构为例(L2缓存在左下图中)
曲面细分:
根据摄像机的距离,调整片面的数量。
几何着色器:
对输入的几何体进行一系列操作,使其变成另一种几何体,如将粒子转化为一个正方形面片(由两个三角形面片组成),以供之后渲染。
光栅化:
在此阶段,将计算三角形的微分、边方程和其他数据。这些数据可用于三角形遍历,以及几何阶段生成的各种阴影数据的插值
Early-Z:
在像素着色器之前进行深度测试,具体下一问会谈到。
像素着色器:
以从光栅化阶段所传来的顶点各值的插值进行各种计算、纹理采样
透明度测试:
即Alpha Test,若某片元的Alpha值不满足某条件,则直接将其舍弃。
模板测试:
即Stencil Testing
深度测试:
将被其他片元挡住的片元舍弃。
Early-Z技术:将深度测试提至像素着色器之前
Early-Z技术可以将很多无效的像素提前剔除,避免它们进入耗时严重的像素着色器。Early-Z剔除的最小单位不是1像素,而是像素块(2x2)
但是,以下情况会导致Early-Z失效:
- 开启Alpha Test:由于Alpha Test需要在像素着色器后面的Alpha Test阶段比较,所以无法在像素着色器之前就决定该像素是否被剔除。
- 开启Alpha Blend:启用了Alpha混合的像素很多需要与frame buffer做混合,无法执行深度测试,也就无法利用Early-Z技术。
- 开启Tex Kill:即在shader代码中有像素摒弃指令(DX的discard,OpenGL的clip)。
- 关闭深度测试。Early-Z是建立在深度测试看开启的条件下,如果关闭了深度测试,也就无法启用Early-Z技术。
- 开启Multi-Sampling:多采样会影响周边像素,而Early-Z阶段无法得知周边像素是否被裁剪,故无法提前剔除。
- 以及其它任何导致需要混合后面颜色的操作。
Early-Z也会导致一个问题——深度数据冲突(depth data hazard)
假设数值深度值5已经经过Early-Z即将写入Frame Buffer,而深度值10刚好处于Early-Z阶段,读取并对比当前缓存的深度值15,结果就是10通过了Early-Z测试,会覆盖掉比自己小的深度值5
避免深度数据冲突的方法之一是在写入深度值之前,再次与frame buffer的值进行对比
SIMD:Single Instruction Multiple Data,单指令多数据。
SIMT:Single Instruction Multiple Threads,单指令多线程。是SIMD的升级版。可对GPU中单个SM中的多个Core同时处理同一指令,并且每个Core存取的数据可以是不同的。
co-issue是为了解决SIMD运算单元无法充分利用的问题。
当然是,而且是高度并行的。
仍旧以Fermi架构为例,
它拥有拥有16个SM,每个SM拥有2个Warp(线程束),每个Warp拥有16个Core,每个Core又有1个FPU(浮点数单元)以及1个ALU(逻辑运算单元)……
在获取数据之后,在SM中以32个线程为一组的线程束(Warp)来调度,来开始处理顶点数据。Warp是典型的单指令多线程(SIMT,SIMD单指令多数据的升级)的实现,也就是32个线程同时执行的指令是一模一样的,只是线程数据不一样,这样的好处就是一个Warp只需要一个套逻辑对指令进行解码和执行就可以了。也就实现了各个Warp之间的并行
GPC:图形处理簇,内含数个TPC
TPC:纹理处理簇,内含数个SM
SM:Stream Multiprocessor,流多处理器,内含数个Warp
Warp:线程束,内含数十个Core,而每个Core可以作为线程的执行者。
可以,因为如今的GPU已经引入了统一着色器架构(Unified shader Architecture)。用了此架构的GPU,VS和PS用的都是相同的Core。也就是说,同一个Core既可以是VS又可以是PS。
不是,
像素着色器中,会将相邻的四个像素作为不可分隔的一组,送入同一个SM内4个不同的Core
原作者推测有以下原因:
1、简化和加速像素分派的工作。
2、精简SM的架构,减少硬件单元数量和尺寸。
3、降低功耗,提高效能比。
4、无效像素虽然不会被存储结果,但可辅助有效像素求导函数。
同时,也会激化过绘制(Over Draw)的情况,损耗额外的性能
原本只需绘制三个像素,使用3个Core,但实际上这个三角形涉及到了3个像素块,因此需要使用12个Core来绘制,造成性能浪费
绝大部分情况下会降低,除非一个Warp中的所有线程都走进了同一分支或循环次数相等。
SM的warp调度器会按照顺序分发指令给整个warp,单个warp中的线程会锁步(lock-step)执行各自的指令,如果线程碰到不激活执行的情况也会被遮掩(be masked out)。
被遮掩的原因有很多,例如当前的指令是if(true)的分支,但是当前线程的数据的条件是false,或者循环的次数不一样(比如for循环次数n不是常量,或被break提前终止了但是别的还在走),因此在shader中的分支会显著增加时间消耗,在一个warp中的分支除非32个线程都走到if或者else里面,否则相当于所有的分支都走了一遍,线程不能独立执行指令而是以warp为单位,而这些warp之间才是独立的。
左边效率高,因为若同一个像素块如果分属不同的三角形,就会分配到不同的SM进行处理。由此推断,相同面积的区域,如果所属的三角形越多,就会导致分配给SM的次数越多,消耗的渲染性能也越多。
GPU Context:
由于SIMT技术的引入,导致很多同一个SM内的很多Core并不是独立的,当它们当中有部分Core需要访问到纹理、常量缓存和全局内存时,就会导致非常大的卡顿(Stall)。
例如下图中,有4组上下文(Context),它们共用同一组逻辑运算单元ALU。
延迟的后果是每组Context的总体执行时间被拉长了,但是,越多Context可用就越可以提升运算单元的吞吐量。