图形处理单元(GPU)
图形处理单元(GPU)1. GraphicsProcessingUnit(GPU)
GPU就是图形处理单元是NVIDIA在1999年发布的GeForce256显卡中提出的概念。GPU是一组高度可并行化处理任务的芯片组,它拥有专门处理深度缓存区,快速访问纹理和其他缓冲区的定制芯片。更重要的是它提供了支持并行执行的可编程着色器的处理器。
2. 数据并行架构
CPU的设计主要以逻辑处理和计算为主(由于跟逻辑有关因此只能串行按顺序执行),因此现代CPU设计针对其特定进行了优化,如:分支预测,指令重排序,寄存器重命名等。与CPU不同GPU更多的是处理大批量相似但又逻辑无关的数据集合(如:顶点数据,像素数据),因此GPU的设计更适合大规模并行处理数据。GPU设计采用单指令多数据(SIMD)的方式将指令执行逻辑和数据分离开来。这样处理的好处在于可以在多个着色器程序上锁定步骤执行同一指令。例如我们有2000个像素要执行片元着色器。针对每个片元所调用的片元着色器都被称为一个线程。而这里的线程和CPU线程不同。它包含了一位内存用于存放该shader的输入,以及材质球执行所需要的任何寄存器空间。使用相同着色器程序的线程被分成几个组(NVIDIA成为warps),每个组中根据SIMD功能可能有8~64个线程,例如每组同时处理32个线程,也就是对32个像素的计算。此时将创建2000/32 = 62.5个组,这意味着分配了63个组,其中一个组是一半为空。当某一组执行一个SIMD指令时,着色器程序将在所有32个线程上同步执行。以此可以大幅提升并行运算的效率。但隐性其分组和并行效率的因素也很明显,例如分支语句if可能会导致同一组中线程执行逻辑(指令)不同而无法并行,从而导致效率降低。另外一个因素就是每个线程(着色器程序)使用的寄存器数量,因为总寄存器数有限因此所需要的寄存器数量越多,可并行执行的数量越少。GPU并行计算的大致过程如下图:
3. GPU管线概述
在图形渲染管线中我们已经介绍了渲染管线,如下图所示由各个阶段组成的渲染管线是GPU的逻辑模型,其每个环节的实现取决于具体的硬件厂商。也就是说渲染管线中的每个划分清晰的阶段只是逻辑上的定义,并不一定是真实的GPU实现方式。例如渲染管线中的一个固定功能,可能可以通过GPU相邻的可编程阶段中添加一个命令来实现总之渲染管线是一个逻辑上的流水线但并不意味着真实的GPU硬件就是完全按照这个流水线的模块所设计和执行的。
4. 可编程着色器阶段
虽然着色器程序的类型很多,但GPU对所有着色器类型使用同一的着色器设计,这样既保证了所有着色器程序使用同一套指令集也有利于GPU合理分配资源(假设GPU有100个着色器处理器,如果分类型设计30个VS30个PS20个TS...势必出现资源空闲和拥堵导致性能下降)。着色器程序在执行之前先要被编译成虚拟机字节码(IL/DXIL),这样的好处就是将硬件实现和shader代码做了有效隔离。虚拟机会根据当前硬件将shader代码编译成该硬件识别的虚拟字节码。在PC端还可以使用离线编译Shader存储中间码的方式减少运行时编译开销提升性能,但手机平台由于各硬件厂商不同导致编译出来的shader binaray(字节码)不同,并且OpenGL ES3.0以后才支持program binary功能,所以手机很少用离线编译功能。当调用drawcall来绘制一组图元时,也就触发了渲染管线来执行Shader命令,每个着色器阶段都会有两种输入: uniform参数和varying参数,uniform参数一般是用于外部(cpu)设置的在整个DrawCall阶段不会发生变化,varying参数则是用来存储顶点像素等数据。Shader虚拟机会根据不同类型的输入输出提供不同的寄存器,由于uniform存的是用于整个DrawCall的数据或贴图,而Varying是针对不同Shader阶段的(并且每个varying都不同)因此用于存储uniform类型的数据寄存器要多的多。如下图:
GPU中的常见计算通过Shader语言的函数暴露出来供程序使用,逻辑分支控制为静态分支和动图分支。静态分支是指通过uniform参数值确定执行分支,此时对每个顶点/像素执行之前就能确定其执行顺序因此不会有并发的性能问题。动态分支根据每个Varying的值确定执行顺序虽然也可以执行单性能开销会大很多。
5. 可编程的Shader语言和API发展
这里介绍整个Shader语言的体现以及其发展过程具体如下图:
以上基本介绍了主流3D渲染API的发展过程,主要分为三个框架,DirectX.OpenGL和Vulkan。
DX的发展过程如下:
OpenGL的发展历过程如下:
Vulkan是基于Mantle的新架构的渲染库。
6. VertexShader
顶点着色器顾名思义是处理顶点数据的着色器,有前面GPU运行原理可知每个顶点数据都是单独有VertexShader进行运算的因此可以多顶点多着色器并行执行。一个顶点着色器至少要有一个裁剪空间的坐标作为输出。使用顶点着色器可以实现对模型的形变,控制模型动画,程序形变(eg:布料,水波),通过对整个帧缓冲区内容处理实现热浪卷曲等功能。
7. TessellationShader
7.1 概述
TS的作用就是描述曲面细节,这是一个可选的阶段,最早在DX11中使用后被OpenGL4.0 OpenGL ES3.2支持。TS优点也很明显其控制LOD的能力可以做到不增加额外总线带宽(无需CPU传输新的模型数据给GPU)的情况下在性能和表现上做到很好的平衡。TS通常由三个部分组成(DX定义):Hull Shader, Tessellator, Domain Shader。OpenGL中定义的名称有所不同但功能是一样的。(OpenGL中Hull Shader被称为Tessellation Control Shader细分控制着色器. Domain Shader被称为Tessellation evaluation Shader细分评价着色器)Tessellator是固定管线不可编程的。三个步骤的具体负责内容如下:Hull Shader, Tessellatior, Domain Shader。
7.2 Hull Shader
在该Shader中定义了曲面细分规则相关的控制点等参数并以特殊的patch图元为输入,经过该Shader处理后(eg:可以修改patch,增删控制点),将相关数据传递给DomainShader和Tessellatior。
7.3 Tessellatior
该阶段是固定管线部分不可编程,由曲面细分控制着色器传入的图元和细分规则控制点等参数生成新的顶点。(利用HullShader传入的参数可以决定生成曲面类型:三角形,四边形,isoline也可以根据内缘/外缘数决定分割三角形的形式和数量)最终将包含了新生成的点和三角形的patch图元传递给细分评价着色器。
7.4 Domain Shader
细分评价着色器根据Tessllatior传入的新的顶点数据和Hull Shader传入的细分控制参数为这些新增的点计算位置法线,UV坐标等信息并传出到下一步骤。
8. GeometryShader
几何着色器可以将图元转化为其他类型的图元例如将三角形网格转化为线框。这是TS无法做到的,对GS的支持早于TS,DX10 OpenGL3.2 OpenGL ES 3.2都已支持了GS。此外,GS可以用于修改传入的数据或者制作有限数量的副本。例如:一个用途是生成六个不同transform的副本,用于同时呈现立方体的六个面,详见10.4.3。在一个DC中,整个管线只有三个部分会在GPU中创建工作:光栅化、TS和GS。而考虑到所需要的资源和内存,GS是最不可控的,因为它是完全可编程的。在实践中,GS很少会被使用,因为它无法很好的利用GPU的优势。在一些移动平台,它甚至是在软件层开发的,所以严重阻碍了它的使用。
9. 流输出
标准的渲染管线流程是当顶点数据经过VS(TS,GS)处理后送入光栅化然后在PS中计算。流输出提供了一个顶点被VS(TS,GS) 处理完成后输出的一种机制。可以输出一个数据流(有序的数组)甚至可以将光栅化及之后的流程全部关闭。当然这里输出的数据还可以被重新送回管线。(例如模拟流水或者其他例子特效) 流输出锦衣浮点数形式返回数据,因此会有明显的内存开销。输出数据以图元为单位将丢失原始mesh中的顶点共享(顶点索引)关系。每个三角形都会生成自己的三个顶点数据。OpenGL中流输出被称为transform feedback。
10. 像素着色器
在完成VS(TS,GS)之后,图元经过裁剪及光栅化(不可编程)就进入了PS阶段。经过光栅化后顶点数据被光栅化到三角形全部或者部分覆盖的像素上。被三角形全部或者部分覆盖的像素被称为片元fragment,OpenGL中PS被称为FragmentShader。VS中定义的输出参数经过光栅化会作为PS的输入,除此之外在ShaderModel3.0以上版本中PS还可以获取的该片元在平面上位置,当前片元属于三角形正面/背面。根据这些数据PS可以计算出一个片元颜色当然也可以通过discard指令丢弃对当前片元的绘制。此外在PS中也可以修改深度值,甚至在DX11.3还支持了修改模板stencilbuffer值。当对片元计算完颜色后可以直接输出的合并阶段处理也可以使用MRT输出到指定缓冲区(延迟渲染)。在PS计算当前片元颜色时有时会用到相邻像素的颜色值,一个常用的做法是使用两个pass,第一个pass先绘制到缓冲区第二个pass对缓冲区中向量数据采样。此外PS还提供了内置指令用于获取沿平面X/Y轴每像素内差值的变化量。(使用限制是不能有逻辑分支,因为并行机制在有逻辑分支时会被被分配在不同的组内)。DX11引入了Unordered acess view(UAV无序资源视图)缓冲区 OpenGL4.3引入了Shader storage buffer object(SSBO着色器存储缓冲区对象)支持对任意位置的访问。但因为GPU是并发的,因此使用是可能出现访问冲突,访问冲突的问题后续讲解。
11. 合并阶段
合并阶段是用于将片元的深度和颜色(PS输出)与缓冲器上的信息进行合并。这个阶段在DX中成为output merger OpenGL中成为pre-sample操作。这个阶段的操作包含深度缓冲区模板缓冲区并且会触发颜色混合(alpha blend)。试想下如果PS计算出了像素颜色但发现因为深度或者模板测试不能通过,这样白白浪费了PS计算。为了避免这种情况 ,可以将合并阶段的一些测试放到PS之前(如深度测试、模板测试),如果测试不通过则直接丢弃片元不会进行计算。这个功能就是early-z(后续章节会详细介绍)。合并阶段一般是固定管线处理的,但它是高度可配置的。(例如配置混合方式混合操作等)DX11.3提供了一个方法可以通过ROV(Rasterizer order views光栅化顺序视图,强制执行顺序)实现programmable blending(可编程融合)但其性能会有一些代价。ROV和合并阶段都是按照顺序渲染的,也就是output invariance输出不变性。不管PS的输出顺序如何,API要求结果按照输入顺序,逐物件和三角形的排序并发送到合并阶段。
12. 计算着色器(ComputeShader)
GPU不仅可以用来实现图形渲染管线,也可以用于非图形的计算,如AI计算。DX11开始引入计算着色器概念,这个Shader不固定在渲染管线中,但它也可以被用来进行渲染相关的计算入粒子特效,面部动画,提高深度精度等。CS的优势在于并行计算并且全部在GPU中省去了数据从CPU到GPU传输的开销。
13. 总结
本章使我们了解了GPU的基本架构,了解了GPU如果通过并发处理计算大规模数据提升效率了解了GPU逻辑渲染管线: VS ---(TS) ---(GS) --- 裁剪 ---屏幕映射---三角形转配及遍历--- 光栅化 ---(PS) ---合并了解主流渲染库的发展过程:DX8.0支持可编程管线 DX9.0支持HSLS vs/psDX10.0支持ShaderModel4.0支持GS 流输出DX11.0支持ShaderModel5.0支持TS CS支持CPU多线程,DX12基于Mantle优化驱动层开销提升性能。 OpenGL 2.0支持可编程管线GLSLOpenGL 3.2支持GS OpenGL 3.3支持ShaderModel4.0OpengGL 4.0支持TSOpengGL 4.3支持CS OpenGL ES1.0是基于OpenGL 1.3的精简版 OpenGL ES2.0是基于OpenGL 2.0的精简版 WebGL是基于OpenGL ES 2.0 OpenGL ES 3.0基于 OpenGL 3.0WebGL2 基于OpenGL ES2.0Vulkan单独发展顶点着色器用于计算顶点并至少以裁剪空间的顶点为输出数据细分着色器分为三个部分:细分控制着色器用于接受图元定义细分参数控制点等信息,细分器用于按照细分控制着色器参数和图元生成新的点,细分评价着色器则根据新的点和细分控制着色器中的控制参数为每个点计算生成位置法线等属性信息。几何着色器用于控制几何图元变更修改流输出则是提供一个可输出的顶点数据流。像素着色器的目的是计算最终的像素颜色值,其参数有VS输出的顶点属性经过光栅化而来的数据,目前PS自带了片元在屏幕中位置,片元在三角形正/反面等信息。计算时可以通过两个PASS的方式或者使用SSBO/UAV的方式实现相邻像素的读取使用。融合默认是固定管线,可以使用early-z的方式将深度检测模板检测提前到PS之前,DX11.3以后也可以通过可编程的方式实现自定义混合。计算着色器可以用来进行非图形学计算如AI,也可以为图形学进行复制计算,其与CPU相比的优点是高并发且减少了CPU到GPU的数据传输消耗。
页:
[1]