您的位置:首页 > 其它

GPU图形流水线

2016-02-19 20:31 344 查看
《GPGPU编程技术:从GLSL、CUDA到OpenCL》 http://book.51cto.com/art/201205/338028.htm



图形处理器的作用

在了解GPU所起的作用之前,先来看看它在计算机里的位置。图1-3是计算机系统的简易图,其中只包含了GPU周围的重要设备,省略了大多数的外围设备。图中最上方的是CPU,它和北桥芯片用前端总线(Front Side Bus)相连,前端总线的带宽决定了CPU和内存数据交换的速度。内存集线器(Memory Hub)在北桥芯片中,连接着内存。GPU和北桥芯片用PCIe或者AGP总线相连。GPU的输出端连着显示器。

对GPU的功能最简单、直接的描述就是:它处理需要显示输出的数据。一旦计算机系统中有GPU,人们在显示器上看到的一切都是它计算的结果。一块现代GPU的工作流程基本是这样的:它从CPU处获得三维模型,这些模型是用顶点坐标和色彩信息组成的;GPU对这些顶点的位置进行一系列的变换,然后投影到帧缓存上(帧缓存的概念见后文);投影的同时,GPU根据显示器的大小和分辨率对投影结果进行裁剪、光栅化;每个帧缓存里的像素或者像素多边形的色彩经过GPU的一系列变换;最后的结果被GPU输出到显示器上。


图1-3 GPU在计算机中的位置
这一系列的工作是先后有序、不可颠倒的,前面步骤的输出是后面步骤的输入。我们把这一连串的图形处理任务形象地称为图形流水线(graphics pipeline),或者图形管线。图形流水线的入口是顶点坐标和颜色信息,输出的是一帧适合当前显示器显示的图像。流水线以较高的频率工作(须高于显示器的刷新频率),其间不断有数据从中流过,同时连续的一帧帧图像被输出到显示器上。

1.2.1 图形流水线

图形流水线是GPU工作的通用模型。它以某种形式表示的三维场景为输入,输出二维的光栅图像(Raster Images)到显示器,也就是位图。下面依次解释图形流水线中的关键步骤。

1)图形流水线的起点是一个三维模型。这个三维模型可以是用软件设计出的三维游戏人物,也可以是在逆向工程 (reverse engineering)中用激光扫描仪(laser scanner)等设备采集的顶点(vertices)。不论是何种模型,在计算机处理之前都一定要经过采样而得到有限的离散的顶点,每个顶点都可以被一个向量描述为一个三维坐标系里的点。这些可以用来描述三维模型的顶点组成了点云(point cloud)。如果采样频率足够高,得到的顶点就可以足够细致地描述模型的表面。图1-4b所示是用激光扫描仪采集的一个办公室的点云。点云分辨率越高,越能真实地拟合出三维场景。点云中的点可以由一个列表表示,列表中的每一项是某点的三维坐标值。同时,列表中的每一点都带有该点的颜色信息,比如可以用红绿蓝(RGB)向量来表示。这个顶点列表(point
list)即是流水线的输入数据,从起点进入流水线。



图1-4 用激光扫描仪在办公室环境内采集的点云

a) 用以采集点云的办公室场景 b) 在办公室环境中采集的点云数据
2)顶点可以用来形成多边形,从而拟合出近似的表面。由顶点形成多边形最常用的一种方法是三角化(triangulation),即每相邻的三个点组成一个三角形。接下来每个顶点要经过一系列的逐顶点操作(per-vertex operation),比如,计算每个顶点的光照、每个顶点的坐标变换等。

3)由于显示输出的需要,用户会定义一个视口(view port),即观察模型的位置和角度。然后,模型被投影到与视口观察方向垂直的平面上。这个投影变换(projection transformation)也是硬件加速的。根据视域的大小,投影的结果有可能被裁剪(clipping)掉一部分。

4)接受模型投影的平面是一个帧缓存(frame buffer),它是一个由像素(pixels)定义的光栅化平面。光栅化(rasterization)的过程,实际上就是决定帧缓存上的哪些像素该取怎样的值。通过采样和插值,光栅化器(rasterizer)会决定一幅最接近原投影图像的位图。

5)这些像素或者由像素连成的片段还须经历一些逐片段操作(per-fragment operation),也就是说,它们的颜色也可以根据算法改变。另外,纹理映射(texturing或texture mapping)在这一阶段也会覆盖某些像素的值。另外,对于投影和光栅化的结果,还要判断片段的可见性,也就是遮挡探测(occlusion detection)。

6)最后帧缓存里的结果被刷新到显示器上。该过程以较高的帧频率重复,用户就能在显示器上看到连续的图形变换。

这个过程可以简要地表示为图1-5所示的流水线。从顶点列表到最终显示,三维模型主要经历了逐顶点操作、投影变换、裁剪、光栅变换和逐片段操作,最后输出显示。随着日益复杂的图形处理要求和不断完善的硬件加速性能,有越来越多的功能被添加到图形流水线中,图1-5只概括了流水线的基本功能。



