[RTR4]23章 图形硬件翻译

想了解GPU硬件相关知识,网上内容太杂乱,而且网上关于RTR4图形硬件部分的翻译机翻味道太重了,看得莫名其妙,所以在学的同时顺手翻译了.个人渣翻,有错误还请指出.

图形硬件

23.1 光栅化

光栅化包括三角形装配和三角形遍历,此外,我们还将介绍如何在一个三角形中插值,三角形中插值与三角形遍历联系紧密.

每个像素中心的坐标是,x与y都是整数,是屏幕的分辨率.三角形中,未经过变换的顶点为,经过MVP变换后但没有除w分量的坐标为.投射到屏幕空间中后的坐标为.

在着色过程中,一个像素网格被划分为2*2的组合,称之为Quad,当一个Quad中有一个像素被包含进三角形内部,此Quad中的其余像素都被称作辅助像素,这样的设计在着色过程中便于计算细节的纹理等级(,这是大多数GPU设计的核心并且影响后续阶段).辅助像素是在片元着色的过程中用来计算有限差分的,使用贴图LOD时需要计算UV差分值.Quad的设计导致越小的三角形,边际辅助像素的开销越大,造成一定的性能浪费.辅助像素的数量也叫做Quad overshading.

光栅化辅助像素.png

硬件通过对三角形的每一边计算边缘函数来决定是否一个像素的中心(或其他采样点)处于三角形中.
是边的法向量,是边上的点.我们可以通过边的两个顶点和来得到边的向量,法向量就是把边向量逆时针旋转90度得到.

对于计算结果,像素恰好在三角形边上;若,称为该像素在正向的半边,即在三角形内部的一侧;若,该像素就在三角形外部的一侧.这可以判断一个像素是否在三角形内部,如果一个像素在三角形内或边上,那该像素必须满足对每一条边都.

图形API会要求计算屏幕空间的浮点数顶点坐标,并把它转换为定点数坐标,这是为了定义tie-breaker规则.同时,这种设计也可以更高效地判断三角形内.浮点数坐标转换为定点数坐标的转换过程会在边缘函数计算前就完成了.比如使用(1,14,8)的格式定义定点数,1位表示正负,14位表示整数坐标,8位表示在像素内部的小数坐标.对于一个坐标,整数坐标处于范围内,而像素点内部有种可能位置.

边缘函数的另一个重要性质是可加性(incremental),当一个像素中心点计算完毕后,可以很方便地计算出.这个方法经常被用作计算小片的像素是否在区域内,对于一小块像素,会使用每个像素对应1bit的掩码来记录它是否在区域内.

两个三角形共用一条边,同时这一条边穿过了一个像素的中心,出于效率角度考虑,那么这个像素会被一个三角形包含,然后被另一个三角形覆盖.为了达到这个目的,我们需要用到tie-breaker规则.这里介绍在Dx12中应用的top-left规则.对于三角形三条边的边缘函数而言,均成立,则代表该像素在三角形中,top-left规则应用于三角形边穿过像素的情况.三角形的顶边(top edge)指其他边在其下方并且水平的边;三角形的左边(left edge)指在三角形左侧并且非水平的边,这也意味着左边至多可以有两条.用边缘函数的参数来表示就是,顶边的条件是且, 左边的条件是.测试采样点在不在三角形内部的测试叫做内部测试(inside test).

一条直线在渲染时会当作一个一像素宽的长方形处理,能够由两个三角形组成,同时也能多用一次额外的边缘方程.这种设计方法的优点是使直线也能够应用边缘方程.点会被当作四变形来绘制.

为了提高效率,通常以分层的方式进行三角形遍历.通常硬件会计算屏幕空间内点的包围盒子,并确定哪个片(tile)在包围盒中并且与三角形重合.确定一个片是否在包围盒外的方式是二维版的AABB平面测试.处理方法如图所示:在遍历开始前,首先确定该片的哪个角点参与测试,对于每个参与测试的片来说,对一条特定的边,参与测试的角点的位置是相同的,因为哪个角点距离边更近只取决于边的法线.选出的角点会参与边缘函数的测试,当角点在边缘以外,也就说明该角点所在的片在边缘以外,硬件不会再测试这个片中的其他像素了.相邻的片的测试也会应用可加性质,举例来说,水平向右距离8个像素的点,只需在上一个边缘函数结果上加.

片在包围盒内的测试.png

经历过二维的片/边缘测试后,现在需要分层遍历三角形了.方法如图所示.遍历片也需要用某种顺序,一般采用之字形遍历或其他的空间填充曲线(space filling curve),可以增加相干性.如果需要,在层次遍历中也是可以添加其他的层级的.例如层级遍历时,可以先按16*16的片大小遍历测试,有与三角形重合的部分后,遍历测试4*4的片大小层级.

像素片层序遍历.png

片平铺遍历的主要优势在于(比如)以扫描线顺序遍历三角形,像素的处理方式更加连贯,因此,纹理的访问也更加连贯.同时在处理颜色和深度缓冲时具有局部的优势.按扫描线的方式遍历能够缓存最近用到的纹素颜色方便复用.当使用图像金字塔进行纹理采样时会增加缓存中的纹素复用层级,但是如果对像素进行扫描线的方式遍历,当扫描到最后的纹素颜色时,最初缓存的纹素颜色缓存很可能已经被释放.因为缓存比反复从内存中读取更有效率,所以三角形通常以片的形式遍历.这种方式会对纹理采样,深度缓冲和颜色缓冲更有利.事实上,纹理缓冲,深度缓冲和颜色缓冲也通过片的方式存储.

在三角形遍历开始前,GPU通常会进行三角形装配.这个步骤是为了计算三角形的常数参数,方便后续进行遍历.三角形装配也会对属性插值相关的常数参数进行计算.在后续我们还会发现其他需要在三角形装配环节进行计算的常数.

裁剪过程是在三角形装配前完成的,因为裁剪会产生更多的三角形.将三角形裁剪出观察空间是巨大开销的过程,所以GPU不在万不得以的情况下不会进行裁剪.我们通常需要将三角形裁剪出近平面,这个过程会额外生成一个或两个三角形.对于屏幕上的边来说,GPU使用guard-band clipping来减少裁剪的次数.这个裁剪方法如图所示.

guard-bindClipping.png

23.1.1 插值

重心坐标是计算光线与三角形相交的副产品,任何顶点的参数,都能通过重心坐标插值的方式求出

在公式中,是插值参数在三角形中插值得到.重心坐标的定义是:

是该插值点连接三角形顶点构成的子三角形的面积,具体见图所示.重心坐标还有一个分量,由于,这里由代替.

重心坐标解释.png

边缘方程可以写做边的法线的形式 ,其中,这种形式的边缘方程也可以写作

,是边的长度,就是边逆时针旋转90度得到的.(原因:边缘函数中的法线既可以取归一化后的法线,也可以不归一化,不影响最终结果,在这里取的就是未归一化的法线.).公式中的项是线段在上的投影,也就是到的高.也就是说,,可以顺便求出的大小,因为我们需要来计算重心坐标.

在三角形装配过程中会计算因为三角形的面积不变,这样也避免了对像素分割.当我们对三角形进行边缘遍历时,我们能够顺便得到上式子的所有项,这在插值深度和正交投影时是准确的,但在处理透视投影时,重心坐标插值处理纹理会出现偏差.下图解释了.

正交透视投影与重心坐标.png

透视校正重心坐标对每个像素做了一些除法操作.因为线性插值开销小,我们也知道如何计算,我们应当尽可能多地在屏幕空间应用线性插值,在透视校正中也是如此.在三角形中线性插值和,其中是顶点经过一系列变换后的第四维坐标分量.把和相除,其比值依旧是.这就是逐顶点的除法.(也就是为什么投影变换以后要逐顶点除w,因为要做透视校正)例子:直线上点坐标为,有两个点和,求这两个点的中点的插值坐标.1.插值和:即4和2,中点插值为3;即1和1/3,中点插值为2/3. 2.计算:即3/(2/3),a=4.5. 所以和的插值中点为4.5

实际应用中,通常需要利用透视校正来插值许多参数,所以需要计算透视校正重心坐标来辅助插值.所以我们需要对作出一些修改:

因为,三角形装配过程能够计算并存储和和来使逐像素的计算更快.相对来说,所有f方程都能乘上,所以我们存储,和.经过投影校正的重心坐标就是:

投影校正重心坐标我们需要每个像素计算一次,就可以插值计算任何需要投影校正后的参数.需要注意与不同,与子三角形面积不成比例,另外分母不是常数,这也要求了必须每像素执行一次.

最后,关于深度应该使用计算每个顶点的深度然后使用(u,v)进行线性插值.这种方式有好处比如压缩深度缓冲.

23.1.2 保守光栅化

DX11和OpenGL的拓展中,定义了一种新的三角形遍历方式,保守光栅化(conservative rasterization(CR)).保守光栅化分为两种,高估保守光栅化(overestimated conservative rasterization, OCR)和低估保守光栅化(underestimated conservative rasterization,UCR).也被称作外部保守光栅化(outer-conservative rasterization)和内部保守光栅化(inner-conservative rasterization).如图所示.

UCR和OCR.png

OCR是三角形只要与像素有相交就包含该像素,而UCR是完全在三角形中的像素才被包含.OCR和UCR都可以通过将片大小缩小到一个像素来使用片遍历来实现.当硬件中没有支持时,可以使用几何体着色器或三角形扩展实现OCR.CR可用于图像空间中的碰撞检测、遮挡剔除、阴影计算和抗锯齿等算法。

最后,所有的光栅化操作都是在几何处理和像素片元处理之间的桥梁过程.为了计算最终的三角形顶点位置和最终的像素颜色,GPU接下来需要进行海量的弹性运算.

23.2 海量运算和调度

为了提供巨量的对于任意计算任务的计算力,大多数GPU架构都采用统一的着色器架构,采用SIMD多线程处理(single instruction multiple data,单指令多数据流),也称做SIMT处理或超线程.(看3.10了解SIMD处理).warp是英伟达的概念,AMD硬件中称为waves或wavefronts.接下来我们会首先介绍GPU中典型的统一的算数逻辑单元.(arithmetic logic unit,ALU)

ALU,也叫做SIMD lane是硬件的一部分用于在一整个上下文中计算一段整体的程序,如顶点或片元着色器.典型的ALU结构如图所示,主要的计算单元是一个浮点计算单元(FP unit)和一个整数计算单元(int unit).浮点单元采用IEEE 754浮点数标准并支持fused-multiply and add(FMA)指令作为其支持的最复杂的指令之一.一个ALU还包含move/compare和load/store,分支判断和超越数计算(正余弦和指数).因为超越数单元不常用,这些单元可能被组合起来放在特殊单元中(special unit,SU),这些单元可能分别位于不同的硬件单元结构中,例如一些超越数计算硬件单元可能为更多的ALU服务.ALU结构通常使用管线并行,例如,当一些指令计算乘法时,下一个指令也正在执行取值操作.显而易见,在理想情况下,管线阶段划分越细,整体操作的执行也就越快.ALU采用流水线结构还有一个原因是,一个流水线中,运行最慢的硬件部分决定了整体执行的最大时钟频率,所以细分流水线上块的数量可以使整体执行更加迅速.但是为了简化ALU的设计,ALU通常只有几个流水线阶段.

ALU和multi-processor.png

ALU不同于CPU的一点是,ALU没有许多附加功能,如分支预测,寄存器重命名,深度指令管线等.GPU在芯片中,使用大量重复的ALU来提供巨大的算力,扩大寄存器大小以方便warp切换.例如GTX1080Ti有3584个ALU.为了方便调度GPU任务,大多数GPU将ALU32个一组时钟来同步运行,将这一组ALU称作SIMD引擎.不同的厂商对ALU组称呼不同,我们使用多处理器(multiprocessor,MP)来称呼.例如英伟达叫流多处理器,英特尔叫执行单元(execution unit),AMD叫计算单元(compute unit).MP示例如图所示.每个MP通常由个调度器来为SIMD引擎分配任务,MP中还会有L1缓存,局部数据存储(local data storage,LDS),纹理单元(texture unit,TX)和特殊单元(SU)来处理ALU无法处理的任务.调度器会以SIMD多线程的方式为ALU分配任务.MP的架构不同的厂商不同.

因为图形任务中有众多相同计算的任务,如顶点和片元着色器执行相同的程序,这种特点决定了GPU使用SIMD处理过程.这种结构被称为线程级并行(thread-level paralleism)也就是指顶点和片元着色器执行过程中与其他顶点和片元相独立.也就是说,对于采用SIMD或SIMT处理的任务,一个任务被SIMD引擎的所有lane所同时执行.指令级并行(instruction-level paralleism)指如果处理器能够将指令划分成彼此独立的部分,这些指令也能同时并行执行,因为存在可以并行的资源.

靠近MP的位置是调度器(warp scheduler),它接收需要被MP执行的大量任务.调度器的作用就是将工作以warp的形式分配给MP,将寄存器文件(register file,RF)中的寄存器分配给warp中的线程,然后以最佳的方式对任务进行优先级排序.通常下游工作优先级高于上游工作,如片元着色器有限级高于顶点着色器,这样可以避免堵塞.MP可以处理成百上千个线程来避免内存访问的延迟.调度器可以立刻从一个正在运行的warp切换为准备执行的warp,因为调度器是由专用硬件开发的,所以可以零开销地切换warp.例如,如果当前warp执行一个纹理加载指令,纹理加载指令预计会有较长的延迟,调度器可以立即切换当前warp,用另一个替换它,并继续执行该warp.这样,计算单位得到了更好的利用.

对于片元着色任务,warp调度器会分发几个完整的Quad,因为会计算UV微分,像素都是以Quad的尺度着色(在23.8还会提到).所以如果一个warp的大小是32,那么32/4=8个Quad会被调度器分配执行.这里涉及到一个架构选择问题,有的架构会锁定一整个warp在一个三角形中,有的架构会让不同的Quad在不同的三角形中(一个warp不一个三角形).对于前者很容易实现但是对于小三角形而言会浪费效率(一个warp一个小三角形).后者对于小三角形更加友好.

总体来说,为了更高的计算密度,MP还会打包成Chip,并且GPU也会有更高等级的调度器.更高级的调度器负责把任务按需要提交给GPU的结果为依据,划分分发给warp调度器.在一个warp中有多个线程也意味着该线程中的任务需要和其他线程保持独立,图形处理过程中通常都是这样的.例如对一个顶点着色和片元着色一般也不和其他顶点和片元相关.

现在我们已经了解光栅化和ALU相关,接下来还会介绍内存系统,缓存,纹理.紧接着要介绍的是延迟和占用.

23.3 延迟和占用

延迟是查询发出后到返回结果的这段时间.例如根据地址寻址取值的操作,延迟就是寻址开始到获取内容的时间.从一个纹理单元访问一个纹理值的时间可能需要花费几百甚至几千个时钟周期(GPU中访问内存数据和贴图是很慢的).所以在实际应用中,GPU要尽量隐藏这部分延迟,否则GPU任务执行时间就会被内存访问的时间占据.

一个隐藏延迟的方法就是SIMD处理的多线程过程,也就是23.2中提到的warp调度和切换.MP能处理的warps的最大数量是有限的,活跃的warp数量取决于寄存器使用和纹理采样器,L1缓存,插值器和其他因素.这里我们定义占用率(occupancy,):
是一个MP中可以处理的warp数量最大值,是当前正在使用的warp数量.是衡量计算资源投入使用的效率的量.计算举例:假设,一个着色器处理器有256kB的寄存器大小,一个着色器程序线程使用27个32位浮点数寄存器,一个着色器程序线程使用150个32位浮点数寄存器,SIMD宽度是32,我们为这两个着色器程序分别计算活跃warp数量:(着色器处理器的寄存器总大小/着色器程序总使用的寄存器大小)在第一种情况里,较小的着色器程序占用了27个寄存器,,所以占用率就是1,这是较好的情况,因为能够较好的隐藏延迟.对于第二种情况,,这占用率比较低,会影响到延迟的隐藏.所以要设计出能够更好地平衡最大warp,寄存器大小和其他共享资源的架构.

有时,过高的占用率可能会适得其反,因为如果着色器占用过多内存访问,则可能会打乱缓存,导致缓存失效,让执行更慢了.另一种延迟隐藏机制是在内存请求之后继续执行同一warp,如果有独立于内存访问结果的指令,就继续执行(在同一warp中异步执行以隐藏延迟).程序使用了更多的寄存器,有时就会获得更低的占用率.一个例子是循环展开,它为指令级并行提供了更多的可能性,因为通常会生成更长的独立指令链,这使得在切换之前执行更长时间成为可能。然而这种方式也会占用更多临时寄存器.一般的原则是追求更高的占用率,过低的占用率意味着当一个着色器请求访问纹理时,切换到其他warp更困难.

另一种延迟的原因是GPU和CPU之间的数据通信.把GPU和CPU想象成两个分离的计算机异步地工作,这使得二者之间的通信比较耗费时间.转换二者的信息流向产生的延迟非常影响效率.当从GPU中读取数据时,GPU流水线必须被flush,在这段时间,CPU都在等待GPU完成工作.在一些架构中,如英特尔的GEN,GPU和CPU集成在同一芯片上,并且共享了相同的内存模型,低级的缓存被共享,高级的缓存不共享,这样延迟就被大大缩减了.

不会产生CPU stall的回读机制(read-back,GPU向CPU传递数据)的一个示例是遮挡查询.对于遮挡测试,机制是执行查询,然后偶尔检查GPU看看查询的结果是否可用.在等待结果的同时,CPU和GPU还可以完成其他的工作.

23.4 内存架构和总线

端口(port)是在两个设备间负责发送数据的通道,总线(bus)是在两个以上的设备间发送共享数据的通道.带宽(bandwidth)是用来描述在端口和总线中发送总信息量的量,单位是字节/秒(B/s).端口和总线在计算机图形学中重要的概念因为它们起着将各部分连接起来的作用.带宽也是个重要的资源,所以需要在搭建一个图形系统时精细地设计和分析带宽.因为端口和总线同时提供了数据传递的功能,说端口的时候也经常指的是总线.

对于许多GPU来说,在GPU上拥有独立的GPU内存(显存)非常常见,显存也经常被称为视频内存(video memory).访问显存要比让GPU通过总线访问系统内存快得多.比如PC上的16位PCIe v3总线速度是15.75GB/s,PCIe v4总线速度是31.51GB/s.而Pascal架构的GPU(GTX1080)的显存访问速度是320GB/s.

传统上,显存存储的是纹理和渲染对象,但是其他数据也可以存储在显存中.在一个画面中的许多物体不需要明显地在每帧之间都变换形状.即使是一个人类角色也通常使用不变的网格(mesh)进行渲染,每帧变化的是这些网格在关节处混合的顶点.对于这种数据,单纯依靠模型变换矩阵和顶点着色器程序驱动动画,这种就通常使用静态的顶点缓冲(vertex buffer)和内部缓冲(inner buffer)是放在显存中,这样做可以使GPU访问缓冲的速度更快.对于每帧需要经过CPU计算更新的顶点位置,需要用动态顶点缓冲(dynamic vertex buffer)和内部缓冲(inner buffer)位于系统内存中,这些缓冲能够通过总线访问.PCI的一个很棒的特性是查询是管线形式的,所以查询总会在结果返回前完成.

大多数游戏机器,比如PS4和全部Xbox系列,使用的是统一内存架构(unified memory architecture,UMA),这意味着GPU能够使用主机内存的任意位置来存储纹理和其他种类的缓冲.CPU和GPU使用相同的内存和总线.这就明显与专用的显存有所区别.英特尔也使用UMA所以CPU与GEN9图形架构能使用同一内存.如图所示.但也不意味着所有缓存也都共享.图形处理器拥有其独占的L1,L2,L3缓存的部分,而LLC(last-level cache)才是共享的.对于任何计算机或图形架构而言,拥有一个缓存层次结构是很重要的。如果访问中存在某种局部性,那么这样做可以减少对内存的平均访问时间。


英特尔UMA缓存架构.png

23.5 缓存和压缩

缓存在GPU中是在不同的位置分别存放的,存放的位置每个架构都有不同,如图所示.总体来说,GPU架构增加缓存层级的目的是,通过应用内存访问模式的局部性减少内存延迟和带宽占用.也就是说,GPU访问了一个内容,它很有可能接着访问同样的内容或者其相邻的内容.大多数缓冲和纹理都是按照tiled模式存储的,这也对增加局部性有帮助.假设缓存线由512bit(即64B)组成,并且当前使用的颜色格式每像素4B.一种设计选择是将所有像素存储在64B中的一个4×4区域内,也称为一个tile.也就是说,整个颜色缓冲区将被分割为4×4的tile.一个tile也可以跨越多个缓存线.

为了让GPU架构更加高效,需要在各个方面减少带宽的占用.大多数GPU的硬件单元会将渲染目标实时地压缩和解压.这些压缩算法都是无损的,这样可以完好地恢复回来.压缩算法的核心就是我们称之为tile table的结构,它存储了每一个tile的额外信息.tile table会存储在chip上或者级联内存中.如图所示.总的来说,压缩的方式在深度,颜色和模板是一样的,有时会有一些改动.tile table中的每个元素都储存了帧缓冲中tile内部的像素状态.每个tile的状态可能是压缩的(compressed),解压的(uncompressed)或者清除的(cleared).压缩块(compressed blocks)的压缩的模式也有可能不同.例如,有压缩至25%的也有压缩至50%的.支持的压缩等级取决于GPU能处理的内存转换(memory transfer)的尺寸大小.例如在某种架构中,最小的内存转换大小是32B.如果tile size选择为64B,那么它最大能被压缩到50%(32B),而如果tile size为128B,那么它可以被压缩到75%(96B),50%(64B)和25%(32B).

GPU中缓存和压缩示意图.png

tile table经常被应用在快速清除render target.当系统需要对render target进行清除,tile table中的每个tile的状态都被设置为已清除(cleared),但不涉及到缓冲区本身(tile table通过设置状态就能实现快速clear).当访问渲染目标的硬件单元需要读取已清除的渲染目标时,解压缩单元首先检查tile table中的状态,以查看tile是否已清除.如此,render target tile放进缓存中时,会使用clear value而不需要读取和解压真实的render target数据.通过这种方法,访问render target在清除过程中把访问成本缩小了,同时也节省了带宽.如果tile table中的状态不是cleared,那么就会读取真实的render target,而且如果压缩了,解压缩器就会在发送数据之前解压缩.

当硬件单元访问已经写入新值的render target时,当tile从缓存中移出时,render target会被发送到压缩单元并尝试压缩.如果压缩单元支持两种压缩方式,那么两种方式都会尝试并应用存储占用小的那一个.因为API需要用无损的压缩方式,所以如果所有压缩方式都失败,那么就会使用未压缩的数据.这也说明了无损的压缩算法事实上也不是总能节省存储空间的,但是是节省带宽开销的.如果压缩成功,tile状态就会设置为已压缩(compressed),并以压缩的格式发送数据,反之则tile状态设置为未压缩(uncompressed).

需要注意的是,压缩单元和解压单元在缓冲前后都可以,如上图所示.前缓冲压缩(pre-cache)可以显著增加缓冲的利用效率,但通常也会增加系统的复杂性.大多数颜色压缩算法会对一个代表本tile的锚定值(anchor value)编码,然后记录其他像素值与锚定植的差并按其他方式编码.对于深度值压缩算法,因为深度在屏幕空间是线性的,所以大多数算法使用平面方程或差分方法得到.

使用标准深度(standard depth),即先进行MVP变换再乘以透视除法得到的值,往往是不够的.标准深度的主要问题是深度值不是线性的.大多数情况下,场景的前10%(近场景)将映射到0.0到0.9范围.换句话说,90%的精度在前10%的观察距离内用完.如果你的算法严重依赖深度比较,那会很难办.解决这个问题的方法有两个.
1.让深度信息线性化.
2.将深度反转,后90%深度获得前90%的精度值.

23.6 颜色缓冲区

使用GPU渲染需要涉及到许多缓冲,比如颜色缓冲,深度缓冲和模板缓冲.尽管它叫做颜色缓冲,但是它存储的可以是任意种类的数据.根据颜色的精度不同,颜色缓冲通常可以分为几种模式.High Color:每个像素两个字节,15或16位储存颜色,能够表现32768或65536(unsigned)颜色.True Color或RGB Color:每个像素3到4字节,使用24位储存颜色,能够表现16777216种颜色.Deep Color:每个像素30,36或48个字节,能够表现超十亿种颜色.

High Color模式的颜色,RGB每个通道至少有5位的空间,能够有32种灰度变化.这会多出一位,多出的这一位通常会分配给绿色通道,RGB划分为565的位存储,因为绿色对人眼亮度影响最大,所以需要更大的表示精度.使用High Color因为每像素使用两字节的内存会比更高精度访问更快.但是现在High Color比较少见,因为32至多64级灰度变化使相邻颜色很容易区分,导致banding或者posterization的问题.这种问题的出现是由于人眼感知的Mach banding现象,会放大这些差距.通过将相邻的颜色级别进行混合和抖动,可以通过空间分辨率降低颜色分辨率(不懂)以减少这种影响.即使在24位显示器上,banding还是会被注意到,向frame buffer添加噪声可以掩盖这个问题.

True Color使用24位的RGB颜色,每个通道1字节的空间.在PC平台,有些情况下会存储成BGR格式(如opencv).内部往往会将其存储为32位大小,因为大多数内存系统对访问4字节的元素做了优化.有些系统里额外的8位用来存储alpha通道,让每个像素拥有RGBA四个通道.24位的模式也被称作压缩像素格式(packed pixel format)相比于32位的无压缩格式.在实时渲染中使用24位的颜色就够了,虽然24位颜色也是会出现banding现象,但是比16位颜色更少了.

Deep Color使用30,36或48位空间的RGB颜色,也就是每个通道10,12或16位.如果加入alpha通道的话,就变成了每像素40,48,64位空间.HDMI1.3支持全部30/36/48位的模式,DP(DisplayPort)标准也支持每通道至多16位的色彩.

颜色缓冲一般会如23.5讲述的被压缩和缓存.另外,片元颜色和颜色缓冲混合的各种情况在23.10讲述.光栅操作单元(raster operation unit,ROP unit)负责处理颜色混合,而且每个ROP通常连接到一个内存分区,例如使用通用的棋盘格模式(checkerboard pattern).我们接下来会介绍视频播放控件(video display controller),它获取一个颜色缓冲并让其展示出来.单缓冲,双缓冲,三缓冲也在后面介绍.

23.6.1 视频播放控件(Video Display Controller)

在每个GPU中都存在一个视频播放控件(VDC),也叫播放引擎(display engine)或播放接口(display interface),它负责让颜色缓冲中的颜色播放出来.VDC是在GPU中的硬件单元,能够支持不同接口,比如HDMI(high-definition multimedia interface),DP,DVI(digital visual interface)和VGA(video graphics array).需要播放的颜色缓冲可能位于可供CPU访问的内存中,或者专用的frame buffer内存或者显存中,前两者都可以被CPU访问,而显存不能被CPU直接访问.每个接口都使用其特定的协议来输出颜色缓冲,时序信息甚至音频.VDC可能也能进行图像缩放,降噪,多源图像合成和其他功能.

显示设备,如LCD,显示刷新的速率一般在60-144Hz之间,这也叫做垂直刷新率(vertical refresh rate).大多数用户能在72Hz以下注意到闪屏.见12.5了解更多.
显示器技术在许多方面都有发展,如刷新率,通道位数,色域和同步方面.刷新率以往是60Hz,而现在120Hz越来越普遍,甚至600Hz都出现了.高刷新率下的图像往往会重复出现几次,有时中间会插入黑帧来避免帧间移动导致的模糊.相对于每通道8位的表示,现在HDR显示器每通道可以达到10位甚至更多.Dolby的HDR显示技术使用较低分辨率的LED背光阵列来增强其LCD显示器.这么做会使其能获得相当于普通显示器10倍的亮度和100倍对比度.更宽色域的显示器也会称为趋势.它们可以通过具有纯光谱色调来显示更广泛的颜色,例如更生动的绿色.

为了减少撕裂效果(tearing effect),厂商也开发了自适应同步技术,如AMD的FreeSync和英伟达的G-sync.这是为了使显示器适应GPU的更新频率,而不是使用固定速率.例如前一帧渲染需要10ms而后一帧需要30ms,在画面结束渲染后会立即对显示图像更新.通过这种技术,渲染结果的显示会更加顺滑.另外,如果图像没有更新,出于省电,颜色缓冲不会发送显示.

23.6.2 单缓冲,双缓冲,三缓冲

在2.4章我们提到双缓冲保证了图像在渲染完成前不会展示,在本节会介绍单缓冲,双缓冲和三缓冲.

假设我们仅有单个缓冲.这个缓冲就是现在所显示出来的内容.随着显示器刷新,这一帧中的三角形一个接一个被绘制并出现,这是个很奇怪的效果.即使帧更新率与显示器刷新率相等时,单缓冲也是有问题的.如果我们要清除缓冲或绘制一个大三角形时,我们毫无疑问能看到VDC发送这个绘制过程.这有时被叫做撕裂(tearing),因为显示的图像看起来就像被撕乘两部分一样,这不是实时渲染所希望的特性.在一些古老的系统中,如Amiga中,可以通过测试电子束(beam)的位置来避免绘制这里,也就让单缓冲能用了.现在单缓冲很少有人用了,虚拟现实系统可能例外,因为在虚拟现实中racing the beam是一种降低延迟的方法.

为了避免单缓冲的问题,如今更广泛使用双缓冲.渲染完成的图像会在前缓冲(front buffer)中显示,与此同时不可见的后缓冲(back buffer)会绘制正在渲染的图像.然后当整张图像都发送到显示器后,图形驱动会交换前缓冲和后缓冲,这样避免了图像撕裂.交换操作经常只是交换两个颜色缓冲的指针.对于CRT显示器,这个事件叫做垂直回溯(vertical retrace),整个过程叫做垂直同步(vertical synchronization,vsync).对于LCD显示器,不需要物理上电子束回溯(retrace of a beam),但是我们用相同的名词来表示显示器的交换过程.在渲染完成后瞬时交换前后缓冲是测试一个渲染系统的有效指标,并且也用在其他应用上,因为它能衡量最大帧率.不通过垂直同步刷新也会导致撕裂,但是因为有两个完整的图像,这种伪影并不会像单缓冲一样糟糕.在交换过后,新的后缓冲负责接收图形指令,而新的前缓冲会被呈现给用户.这个过程如图所示.

单双三缓冲.png

双缓冲能够通过增加第二个后缓冲来增强,这个缓冲称为等待缓冲(pending buffer).这就是三缓冲.等待缓冲也像后缓冲一样不可见,它可以在前缓冲播放出来后被修改.等待缓冲构成了三缓冲的循环的一部分.在一帧过程中,等待缓冲会被访问到.在下一次交换时,等待缓冲变成了后缓冲,在后缓冲处完成渲染.然后它变成前缓冲,并被用户所见.在下次交换的过程中,前缓冲又变成了等待缓冲.这个事件过程如上图的下部分所示.

三缓冲相比于双缓冲有个主要的优势.通过使用三缓冲,系统能够在等待垂直回溯的时候访问等待缓冲.而在双缓冲里,系统在等待垂直回溯时会进行缓冲交换,所以画面必须等待.这也就导致了因为前缓冲总是需要显示,而在后缓冲完成渲染前,画面必须保持不变(因为要等待,所以造成帧率下降).三重缓冲的缺点是延迟增加整整一帧的延迟,增加的延迟会导致用户的输入反应延迟,如键鼠和手柄等.用户可能感觉控件反映迟钝,因为对用户事件的反馈需要推迟到等待缓冲展示出来的时候了.

理论上来说,三个以上的缓冲也是可行的.如果计算每一帧的时间变化很大,那么更多的缓冲会平衡播放率,让画面更加平滑,但是代价是更高的延迟.多重缓冲可以想成一个环形结构.有一个渲染指针和展示指针,这两个指针分别指向不同的缓冲,展示指针跟随着渲染指针,当当前渲染帧已经渲染完成后移动到下一帧.唯一规则就是展示指针不能和渲染指针指向相同.

对于PC的GPU而言,一种额外的加速方式是使用SLI模式.1998年的3dfx将SLI作为扫描线交错的缩写,指的是两个图形芯片并行运行,一个处理偶数扫描线,一个处理奇数扫描线.英伟达(收购3dfx)使用SLI的缩写表示完全不同的一种连接两个以上图形卡的方式,称为可升级链接界面(scalable link interface).AMD称其为CrossFire X.这种并行形式通过将屏幕分成两个(或两个以上)水平部分,每个GPU渲染一部分或让每个GPU完全呈现自己的帧,交替输出来将工作进行划分.还有一种模式允许卡加速抗锯齿的同一帧.最常见的用法是让每个GPU分别渲染一个单独的帧,称为交替帧渲染(AFR).虽然这个方案听起来似乎会增加延迟,但它通常很少或根本不会.假设一个单一的GPU系统呈现为10帧每秒.如果GPU是瓶颈,使用AFR的两个GPU可以以20帧/秒的速度渲染,甚至可以以40帧/秒的速度渲染4个GPU.每个GPU渲染帧所需的时间相同,所以延迟不一定会改变.

屏幕分辨率在不断增加,这对基于单个像素的渲染采样来说是一个严峻的挑战.一种保持帧率的方式就是自适应地根据屏幕和表面调整像素着色率.

23.7深度裁剪,深度测试和深度缓冲

本节将讲述关于深度的内容,包括深度分辨率,测试,裁剪,压缩,缓存,缓冲和提前深度测试(early-z).

深度分辨率很重要因为它能帮助避免渲染的错误.例如,你建模了一叠纸并把它放在桌子上,距离桌子表面非常近的上方.在有精度限制的z深度下计算桌子和纸的深度,桌子可能在不同的位置与纸出现穿模,出现斑点.这个问题叫做深度冲突(z-fighting)现象.假设如果纸刚好放在了桌子的同样高度,纸和桌子共面,那么需要额外的信息才能正确地描述它们的关系(深度测试无法判断二者关系),这是由于建模错误导致的,提高z精度也无济于事.

深度缓冲(也叫z缓冲)可以用来解决可见性问题.这种缓冲通常每像素(或每采样点)具有24位或32位深度的浮点数或定点数表示.对于正交视角,距离量和z值成正比,所以精度分布是均匀的.而对于透视视角,精度分布是不均匀的(近处10%占用了90%的精度).在应用透视变换后,需要做透视除法,各分量同除w分量.深度分量变成了,是经过mvp变换的顶点.如果z是定点数表示的,的值还从其有效范围被映射到了整数范围(b是位数)并储存在了深度缓冲中.

深度管线的硬件如图所示.这个管线的主要目的是测试输入的深度(该深度是光栅化面元时生成),如果该片元通过了深度测试就把输入深度写入到深度缓冲中.与此同时,管线需要尽可能地高效.图片的左部分从粗糙光栅化(coarse rasterization)开始,也就是tile级别的光栅化,这时只有和面元有重叠的tile能够进入下一个阶段,也就是深度剔除(z-culling)技术应用的HiZ单元.

深度管线硬件.png

HiZ单元从叫做粗糙深度测试(coarse depth test)的块开始,这里进行两个测试.一是最大深度剔除(zmax-culling),这是Greene的层级深度缓冲算法的简化.思路是存储tile中的深度最大值,称作.tile的大小是根据各架构而不同的,最广泛的是8*8像素的.这些值会存储在固定的片内存储器(on-chip memory)或通过缓存访问,图中表示为HiZ cache.如果我们想测试一个三角形在该tile中是不是完全被遮挡了,我们需要计算出tile内三角形最小的z值,如果,这就保证了三角形被先前tile内渲染的几何体遮挡了.那么在tile内对这个三角形的处理就可以结束了,这也就节省了逐像素的深度测试.但需要注意,这并不意味着会跳过逐像素的片元着色器的执行,因为逐像素的深度测试会结束后续管线的片元着色.在实际上,我们不会准确计算的具体值,而是会估算一个保守值.这里有两个计算的方法,每种都有优缺点.

  • 1.三角形三个顶点的最小z值,虽然不保证总是准确,但额外计算量很小.
  • 2.使用平面方程计算三角形在tile四个角的z值,并使用其中的最小值.

这两种方法结合能实现最好的裁剪表现,通过获取两个中的大值.

另一种粗糙深度测试的方法是最小深度剔除(zmin-culling),想法是储存tile中的所有像素的.这个测试有两个作用.其一,是能避免读取深度缓冲.如果一个正在渲染的三角形相比于先前渲染的所有几何体都绝对地靠前,那么深度测试对于它来说就是不需要的.在一些情况下,读取深度缓冲能够被完全避免,这能大大优化表现.其二,能够用来支持不同种类的深度测试.对于zmax-culling方法,我们假设使用标准的"less than"深度测试(也就是写入深度值更小的).然而如果裁剪过程能够也使用其他的深度测试,并且zmin和zmax都可用的话,那么裁剪过程就能支持所有的深度测试方法(这么做确保了支持多样的深度测试和裁剪方法).

上图中绿色的部分用于以不同方式更新tile的和值.如果三角形包含了整个tile,将由HiZ单元直接完成更新计算.否则,需要读取tile内每个采样点的深度,并通过反馈HiZ更新(feedback HiZ update)发回HiZ单元中,这会造成一些延迟.

对于经过粗糙深度测试的tile,会应用边缘函数计算像素或采样点覆盖,以及计算逐像素的深度(称为深度插值,z-interpolate).这些值会发送到图右所示的深度单元.根据API的描述,后面将跟着片元着色器.然而,在一些情况下,提前深度测试(early-z或early depth)是在片元着色器前进行的逐像素的深度测试,被遮挡的片元会被丢弃.这个过程就避免了不必要的片元着色器执行.提前深度测试经常与深度剔除(z-culling)混淆,这两个过程是分别由不同的硬件执行的,二者的执行是独立的,不受对方影响.

最大深度剔除,最小深度剔除和提前深度测试是GPU在不同情况下自动执行的.然而,比如在片元着色器写入了自定义的深度,使用了丢弃操作或者向UAV中写入了值,在这些情况下最大深度剔除,最小深度剔除和提前深度测试是会被关闭的.如果提前深度测试没有使用,那么深度测试就会在片元着色器后执行(称作后深度测试,late depth test).

着色器资源视图(SRV)通常以方便着色器访问纹理的方式围绕纹理.
无序的访问视图(UAV)提供类似的功能,但支持以任何顺序读取和写入到纹理(或其他资源).
环绕单个纹理可能是着色器资源视图(SRV)最简单的形式. 更为复杂的示例包括子资源集合(细化纹理的个别阵列、平面或颜色)、3D 纹理、1D 纹理颜色渐变等.
无序的访问视图(UAV)在性能方面费用稍高,但支持同时读/写纹理等功能.借此,图形管道可将已更新的纹理用于其他目的.着色器资源视图(SRV)具有只读用法(这是>最常见的资源用法)

在最新的硬件上,可能会支持原子的读取-修改-写入操作,从着色器中加载或者储存图像的功能.在这些情况下,你可以忽略上面的限制并显式的启动提前深度测试.还有个特性是,在片元着色器输出自定义深度值是保守深度值时,也可以启用.比如,如果程序员确保自定义深度比三角形中的深度大时,就可以开启提前深度测试和最大深度剔除.

一般来说,从前到后的渲染有利于遮挡剔除.另一种具有类似名称和目的的技术是z-prepass.思路是程序员首先渲染一遍场景,但仅仅写入深度信息,关闭片元着色和颜色缓冲的写入.当渲染后面的pass时,只会对深度做"equal"的测试,也就是说只有最前面的面会被着色,因为深度缓冲已经初始化完毕了.(第一遍pass只写深度,第二遍pass做equal深度测试写入颜色,这是软件层面的深度测试,方便从后到前渲染时的高效渲染)

概括一下本节.我们简单介绍了深度管线的缓存和压缩,在图片的右下呈现.整体的压缩环节和23.5描述的相同.每个tile会被压缩成一些大小,并且经常会在压缩失败的时候使用非压缩数据的回调(fallback)操作.为了在清除深度缓冲时节省带宽占用会使用快速清除的方法.因为深度信息在屏幕空间是线性的,典型的压缩算法要么存储高精度的平面方程,要么使用差分技术和增量编码,要么使用锚定方法.tile table和HiZ缓存可能会被完全储存在on-chip内存里,或者通过内存层级交流,深度缓冲也是如此.on-chip的存储开销很大,因为需要足够大的内存来保证分辨率.

23.8纹理采样

当进行纹理采样操作时,会执行获取,过滤和解压缩,虽然这些操作可以在GPU上运行的纯软件方式解决,但是已经证明了使用特定功能的固件进行纹理采样能够快40倍.纹理单元会对纹理格式进行寻址,过滤,clamp和解压操作.它和纹理缓存(texture cache)一起可以减少带宽占用.我们首先关注过滤和它对纹理单元的影响.

为了应用缩小过滤器,比如mipmap和各向异性过滤,需要计算纹理坐标在屏幕空间的微分.也就是计算纹理细节LOD等级,我们需要计算,,和.它们表示,纹理的区域或功能是通过片元表现的.如果通过顶点着色器的纹理坐标被用于直接从纹理中取值,然后微分会被解析地计算出来.如果纹理坐标是通过某种函数进行了转换,比如,那么解析地计算微分就会变得更加复杂,虽然也是可以使用链式法则和符号微分来计算的.尽管如此,GPU并不专门提供图形硬件层面的支持,因为计算可能非常复杂.想象用环境光贴图为带有凹凸贴图的表面计算反射,这时候要得到反射向量的微分,是很难解析地计算出来的.所以,微分总是在一个quad(也就是2*2的像素)中,数值地计算x与y的差分.这也是GPU将quad作为调度单位的原因.

一般来说,微分计算总是在后台的,也就是用户不可见的.实际的实现方式通常是在一个quad上应用cross-lane指令(shuffle/swizzle),编译器会自动插入这些指令.一些GPU会使用混合函数(mixed-function)硬件来计算这些微分.没有固定计算微分的方式.一些广泛用的方法在图里标明了.OpenGL4.5和DirectX 11都支持了计算粗细导数的函数.

纹理坐标计算微分.png

纹理坐标的计算方式.一种是计算quad四点处

所有的GPU都会进行纹理缓存,目的是减少纹理对于带宽的占用.一些架构使用专用的纹理缓存,或者甚至两个专用的纹理缓存级别,而一些架构对所有缓存的访问都使用共享的缓存.通常使用小型的on-chip内存(通常为SRAM)来实现纹理缓存.这个缓存储存了最近的纹理读取结果,拥有很快的访问速度.缓存置换方法和大小是取决于架构的.如果相邻像素需要访问相同或附近的纹素块,通常在纹理缓存里会访问.正如23.4提到的,纹理在GPU中通常是哟你tiled的顺序存储,如4*\4纹素,而非采用扫描线顺序存储,由于一个tile的纹素都是一起获取的,所以这能提高访问效率.tile的内存大小一般是和缓存中线大小相同,比如64字节.另一种存储纹理的方式是使用swizzled模式.假设纹理坐标已经转换成了定点数,每个占用位.的第位表示为.然后重映射到swizzled纹理坐标

是纹理的初始地址,是一个纹素占用的内存大小.重新映射的方法的优点是能够让纹素保持如图所示的连续增加的内存分布.这是一种叫Morton sequence的空间填充曲线(space-filling curve),它经常用来改进一致性.在纹理坐标方面,Morton sequence是二维的,纹理刚好也是二维的.

纹理单元中会包含不同的解压纹理格式的结构.使用硬件级的支持要比软件级的支持效率提高数倍.注意到当使用纹理图当作渲染对象和纹理贴图时会出现其他的压缩机会.如果启动了颜色缓冲的压缩,那么当以纹理的形式访问这个渲染对象时有两个设计选项.当渲染对象结束了它的渲染过程,一个选项是从颜色缓冲的压缩格式中解压缩出整个渲染对象,然后将其以菲亚所的方式存储方便后续访问.第二个选项是,在纹理单元中增加对颜色缓冲压缩格式解压的硬件支持.后者是更加高效的选择,因为渲染对象会在访问过程中保持压缩状态.了解更多压缩和缓冲请看23.4

MortonSequence.png

Mipmap对于提高纹理采样缓存局部性很重要,因为它可以最大化纹素-像素比例.当遍历三角形时,每个新像素尽可能对应纹理空间的一个纹素.Mipmap是提升视觉和性能的几种渲染技术之一.

23.9 架构

实现快速的图形处理的最好方式就是应用并行性,GPU能够在几乎所有阶段实现并行.思路是同时计算多个结果并将这些结果在后续阶段融合操作.总体上,一个并行的图形架构会是如图所示的样子.分为四个阶段.应用阶段为GPU发送任务,经过调度,进入几何处理阶段.几何阶段由诸多几何单元(geometry unit)并行处理.几何阶段后是光栅化阶段,几何单元的处理结果会进入一组光栅化单元(rasterizer unit)中,在光栅化单元进行光栅化.下一阶段是片元着色和融合阶段,也是通过像素处理单元(pixel processing unit)并行执行的.最终结果图像会发送到显示器.

GPU架构拓扑.png

对于硬件和软件来说,弄清楚你的硬件或代码中是否有串行的部分是很关键的,它会限制你可能的性能改进总量.Amdahl法则(Amdahl's law)有以下表述:

是程序或硬件串行的比例,是适合并行的比例,是通过将程序或硬件并行化能够获得最大性能提升的倍数.例如,如果我们本来有一个多处理器,额外增加了三个,那么.这里是能从改进中获得的加速倍数.如果我们有一个架构,实现了10%串行,也就是,我们改进了架构,使剩余的(非串行)部分能改进20倍,也就是,于是我们有.我们能看到,并非提速了20倍,因为程序或硬件中串行的部分严重地限制了性能.事实上,当时,.应该花费精力在改进并行部分还是串行部分并不总是清晰的,但是当并行部分得到大幅改进后,串行的部分会更加限制性能.

对于图形架构而言,许多结果是并行计算出来的,但是draw call中的面元是按它们被CPU提交的顺序处理的.所以,需要进行排序以保证并行的单元能一同渲染出用户需要的图像.具体地讲,需要从模型空间到屏幕空间进行排序.需要注意的是,几何单元和像素处理单元可能被映射到相同的单位,即统一的ALU.我们23.10讲的所有的架构使用统一的着色器架构(unified shader architectures).即使这样,了解排序发生的位置也很重要.我们对并行架构进行分类.排序可能会发生在管线的任何位置,这会导致并行架构中的四个不同种类的工作分布,如图23.17所示.被称为sort-first, sort-middle, sort-last fragment,和sort-last image.注意这些架构导致了GPU在并行单元中分配工作的不同方式.

![sort-first-based架构.png](https://upload-images.jianshu.io/upload_images/27541119-1f16fd10b3e03a12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

sort-first-based架构在几何阶段前排序面元.策略是将屏幕划分为一系列区域,区域中的面元被送入负责这个区域的一条完整的管线中.如图所示.在排序的步骤中,对面元的初始处理就足以让它知道该被送入哪个区域.对于机器而言,sort-first模式是最后一种被使用的架构.当驱动一个包含多个屏幕或形成一个大屏幕的投影仪的系统时,它确实是一种有用的方案,因为单个计算机专用于每个屏幕.有一种名为Chromium的系统,它可以使用一组工作站实现任何类型的并行渲染算法.例如,sort-first和sort-last能够高性能地渲染.

sort-last-image架构.png

Mali架构是一种sort-middle的架构.每个几何处理单元处理的几何体数量大致相同.然后经过变换的几何体会被排序到不重合的矩形中,称为tiles,所有的tiles覆盖了整个屏幕.注意一个经过变换的三角形可能会与几个tile重合,也就是会被不同的光栅化单元和像素单元处理.高效处理的关键是每个光栅化单元和像素处理单元的对,拥有一个与tile相同大小的(tile-sized)on-chip帧缓冲,这意味着所有帧缓冲的访问速度都很快.当所有的几何体都被排序为tiles,对每个tile的光栅化和像素处理就会独立地进行.一些sort-middle架构,在对tile处理时,针对不透明的几何体应用提前深度测试,这也就意味着每个像素只会被着色一次.然而,不是所有的sort-middle架构都这样做.

sort-last fragment架构在光栅化(有时叫做片元生成)后,在像素处理前对片元进行排序.例如23.10.3介绍的GCN架构.正如sort-middle架构,面元被尽可能均匀地分发给了几何单元.一个sort-last fragment架构的优点是不会有任何的遮挡,也就是说一个生成的片元会被发送到一个像素处理单元中,这正是最优情况.如果一个光栅化单元处理巨大的三角形而另一个处理小三角形时,便会变得不平衡.

sort-last image架构在像素处理后进行排序.如图所示.整个架构能被看作是独立的管线的集合.面元被分配给管线,每个管线渲染一个带有深度信息的图像.在最后的组合阶段,所有的图像会被根据其各自深度缓冲混合.需要注意的是sort-last image系统不能完全被OpenGL和DirectX执行,因为它们需要面元以发送的顺序渲染.PixelFlow是sort-last image架构的例子.PixelFlow架构需要注意它使用延迟着色,也就是说它只会为看得见的片元着色.要注意因为管线尾端的大量带宽占用问题,并没有现有的架构使用sort-last image架构.

sort-last-image架构.png

纯粹的sort-last image主题在large tiled display系统上的一个问题是,需要在渲染节点之间传输的大量图像和深度数据,Roth 和 reiner的方法通过使用每个处理器结果的屏幕和深度边界来优化数据传输和组合成本.

Eldridge等人提出了Pomegranate架构,是一种任意阶段排序的架构.简单来说,它在几何阶段和光栅化单元之间,在光栅化单元和像素处理单元之间,像素处理单元和展示之间,都插入了排序阶段.因此随着系统规模的扩大,工作会更加保持平衡(因为增加了更多的管线).排序阶段通过带有点对点连接的高速网络实现.模拟结果显示,它能够随更多的管线加入,达到近乎线性的优化表现.

图形系统的所有组成部分(主机,几何处理,光栅化和像素处理)相互连接并构成了一个多处理系统.在这种系统中,有两个与多处理相关的问题:负载均衡(load balancing)和通信(communication).FIFO(first-in first-out)队列队列通常插入到管线中的许多不同位置,因此可以排队执行任务,以避免管线的部分停止.例如,可以在几何单元和光栅化单元之间放置FIFO,可以将经过几何处理的三角形放入缓冲,以免由于三角形过大(等原因)导致光栅化单元跟不上几何单元的处理节奏.

不同的排序架构描述有各自不同的负载均衡优缺点.参考Eldridge’s PhD理论或Molnar et al的论文了解更多.编程者也可以影响负载均衡,相关技术在18章介绍.如果总线带宽过低或低效利用的话,通信也会变成问题.有必要设计一套应用上的渲染系统,使性能瓶颈避开总线,也就是从主机到图形硬件的总线.18.2章介绍了检测瓶颈的不同方法.

23.10 GPU架构案例

在这节,我们将介绍三个不同的图形硬件架构.首先介绍ARM Mali G71 Bifrost架构,主要应用于移动设备和电视.其次是NVIDIA的Pascal架构.最后介绍AMD GCN架构Vega.

注意图形硬件厂商的设计决策通常是建立在对还没有实现的GPU进行大量软件模拟的基础上的.也就是说,建立一个参数化的软件模拟体系,一些应用如游戏,配置不同的参数运行在这些参数化软件模拟系统上.比如说,可能的参数有MP的个数,时钟频率,缓存的数量,光栅化引擎/细分曲面引擎的数量,光栅化处理器(ROP)的数量等.此模拟器是用于收集这些参数对于性能,耗电量,内存带宽占用等的信息.在最后,能在大多数情况下运行得最优的参数配置会被选中,并依照此参数配置生产出芯片.另外,模拟器也能找出架构中的性能瓶颈,以便针对性地解决,比如增加缓存的大小.对于某个特定的GPU,使用不同速度和数量的单元的原因很简单,"it works best this way".(别问,问就是it works)

23.10.1 案例学习ARM Mali G71 Bifrost

Mali产品线包含了ARM的所有GPU架构,Bifrost是其2016年以来的架构.这个架构的目的是移动端和嵌入式系统,比如手机,平板和电视.2015年,基于Mali的GPU出货了7.5亿片.因为许多芯片是用电池供电的,Mali产品线GPU的架构设计会关注节能而不是性能表现.于是,这使得sort-middle架构成为可能,因为sort-middle架构所有帧缓冲访问在芯片中,这种设计降低了能源消耗.所有Mali架构都是sort-middle类型的,有时也称为tiling架构.这种GPU的总览如图所示.G71能支持至多32个统一的着色器引擎(shader engine).ARM使用着色器核心(shader core)指代着色器引擎,但是我们继续使用着色器引擎以避免用词冲突.一个着色器核心一次性能够执行12个线程,也就是说它拥有12个ALU.32个着色器核心是专为G71设计的,但是它可以扩展到32个以上的数量.

G71架构总览.png

驱动软件为GPU分配工作.作业管理器(job manager)也就是调度器(scheduler)将工作分发到着色器引擎上.所有的引擎通过GPU fabric连接在一起,GPU fabric是一个着色器引擎能和其他引擎通信的总线.所有的内存访问是通过内存管理单元(memory management unit,MMU)发送,它能将虚拟内存地址转换为物理内存地址.

一个着色器引擎的总览如下图所示.能够看到,它包含三个执行引擎(execution engine),以对quad的着色为核心.而且,这些执行引擎被设计为SIMD宽度为4的小型通用处理器.每个执行引擎包含4个用于32位浮点数的混合乘加单元(fused-multiply-and-add,FMA unit)和四个32位加法器.也就是说每个着色器引擎拥有3*4个ALU(执行引擎4个ALU),也就是12个SIMD通道.一个quad也就相当于一个warp.为了隐藏访问纹理的延迟,每个着色器引擎能同时保持256个线程.

着色器引擎总览.png

需要注意着色器引擎是统一的,能够执行计算着色,顶点着色和片元着色等.执行引擎也支持许多超越函数计算比如和.另外,当使用16为浮点数精度时,性能会提升两倍.这些单元还支持在寄存器结果只用作后续指令的输入时,绕过寄存器的内容,因为寄存器文件不需要被访问,所以会省电.另外,比如当访问纹理或其他内存时,quad管理器会将一个quad切换进来(访问纹理很耗时),就像其他的架构在遇到访问纹理等操作时需要隐藏延迟一样.注意这发生在小粒度层面,交换4个线程而不是全部12线程.读取/储存单元(load/store unit)负责处理一般的内存访问,内存寻址转换和一致性缓存.属性单元(attribute unit)处理属性索引和寻址,它将访问信息输送给读取/存储单元.变化单元(varying unit)执行变化属性(varying attribute)的插值.

tiling架构(sort-middle架构)的核心思想是首先执行几何处理,以便找到每个要渲染的图元的屏幕空间位置.同时为帧缓冲(framebuffer)中的每个tile建立一个包含着各个图元重叠哪个tile的指针的多边形列表(polygon list).这步以后,与tile重叠的图元的集合就已知了.于是,tile中的图元就能被光栅化和着色,结果储存在on-chip tile内存中.当tile渲染完毕它中的所有面元,tile内存的数据会通过L2缓存写回到外部内存中,这样减少了内存带宽占用.接着下一个tile被光栅化,然后接着,直到整个帧都渲染完成.第一个tiling架构是Pixel-Planes 5,这个架构在高层上和Mali架构很相似.

几何单元.png

几何处理和像素处理的过层如图所示.如上图所示,顶点着色器被分成了两份,一个只进行位置着色,另一个被称为变化着色在tiling环节之后完成.这种结构相比于ARM以前的架构来说更加节省内存带宽.分拣操作(binning)确定哪些tile与图元重叠,其需要的唯一信息是顶点位置.分片器单元(tiler unit)如下图所示,按层级的方式执行分拣(binning)操作.这有助于使分拣操作内存占用更小,更可预测,因为它不与图元大小成正比(也就是说层级大小固定).

层级分片操作.png

当分片器结束为所有场景中的图元分拣后,它已经完全知道哪个图元和某个tile重叠的信息.于是,剩下的光栅化,像素处理,混合等流程对于任何数量的tile都可以进行并行处理了,当然要在着色器引擎能并行处理的个数范围内.一般来说,一个tile要被提交到着色器引擎,由该引擎处理所有在此tile中的图元.当每个tile的工作都完成后,就可以开始为下一帧的渲染进行几何处理和分片了.这个处理模型意味着在tiling架构中会出现更多的延迟.(不懂)

此时,接下来是光栅化,片元着色器处理,混合和其他逐像素的操作.tiling架构最简单重要的特性是为每一个tile的帧缓冲(包括颜色,深度,模板等),能够存储于on-chip内存中,称之为tile内存.这个开销很小,因为tile只有16*16的大小,是很小的.当tile内的全部渲染完成后,tile所需的输出(通常是颜色,有时是深度)会拷贝到一个与屏幕大小相同的off-chip缓冲内(位于外部存储中).这意味着在像素处理过程中所有对于帧缓冲的访问会高效到近于零开销.因为使用总线的能量消费非常高,所以非常需要避免使用外部总线.当从on-chip tile内存向off-chip帧缓冲交换数据时,帧缓冲也可以用压缩的形式.

Bifrost支持像素本地储存(pixel local storage,PLS),一种通常被sort-middle架构支持的拓展集.通过PLS,可以让片元着色器访问帧缓冲中的颜色并从而使用自定义混合技术(custom blending techniques).相对地,混合一般通过API配置并且是不允许通过片元着色器编程的.我们也可以使用tile内存存储任意固定大小的数据结构.这就允许了程序员高效地实现例如延迟着色技术.G-buffer(如法线,位置和漫反射纹理)在第一个pass中被存入PLS.在第二个pass执行光照计算,并积累结果存入PLS.在第三个pass应用PLS中的信息计算最终的像素颜色.对于单个的tile,所有计算都发生在整个tile内存还on-chip的情况下进行的,因为这能加快速度.

所有Mali架构在设计时都考虑到了MSAA,它们实现了第143页所述的旋转网格超采样(RGSS)方案,每个像素4个采样点.sort-middle架构非常适合抗锯齿设计.这是因为在tile离开GPU被送往外部内存的前一步实现了过滤.因此外部内存的中的帧缓冲只需要存储每个像素一个颜色.标准的架构需要四倍大的帧缓冲.对于tiling架构,只需要增加四倍的on-chip的tile缓冲,或者高效地使用更小的tile(宽度和高度的一半).

Mali Bifrost架构能选择性地在一组渲染图元中选择使用多采样(multisampling)或超采样(supersampling).这意味着在一个像素内多个采样点都分别执行一次片元着色器的昂贵的超采样方法,也能随需求使用.举例来说是使用alpha映射渲染纹理树,在这里需要高质量地采样来避免视觉伪影,对于这里的图元而言,需要开启超采样.当复杂的情况结束,需要渲染的只是简单的物体,我们可以切换回使用更小开支的多采样方法.此架构也支持8倍和16倍MSAA.

Bifrost(和其前身架构Midgard)也支持一种叫做事物消除(transaction elimination)的技术.它的思路是避免不是本场景完整的一帧的帧缓冲从on-chip到off-chip的内存转换(避免部分tile的帧缓冲内存转换).对于当前帧,每当tile内存被转换,会为该tile计算一个唯一的标识.这个标识是一种校验和.对于下一帧会在当tile的内存即将要转换时计算校验和.如果某一tile前一帧的标识和当前帧的标识是相同的,那么架构会避免将on-chip内存写出到off-chip内存中,因为这个tile两帧是一样的,而上一帧的内存已经写好了.事实上,这在许多移动游戏中很有用,比如愤怒的小鸟,因为一帧中只有很少部分需要更新.注意到这种技术在sort-last架构中的应用是不同的,因为sort-last架构不是基于tile的处理.G71也支持智能组合(smart composition),它能够应用在用户接口组合时实现事务消除.如果一块像素中所有源都和前一帧相同并且操作也都相同,那么可以避免读取,组合,写入这块像素.

低等级的省电技术也在这架构中频繁使用,例如时钟门控(clock gating)和电源门控(power gating).这意味着管线中未使用的或不积极的部分会关闭或者保持低能耗待机来降低电能使用.为了减少纹理带宽,该架构还准备了独立的解压缩单元用于ASTC和ETC.另外,压缩的纹理以压缩形式储存在缓存中,而不是解压以后将纹素放入缓存中.这意味着每当需要访问纹素,硬件会在缓存中读取块然后动态解压缩块中的纹素.这种设置可以增加缓存中的可用区域大小,并提高效率.

总体来说,tiling架构的一个优势是它天生地设计了tile的并行处理结构.比如说,所有着色器引擎可以同一时间独立地渲染各自的tile,更多的着色器引擎也可以添加进来.tiling架构的缺点是整个场景的数据需要发送到GPU中分片,并将处理后的几何体流传送到内存中去.总的来说,sort-middle架构并不是处理几何体放大的理想选择,比如应用几何着色器和细分曲面,因为更多几何体会增加用于前后shuffling几何体的内存传输量.对于Mali架构,几何着色和细分曲面都是GPU上用软件实现的,而且'Mali best practices guide'建议永远不要用几何着色器.对于大多数内容,sort-middle架构能够在移动设备和嵌入式系统中表现很好.

23.10.2 案例学习:英伟达Pascal

Pascal是英伟达的GPU架构,它包含了图形部分和计算部分,计算部分针对的是高性能计算和深度学习应用.在这节我们主要关注图形部分,具体地尤其是在GTX1080.我们会自下而上地介绍,从最小的同一的ALU开始,然后构建起整个GPU.在这一节的结尾,我们会简要提到其他的芯片.

ALU和multi-processor.png

英伟达把Pascal图形架构中使用的统一的ALU称为CUDA核心(CUDA core),其高层图如上图左侧所示.ALU的中点是浮点数和整数运算,但是它们都支持其他操作.为了增加计算性能,几个这样的ALU被组合成了流处理器(streaming multiprocessor,SM).在Pascal的图形部分,SM包括四个处理块(processing block),每个处理块拥有32个ALU.这意味着SM能同时处理4个warps每个拥有32线程.如下图所示.

PascalSM.png

GTX1080共有2560个CUDA核心,也就是说总共80个处理块,20个SM

处理块也可以叫做宽度为32的SIMT引擎,每个处理块拥有8个读取/储存单元(load/store unit,LD/ST unit)和8个特别功能单元(special function unit,SFU).读取/存储单元负责向寄存器文件内的寄存器中读取和写入数值,寄存器文件大小为16k*4B,也就是每个处理块中的寄存器文件有64kB,加起来每个SM有64kB*4的寄存器文件大小.SFU处理超越函数指令,如sin,cos,exp2,log2,倒数和倒数平方根.还支持属性插值.

SM中的所有ALU共享一个指令缓存,而每个SIMT引擎(处理块)拥有独占的指令缓冲,缓冲区里包含最近读取的指令集合以提高指令缓存命中率.warp调度器每个时钟循环负责分发两个warp指令,例如,在同一时钟循环中工作能够同时调度给ALU和LD/ST单元.注意每个SM中有两个L1缓存,每个缓存24kB,也就是每个SM内48kB的L1缓存.使用两个L1缓存是因为如果使用更大的L1缓存会需要更多的读与写端口,这会增加缓存的复杂性并导致芯片上的缓存面积更大.另外,每个SM有8个纹理单元.

由于渲染需要在2*2像素的quad中完成,warp调度器找到8个不同像素的quad并且将他们组合在32个SIMT通道中执行.由于统一的ALU设计,wrap调度器能组合顶点着色器,片元着色器,图元或计算着色器,进入warp中工作.注意SM能同时处理不同种类的warps(如顶点,片元和图元).这架构也能零重叠地来切出当前执行的warp到准备执行的warp.Pascal会挑选什么样的warp来执行的细节我们不得而知,但是从英伟达以前的架构或许能找到暗示.在英伟达2008年的架构Tesla中,一个记分版(scoreboard)被用来衡量每个时钟循环中每个warp.记分板是一种通用的机制,允许无冲突地无序执行.warp调度器在warps中选择准备好执行的warp,比如不在等待纹理读取返回的warp,然后选择一个给予最高优先级.warp类型,指令类型,和公平性是选择最高优先级warp的依据.

SM是和polymorph引擎(PM)组合工作的.这个单元是在Fermi芯片的第一个实现中出现的.PM执行数个几何相关任务(geometry-related tasks)包括顶点获取,细分曲面,同时多投影(simultaneous multi-projection),属性装配和流输出.第一个阶段从全局顶点缓冲获取顶点并分发warps给SM用于顶点和hull着色.然后接着是可选的细分曲面阶段,这个阶段新生成的patch坐标被分配给SM进行域着色(domain shading)和可选的几何着色.第三阶段处理视口变换和透视校正.另外,可选的同时多投影步骤在此执行,比如它能让VR渲染更高效.接下来是可选的第四阶段,这里顶点以流的方式输出到内存.最终,结果被提交到相关的光栅引擎(raster engine)中.

光栅引擎有三个任务,三角形装配,三角形遍历和深度剔除(z-culling).三角形装配获取顶点,计算边缘函数,然后执行背面剔除(backface culling).三角形遍历使用层级tile遍历技术(hierarchical tiled traversal technique)来访问与三角形重叠的tile.它使用边缘函数执行tile测试以及执行内部测试.在Fermi架构里,每个光栅化器每个时钟循环内能处理至多8个像素.对此Pascal并没有公开的数据.深度剔除单元处理基于逐个tile的剔除,使用23.7提到的技术.如果一个tile被剔除完毕,那么这个tile的进行会被立刻终止.对于留下来的三角形,逐顶点的属性会被转换进平面方程,为了在片元着色器中的表现更加高效.

流处理器(streaming processor)与polymorph引擎的组合叫做纹理处理簇(texture processing cluster,TPC).在更高一层,5个TPC一组组成图形处理簇(graphics processing cluster,GPC),由一个光栅引擎伺服5个TPC.一个GPC可以看作一个小的GPU,它的目的是组成均衡的硬件单元集,提供图形处理的功能,如顶点,几何,光栅,纹理,像素和ROP单元.我们会在本节最后看到,创建独立的功能单元可以让设计人员更容易地创建一个具有一系列功能的GPU芯片家族.

到此我们有了构成GTX1080的大多数块.它包括四个GPC,总体如图所示.注意到图中有另一级别的调度器,有GigaThread engine驱动,在PCIev3的接口旁边.GigaThread engine是一个全局工作分配引擎,为所有GPC调配线程.

![Pascal压缩效率对比.png](https://upload-images.jianshu.io/upload_images/27541119-c6ef9f3aec84ee41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

光栅操作单元(raster operation unit,ROP unit)也在图中有显示,尽管有隐藏.他们位于中间的L2缓存的正上方和正下方.每个块是一个ROP单元,被分成了8组,每个组有8个ROP,共有64个ROP.ROP单元的主要任务是写入输出到像素和其他缓冲,以及进行多种操作,例如混合.如图左侧和右侧所示,一共有8个32位内存控制器,一共是256位.8个ROP单元与一个内存控制器和256kB的L2缓存绑定(每组一个控制器和256kB缓存).每个芯片提供了总共2MB的L2缓存.每个ROP绑定到了一个特定的内存分区,这意味着一个ROP处理缓冲中特定子集的一些像素.ROP单元也处理无损的压缩.共支持三种压缩格式,另外还支持解压和快速清除(fast clear).对于2:1压缩(也就是256B压缩到128B),每个tile会存入一个参考颜色值,各像素与参考颜色的插值会被编码,插值编码会比原本颜色编码更省空间.4:1的压缩是2:1压缩的拓展,这个模式只会在插值能被使用更少空间编码的情况下才会采用(能压才压,不强求),而且它只能在tile内容平滑变化的情况下.也有8:1压缩的模式,8:1压缩是把2*2像素块的4:1恒定颜色压缩,应用了2:1压缩.GPU总会使用最高压缩率的模式,8:1优先级高于4:1高于2:1,当所有的压缩尝试都失败后,tile会被转换并以非压缩的方式存储在内存中.Pascal压缩系统的效率如图所示.

Pascal压缩效率对比.png

Pascal架构使用的显存是GDDRX5,时钟频率是10GHz.上面我们看到8个内存控制器提供了总共256位,也就是32B.这提供了总共320GB/s的最高内存带宽,但是带有压缩技术的多级缓存能提供更高效的频率.

芯片的基础时钟频率是1607MHz,当电力充足时它能经常以1733MHz的增压模式(boost mode)运行.峰值计算能力是(浮点数运算个数):

其中,2是融合乘加经常被当作两个浮点数运算,然后我们除了来将MFLOPS转换成TFLOPS.

英伟达开发了很长时间的sort-last fragment架构.然而从Maxwell开始,它们支持了一种新的渲染类型叫tiled caching,这是介于sort-middle和sort-last fragment的某种方式.这个架构如图所示.思路是应用局部性和L2缓存.几何体在足够小的chunk中,所以能够输出并留在缓存中.另外,只要与tile重叠的几何体没完成像素着色,帧缓冲也会一直留在L2中.

英伟达tiledcaching.png

在GTX1080的图中,有四个光栅引擎,但是我们知道图形API(大多数情况下)必须遵守图元提交顺序.帧缓冲也经常通过生成的记录板模式分割成tiles,然后每个光栅引擎各自拥有一组tiles.当前的三角形被送到至少有一个tile与三角形重叠的光栅引擎中,每个光栅引擎独立解决tile的排序问题.这样能更好地负载均衡.GPU架构中也有许多FIFO队列,用于减少硬件单元的饥饿感(不理解).队列没在图中画出来.

播放控件拥有每个颜色分量12位大小,能提供BT.2020宽的色域支持.它也支持HDMI2.0b和HDCP2.2.对于视频处理,它支持SMPTE 2084,一个针对高动态范围视频的转换方法.Venkataraman描述了英伟达架构从Fermi及之后的架构如何拥有一个或多个拷贝引擎(copy engine).这些都是可以执行直接内存访问(direct memory access,DMA)变换的内存控件.DMA变换出现在CPU和GPU之间,这种变换会出现在它们中的任何一个上.开始处理单元(starting processing unit)能在转换过程中继续进行其他的计算.拷贝引擎能初始化CPU和GPU内存间的DMA数据转换,拷贝引擎能独立于GPU的其他部分执行.于是,GPU能在信息从CPU到GPU传送的过程中(反之亦然),渲染三角形和进行其他操作.

Pascal架构也能配置用于非图形的应用,例如训练神经网络或大规模数据分析,Tesla P100就是其中一种配置,与GTX1080的区别包括它使用高带宽内存2(high-bandwidth memory 2,HBM2)拥有4096位用于内存总线,提供总内存带宽720GB/s.另外,提供原生的16位浮点数支持,能够比32位浮点数提高两倍的性能和更快速的双精度处理.SM的配置也不同,寄存器文件设置(register file setup)也不同.

GTX1080Ti是更高端的配置.拥有3584个ALU,352位内存总线,总内存带宽484GB/s,88个ROP和224个纹理单元;而GTX1080只有2560,256位,320GB/s和160.GTX1080Ti拥有6个GPC,也就是说6个光栅引擎;而GTX1080只有4个.4个GPC是和GTX1080相同的,而多出来的2个GPC每个只包含4个TPC.GTX1080Ti使用了120亿个晶体管,而GTX1080只采用了72亿个.Pascal架构的灵活之处在于可以缩小规模.比如GTX1070是GTX1080减去了一个GPC,而GTX1050包含了两个GPC,每个GPC包含3个SM.

23.10.3 案例学习:AMD GCN Vega

AMD Graphics Core Next(GCN)架构备用与许多AMD产品和Xbox One以及PlayStation4.在这里我们描述GCN Vega架构的一般元素,是这些游戏机使用的架构的进化.

GCN架构的核心构件块是计算单元(compute unit,CU),如图所示.CU拥有4个SIMD单元,每个SIMD单元拥有16个SIMD通道,也就是16个一致的ALU.每个SIMD单元能够执行64线程的指令,这称为一个wavefront.每个时钟周期每个SIMD单元能执行一个单精度浮点指令.由于GCN架构每个SIMD单元执行一个wavefront的64线程,所以需要4个时钟周期才能将一个wavefront处理完毕(16通道并行,4个周期执行完).注意一个CU能同时执行来自不同核(kernel)的代码.由于每个SIMD单元拥有16通道并且每个时钟周期执行一个指令,每个CU的最大吞吐量是4个SIMD *16SIMD通道=64单精度浮点运算(每个时钟周期).CU也能在不需要高精度时,执行两倍的半精度浮点(16位浮点数)指令.比如机器学习和着色器计算.注意两个半精度浮点数值会被打包进一个单精度浮点寄存器.每个SIMD单元拥有64kB的寄存器文件大小,因为一个单精度浮点数需要4B,每个wavefront64线程,所以每个线程有个单精度浮点数寄存器.ALU拥有四个硬件管线阶段.

GCN的CU.png

每个CU拥有指令缓存(图里没画)被4个SIMD单元共享.相关指令被提交到SIMD单元的指令缓冲(instruction buffer,IB).每个IB拥有能处理10个wavefront的容量,便于使SIMD单元切入和切出来隐藏延迟.这意味着CU能处理40个wavefront,也就相当于个线程,所以图中的CU调度器能同时处理2560个线程,它的工作就是为CU中的不同单元分配任务.每个时钟周期,CU会考虑其中所有的wavefronts的指令问题,然后为每个执行端口(execution port)分配至多一条指令.CU的执行端口包括分支,标量/向量ALU,标量/向量内存,局部数据共享,全局数据共享或导出,以及特殊指令;也就是说,每个执行端口大致映射到CU的一个单元上.

标量单元是64位ALU,也在SIMD单元之间共享.它自身拥有专属的标量RF(寄存器文件)和标量数据缓存(没画).标量RF拥有800个32位寄存器为每个SIMD单元,也就是共有大小.执行与wavefront紧密耦合.因为需要4个时钟周期向SIMD单元完全发布一个指令,所以标量单元只能每4个时钟周期为特定SIMD单元服务.标量单元处理控制流,指针运算和其他可以在一个warp中线程间共享的运算.条件和非条件分支指令由标量单元发出,在分支和消息单元(branch and message unit)中执行.每个SIMD单元拥有一个48位程序计数器(program counter,PC),被所有通道共享.一个PC已经足够,因为所有通道执行相同的指令.对于采取的分支,PC会更新.被此单元发送的消息包括debug消息,特殊图形同步消息和CPU中断.

Vega10架构如图所示.最上面的部分包括一个图形命令处理器(graphics command processor),2个硬件调度器(HWS)和8个同步计算引擎(asynchronous compute engine,ACE).GPC的工作是向GPU的图形管线和计算引擎分发图形任务.HWS的缓冲以队列方式工作,并尽快分配给给ACE.ACE的工作是为计算引擎调度计算任务.Vega10有两个DMA引擎负责处理拷贝任务(图里没画).GPC,ACE和DMA引擎能以并行的方式工作并且为GPU提交工作,这样可以提高利用率,因为任务可以在不同的队列中交错进行.工作能从任何队列中分发出去,不需要等待其它工作完成,这意味着计算引擎能够同时执行多个独立任务.ACE通过缓存或内存同步.它们一起支持任务图(task graph),所以一个ACE的任务可以依靠另一个ACE的任务或者图形管线上的任务.推荐将更小的计算和拷贝任务与更大的图形任务交错进行.

Vega10架构.png

图中可以看到,一共有4个图形管线(graphics pipeline)和4个计算引擎(compute engine).每个计算引擎拥有16个CU,总共64个CU.图形管线有两个块一个叫图形引擎(graphics engine)一个叫绘制流分片器(draw-stream binning rasterizer,DSBR).图形引擎拥有一个几何装配器(geometry assembler),细分单元(tessellation unit)和顶点装配器(vertex assembler).另外,还支持一个新的图元着色器(primitive shader).图元着色器的设计想法是为了更灵活的几何处理和更快的图元剔除(primitive culling).DSBR结合了sort-middle和sort-last架构的优点,也是tiled缓冲的目的.屏幕空间的图像被分割为tile,经过几何处理后,每个图元都被分配给了与它们重叠的tile.在光栅化一个tile的过程中,所有数据(如tile buffer)需要保存在L2缓存中,这样可以提高性能表现.像素着色会自动延迟直到tile中的所有几何体都被处理完毕.于是z-prepass在后台完成,像素着色只进行一次.延迟着色能够开启或关闭,如透明几何体就需要关闭延迟着色.

为了处理深度缓冲,模板缓冲和颜色缓冲,GCN架构拥有一个块称为颜色和深度块(color and depth block,CDB).他们处理颜色,深度和模板缓冲的读和写,还有颜色混合.一个CDB能使用23.5节的一般方法压缩颜色缓冲.增量压缩技术(delta compression technique),每个tile储存一个未压缩的像素颜色,其余颜色值会编码为这个像素颜色相关的值.为了提高效率,tile的大小能够通过访问不同的模式而动态的选择.对于一个原来存储需要256B的tile,最大压缩率是8:1,也就是压缩完32B.在后续pass中压缩的颜色能用作纹理颜色,这种情况下纹理单元会解压这些压缩的tile,这种做法节省了带宽.

光栅化器每个时钟周期能够光栅化至多4个图元.而与图形管线和计算引擎相连的CDB,每个时钟周期能写入16像素.这也就是说,小于16像素的三角形会降低效率.光栅化器也处理粗糙深度测试(HiZ)和层级模板测试.用于HiZ的缓冲称为HTILE并且是可编程的,比如将遮挡信息反馈给GPU.
Vega的缓存层级如图所示.层级的最上层(图中的最右侧)的是寄存器,然后是L1和L2缓存.然后是高带宽内存2(high-bandwidth memory2,HBM2)位置也在图形卡上,最终系统内存位置在CPU的一方.Vega的一个新特性是高带宽缓存控制器(High-Bandwidth Cache Controller,HBCC)在上图的最下面.它能够允许显存能表现得像末级缓存(last-level cache,LLC).意思是如果要进行内存访问并且对应的内容不在显存中,比如HBM2,那么HBCC会自动从相关的系统缓存中取出并通过PCIe放入到显存中,结果是显存中很少被用到的页可能被交换出去.HBM2和系统内存之间共享的内存池被称为HBCC内存段(HBCC memory segment,HMS).所有图形块也是通过L2缓存访问内存的,这和之前的架构不同.Vega架构同时支持虚拟内存.

Vega缓存层级结构.png

注意到所有on-chip的块,如HBCC,XDMA(CrossFire DMA),PCI express,显示引擎(display engine)和多媒体引擎(multimedia engine),通过一个互连的Infinity Fabric(IF)通信.AMD的CPU也能够和IF相连,IF可以连接不同芯片模具上的块.IF也是相关的,这意味着所有块能够看到相同的内存内容视图.

Vega架构的基础时钟频率是1677MHz,也就是说,峰值计算能力为

其中,2是融合乘加经常被当作两个浮点数运算,然后我们除了来将MFLOPS转换成TFLOPS.这个架构灵活而且可拓展.期待看到更多的配置.

23.11 光线追踪架构

这章简单介绍光线追踪硬件.我们不会在这个话题上列出所有的最近的引用,但是会指路文章供读者自行了解.这个领域的研究是2002年开始的,那时的关注重点是遍历和交叉,着色是通过固定功能的单元计算的.这个工作后来被Woop等人研究,他提出了一个带有可编程着色器的架构.

最近几年光线追踪架构的议题获得了越来越多的经济上的兴趣.这能通过一些公司如Imagination Technologies,LG Electronics和Samsung都发布了他们各自的实时光线追踪的硬件架构中看到.然而在本书写作时只有Imagination Technologies发布了商业产品.

这些架构有一些共同的特点.首先他们使用了基于AABB的层级体积包围盒.第二,他们倾向于通过减少ray/box相交检测的精度来降低硬件复杂度.最后,他们使用可编程核心以支持可编程着色器,这在当今或多或少是必需的.例如Imagination Technologies拓展它们原来的芯片设计,比如增加了一个能利用着色器核心来着色的光线追踪单元.光线追踪单元包含一个光线相交处理器和一个相干性引擎(coherency engine),相干性引擎负责的是将具有相似属性的光线收集起来并且共同处理来实现局部性以达到更快的光线追踪.Imagination Technologies的架构也包含一个用于构件BVH的专用单元.

该领域的研究还在很多方面继续.包括在便捷遍历上降低精度,BVH的压缩表现,以及省电.毫无疑问很多的工作需要搞定.

深入阅读和资源

Akeley,Hanrahan,Hwu,Kirk的计算机图形架构的课程笔记是非常好的资源.Hwu和Kirk写的书也是CUDA编程和GPU编程很好的资源.每年的High-Performance Graphics和SIGGRAPH会议也提供了最新架构特性的学习资源.Giesen的trip down the graphics pipeline是非常棒的在线资源,对于想要研究GPU的更多细节的人.我们也推荐感兴趣的读者阅读Hennessy和Patterson的书了解内存系统.关于移动端渲染的信息在各个资源之间非常分散,GPU Pro 5这本书中的七篇关于移动端渲染技术的文章值得注意.

你可能感兴趣的:([RTR4]23章 图形硬件翻译)