图1-5 简化的图形流水线
1.2.2 OpenGL:流水线的一种实现

图形流水线有不同的应用程序接口(API)来定义它们的功能,最主要的是OpenGL和Direct3D,GPU的发展与这两个API息息相关。每当有新的图形效果和算法被开发出来,它们就会被审核并加入API,接着,支持新的API的GPU也会很快上市。这一过程也可能是由硬件驱动的,即某一GPU制造商推出了某项功能,该功能会在稍后被图形API中某个新添加的接口函数来驱动。1992年以来,作为一种跨平台的应用程序接口,OpenGL一直是业界的标准。它与平台的无关性使它比DirectX更易于开发移植性强的应用程序。

OpenGL可以运行在几乎所有的主流操作系统上。而这里的平台无关性,不仅针对操作系统,还指GPU。整合出一套高性能,且与硬件无关的API不是一件容易的事。不同的硬件有不同的设计细节和性能特色,这些特色常常体现在底层的硬件实现上。若仅针对有限的几款GPU来优化接口,API的平台无关性就会受到影响。所以,对于通用接口来说,性能和通用性是相互矛盾的。OpenGL的设计理念是,它尽可能提供对GPU更底层的硬件访问,同时保证接口的平台无关性。也正是由于OpenGL这样的业界标准的存在,促使GPU制造商在开发硬件功能时,会考虑到标准的接口,从而使图形设备的差异最小化。

OpenGL定义的图形流水线符合1.2.1节所述的图形流水线模型。作为接口,它进一步定义了流水线中各功能与硬件之间的关系,以及实现这些功能的具体方法(函数)。图1-6表示了一个简化的OpenGL图形流水线,其中已经略去了与经典GPGPU方法无关的模块。流水线的功能模块用箭头串起来,表示工序流动的方向。灰色的模块为可编程模块。当然,除了图上所给出的模块之外,它还有其他的模块。关于完整的OpenGL流水线说明,可以参考文献[1]。这里,除了我们已经熟悉的这条流水线,图1-6还添加了另一个重要的模块──纹理缓存(Texture
Buffer)。它的重要性在开始学习经典GPGPU后就会体现。这里,为了不使读者迷惑,我们暂且不提纹理缓存以及图中与它相连的两根带有箭头的连接线的意义。另外,在图中的最右方,即帧缓存之后少了显示输出的步骤(见图1-5)。事实上,图1-6是针对GPGPU的,它略去了其他与GPGPU无关的功能,只包括了我们在使用经典GPGPU时要用到的部分。在用GPU做通用计算时,用户并不需要显示输出,所以GPU的计算结果并没有刷新到显示器上,而是写入了纹理缓存,这与我们在使用OpenGL做计算机图形任务时是有所不同的。



图1-6 简化的OpenGL图形流水线
从结构上来说,图1-6中的流水线模型从应用程序到帧缓存的部分同图1-5中的功能一一对应,只是OpenGL使用了不同的术语来指代这些流水线组件和硬件之间的对应关系。着色器(shader),又叫着色单元,实际上就是GPU的处理器。一般情况下,一个GPU会有多个处理器(几十个甚至几百个),它们同时工作,体现了GPU大规模并行处理的能力。进行几何计算的处理器叫顶点着色器,它负责对顶点进行坐标变换、投影变换等;进行片段的颜色处理的叫做片段着色器(Direct3D中称其为像素着色器)。应用程序输入GPU的是三维的点云数据。从流水线输入端直到顶点着色器,流水线计算的对象都是三维几何模型;从光栅化器开始,所有的操作都是针对二维的像素了。

1.2.3 可编程图形流水线和GLSL

细心的读者也许已经留意到,图1-6中的两个着色器模块被涂成了灰色。这是为了引入一个重要的概念:可编程图形流水线。支持可编程图形流水线的GPU就是可编程图形处理器。2001年之前,GPU都是功能固定的,或者是可设置的(configurable)。可编程GPU与它们最大的区别是,用户可以用自定义的算法来实现着色器的功能。在可编程图形流水线中,有两个模块是可以让用户加载自定义算法的,它们是顶点着色器和片段着色器。

为着色器编程的语言叫做着色语言,它们是实现复杂三维效果的关键,也是经典GPGPU技术的关键。目前最常用的着色语言主要有随OpenGL发展而来的GLSL(OpenGL Shading Language),NVIDIA设计的Cg(C for Graphics)和DirectX支持的HLSL(High Level Shader Language)。

GLSL从OpenGL 1.4版起就一直伴随着OpenGL,直到OpenGL 2.0开始正式成为OpenGL核心的一部分。作为OpenGL的正式成员,它继承了OpenGL的一切优点。首先,它具有平台无关性。GLSL可以运行在所有OpenGL支持的操作系统上,也可以运行在不同的GPU上,只要这些GPU提供了如图形流水线所定义的可编程硬件(如今几乎所有的GPU都满足这样的要求)。其次,GLSL提供尽可能底层的硬件接口,表现出很高的运行效率和灵活性。同时,GLSL的语法近似于C/C++,易于开发。

1.3 GPGPU语言

自从2006年以后,越来越多的研究人员把目光投向用GPU进行通用计算的GPGPU领域,在他们的努力下,很多原本由CPU执行的算法都有了GPU版本,它们的运行速度皆大幅提高[2]。随着GPGPU开发人员的增加,这种最初是"非主流"的技术变得越来越成熟,并影响了图形处理器的发展方向。终于,专门用于实现GPGPU技术的编程语言应运而生,它们被称为GPGPU语言,或GPU计算语言(GPU Computing Language)。最主要的语言有CUDA(Compute Unified Device Architecture,不过几乎没有人会记得这个全称)、OpenCL(Open
Computing Language)、DirectCompute(DirectX的GPGPU解决方案)、Stream SDK(以前叫Close to Metal)和BrookGPU(即之前的Brook+)。前三个是目前市场占有率最高的。这些语言的设计目的就是封装GPU尽可能多的与CPU不同的特殊性质,提供对GPU进行通用计算编程的接口。它们被设计成具有近似于高级语言的语法特性,易于学习。另外,人们不用为了使用GPGPU而去学习过多的计算机图形学知识,相比用经典的GPGPU方法,它们在开始时的学习曲线比较平缓。

随着GPGPU语言的出现和GPU硬件的迅速升级,经典的GPGPU方法已经渐渐退出了人们的视线。在选择使用经典GPGPU方法或是GPGPU语言时,可以根据硬件环境优先考虑使用GPGPU语言。即只要有支持CUDA或OpenCL等的图形处理器,用户就不应考虑使用经典的GPGPU方法。

1.3.2 统一着色器模型

正如1.2.3节所述,GPU内部至少存在两种处理器:顶点着色器和片段着色器。虽然它们都是可编程硬件,但是它们的任务从根本上是不同的。例如,顶点着色器不能设定片段和像素的颜色,片段着色器也无法处理顶点的坐标变换。所以,在设计GPU硬件时,必须结合通常情况下图形处理任务的分配,令顶点着色器的数目和片段着色器的数目有个合理的比例,从而使计算资源达到最高的利用率。一般来说,片段处理器总是顶点处理器的2到3倍。

这样的分配终究是基于统计规律和架构设计的需要,却不可能做到所有处理器在所有时刻都处于忙碌状态。统一着色器模型(unified shader model)就是为了解决这一问题而出现的。依照这一模型设计的GPU内的所有处理器不再分为两类,而成了通用的计算单元。它们既可以处理顶点变换,又可以处理像素信息,不同任务由控制器实时地动态分配。NVIDIA GeForce 8000系列GPU是首批支持统一着色器模型的图形硬件。如图1-7所示为传统架构的GPU和支持统一着色器模型的GPU的结构示意图。

图1-7a所示为NVIDIA GeForce 6800架构。最上方的处理器阵列是顶点着色器,中间数量更多的是片段着色器。这属于传统的可编程GPU模型,顶点着色单元和片段处理单元是分立的。图1-7b所示为NVIDIA GeForce 8800架构。每个小方块代表一个标量处理器(scalar processor),或称线程处理器(thread processor),它们既可处理顶点,又可处理片段。每八个处理器组成一个多处理器(multiprocessor)。每两个多处理器被编在一个多处理器单元。这就是NVIDIA支持统一着色器模型的第一代GPU。


图1-7 两个GPU架构的例子:传统架构和统一着色器模型架构

a) 采用传统架构的NVIDIA GeForce 6800


(点击查看大图)图1-7 两个GPU架构的例子:传统架构和统一着色器模型架构(续)

b) 采用统一着色器模型架构的NVIDIA GeForce 8800
传统的可编程GPU架构有分立的顶点着色器和片段着色器,这与图1-7a可以对应起来。但图1-7b的架构对读者来说可能有些陌生。它的外形同图形流水线的描述有些出入,但它的功能和传统GPU是完全相同的。在处理图形任务时,所有的处理器都可投入计算,不会因为功能的差异而浪费计算资源。事实上,对于GPGPU的开发人员来说,统一着色器架构比传统架构要简单得多,它提供给用户的是一个通用处理器阵列,支持高效的大规模并行处理计算。用户可对阵列中的每个处理器都一视同仁,不必考虑其中功能上的差异,此时,开发人员也不用花太多时间来研究硬件构造和计算模型,他们可以把更多的精力放在算法开发上。因此,支持统一着色器模型的GPU更适合GPGPU。

CUDA模型的大致计算流程可以归结为4步,如图1-8所示。


图1-8 CUDA模型的计算流程
1)把需要处理的数据从内存复制到显存中。

2)CPU把程序指令发送给GPU。

3)GPU的多处理器对显存中的数据执行相关指令,计算的过程中GPU可能需要频繁和显存交换数据。最后的计算结果存放在显存中。

4)从显存中把计算结果复制到内存里。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: