分类: LINUX
2011-09-05 09:41:28
一、GPU历史简要回顾
在这里只从90年代早期开始叙述。
第0代:1993年GPU hardware和graphics pipeline开始真正成形,这时SGI发布了RealityEngine板卡,这个时期graphics pipeline前半部分的stage都需要CPU的实现,且一个时钟周期只能产出一个pixel。Quake和Doom对整个GPU的发展起到了卓越的贡献。
第1代:3dfx Voodoo在1996年时开发了真正意义上的3D游戏卡,基于PCI总线传输数据,有一百万个集体管,4MB的64位DRAM,核心频率50MHz。关键一点,vertex transformation仍然需要在CPU上完成,但是texture mapping, z-buffering,rasterization由3D卡完成。
第2代:1999年,第一次完整实现了整个pipeline的GPU发布,NVIDIA GeForce256和ATI Radeon 7500,NVIDIA创立了GPU的概念。这个时期使用AGP替代PCI传输数据,并且增加了新的图形特性,比如multi-texturing, bump maps, light map, hardware geometry transform以及lighting。这个时期实现的pipeline就是熟知的fixed function。
以下的图显示了上面三个时期GPU功能的变迁:
第3代:GPU开始引入programmable pipeline,2001年,NVIDIA发布了GeForce 3实现了DX8 Pixel shading pipeline。
第4代:2002年,完整支持vertex shader和pixel shader的GPU面世(GeForce FX,Radeon 9700),DX9特性的支持,全浮点运算和高级的texture process也开始在GPU中出现。这个阶段,GPU的发展超越了摩尔定律。2004年发布的GeForce 6和Radeon X800是两块典型的DX9显卡,数据传输使用PCI-E,支持64位的double和multiple rendering buffers。稍后详细叙述。
第5代:以2006年GeForce 8800 为代表,特点——GPU具有更大量的并行处理核心。GeForce 8800全面兼容DX10特性,引入了统一可编程shader(unified programmable shader)的概念,也即SM(Streaming Multiprocessor)。由于采用了SM的设计,传统的pipeline模型只是软件概念上的抽象意义了。稍后详细叙述。
第6代:GPU采用NVIDIA的Fermi架构,GPU更加朝向GPGPU方向发展。具体后面详细叙述。
二、DirectX 9的GPU架构
2.1、GPU架构解析
GeForce 6 series Architecture
general graphics pipeline
如上图,DirectX 9的GPU经典架构,现从graphics pipeline的角度,游览架构的内部细节:
1、CPU处理过的Command、texture、vertex data通过系统内存或本地板载内存传入GPU。command stream由CPU产生,用于初始化和修改渲染状态,发送渲染指令,以及引用相关的texture和vertex Data。由于CPU执行的相对有序性和GPU的乱序执行,为了同步CPU和GPU,通过一个Command Buffer来传输CPU产生的command stream,command buffer中一般只有对资源的指针引用。
2、vertex processor为每个传入的vertex执行vertex shader的程序片段,对每个顶点进行transformations, skinning, 和一些其他的逐顶点的操作(GeForce 6系列还能在顶点shader程序中读取texture,所以vertex engine连接有texture cache)。另外,vertex engine还有一个vertex cache,用于储存处理过的顶点,可以提高顶点的处理效率(为了能利用vertex cache,一般使用indexed primitives来提交顶点数据给GPU。因为传入的顶点有重复,如果下一个要处理的顶点刚好在vertex cache中,那么直接跳过此顶点的处理,减少了顶点的重复操作,为了更好的利用cache的特性,Nvidia提供了一个工具,NVTriStrip 可以增加cache的利用率)。
3、经过vertex processor处理过的顶点组织成图元,Cull/Clip/Setup部件对每个图元进行操作,移除不可见的图元,剪切和视域相交的图元,生成边面以便于下一步的rasterization的操作。
4、rasterization部件生成每个图元所覆盖的pixel,然后通过Z-Cull部件丢弃掉那些被遮挡的pixel。所有有效的pixel都将送交fragment processor进行下一步的处理。
5、fragment processor处理texture和fragment-processing有关的操作,其中texture data缓存(cache)在芯片上,以便于减少带宽和改善性能。GeForce 6的texture和fragment-processing单元可以同时操作4像素组成的方形块,方便直接计算texture的LOD(mipmap的层级)。在pixel shader pipeline的最后是fog unit,用于fog blending。
6、经过fragment processor处理的pixels被送往z-compare和blend单元,进行深度测试,stencil测试,alpha blending一系列操作后,最终的像素颜色写入目标表面。
2.2 性能优化策略
2.2.1 定位性能瓶颈
优化的关键是定位性能瓶颈出现在渲染流水线的哪个阶段,一般按照如下图所示的步骤来定位性能瓶颈:
1) ROP:大多数的ROP瓶颈和frame-buffer的带宽有关,是否出现效率瓶颈通过改变bit depth来观察,如果从32-bit更改为16-bit显著提升性能,那么可以明确这个阶段出现了效能瓶颈。
2)Texture Bandwidth:texture bandwidth是纹理从内存里拾取时的一个性能指标,尽管现今GPU用texture cache来减少额外的内存请求,但是带宽依然会成为性能的瓶颈。相较于更改纹理格式来检测是否对性能有影响,更推荐用一系列的mipmap的偏移值(bias)来更改纹理尺寸,以观测性能的变化。
3)Fragmeng Shading:fragment shading和frame-buffer统称为填充率(fill rate),因为他们都和窗口分辨率相关。在确定并优化了ROP瓶颈之后,可以通过改变窗口分辨率来观察性能的变化,另外还可以调整fragment program的长度(减少总共的指令数)来检测性能的变化。
4)Vertex Processing:改变vertex program的指令数(程序长度),观察性能的改变
5)Vertex and Indice Transfer:Vertex和Index数据的存储位置的区别,存储在系统内存里,AGP和PCI-E的传输效率是影响性能的指标,存储在GPU的local-memory里,则显存时钟频率为参考性能值。定位瓶颈是否出现在这个阶段,一般通过改变vertex和Index格式和大小。、
6)如果以上皆排除,那么性能瓶颈只可能出现在CPU阶段。
2.2.2 优化策略
1)优化CPU的性能束缚
cpu上复杂的物理和AI计算,以及混乱的资源管理,batch的数量都有可能对整体性能产生很大影响。复杂的物理和AI计算的优化需要针对具体的程序进行考量,这里只涉及资源和batch方面的优化策略。
减少资源Locking
任何涉及对GPU上resouce的同步操作,都将造成CPU的空转和GPU的停等。cpu需要等待GPU流水线空闲和资源返交,而GPU则需要等待资源重新填充。locking的操作发生在,一、对前一个frame渲染的surface的读取,二、GPU准备读取的texture或者vertex buffer需要更新和写入。一般,应该尽量避免在渲染时locking操作。
最大化batch的大小
也即最小化batch的数量,每一个rendering call都会产生一个batch,任何一个rendering call都有昂贵的CPU代价,所以应该尽量在每一个rendering call时提交尽可能多的triangle数量。有如下一些措施可以实现这个目的。
>如果使用triangle strips,使用退化的triangles缝合分散的strips(这些strips使用相同的material)
>texture atlas(针对DirectX 9,DirectX 10以后提供了更好的解决方案texture array)
>使用shader分支来整合只是因为某几个条件不同的rendering call
>使用vertex shader的const memory来存储matrices表:比如要渲染很多仅仅只是位置不同的物体时,除了使用instance技术之外,还可
以把每个物体的变换矩阵存储在const memory,然后在顶点数据中添加每个物体存储在const memory对应的matrice的索引。
>尽可能的把分割batch的变量存储在vertex或texture中。
2)减少Vertex和Indice传输代价
通过如下方法达到这个目的:
>为顶点结构中的元素选择合适的数据类型,原则,尽量选择位数更少的数据类型。
>如果顶点数据结构中某个元素可以通过其他元素计算得到,那么不需要出现在结构中。
>优先使用16-bit的indice
>存储顶点数据尽量以相对有序的方式,以便于更好的利用cache。
3)优化vertex processing
尽管vertex processing很少成为效率瓶颈,但依然可以按照如下措施优化:
>使用NVTriStrip组织模型数据,可以更好的利用post-T&L vertex cache。
>使用LOD,包括,模型的静态LOD,以及material的shader program的LOD,比如,远处的模型,skining和light可以尽量的简单,
multi-pass也可以尽量减少。
>将每个模型只计算一次的计算转移到CPU上,比如对于每个模型来说,方向光对每个顶点都是一样的,所以方向光变换到模型空间这个
操作应该放在CPU上执行。
>对于vertex shader里用到的变量应该统一到同一个坐标空间里,即不应该再有额外的变换。
4)加速fragment shading
fragment shading一旦控制不当,最有可能产生瓶颈,相应优化措施:
>优先渲染depth,在multi-pass条件下,depth-only pass可以关闭color write,可能的情况还可以只保留depth相关的设置,禁用fragment shading。
>尽可能复杂的计算在CPU上计算出结果存储在texture里
>尽可能将一些不影响渲染质量的计算从fragmeng shader转移到vertex shader
>不要过分追求运算精度
>使用LOD,策略和vertex shading一样
5)减少texture带宽
拾取texture时,减少传输带宽,有如下优化措施:
>减少size
>使用compress的texture
>texture format尽量cheap
>总是使用mipmap
6)优化frame-buffer带宽
优化frame-buffer带宽,主要针对ROP的一些优化措施:
>先进行depth的渲染(比如多pass时)
>只在实在需要的时候进行alpha blending,alpha blending需要对framebuffer进行读和写的操作。
>当不需要depht write时,关闭它(比如在多pass环境下,只需要一个pass打开depth write就行了)。
>避免使用额外的color clear操作,当每帧的pixel都能保证得到更新时,那么就不需要为每帧提供color-buffer clear操作,但是depth和
stencil缓冲区必须得实时更新,因为某些early-z操作需要用到它们。
>使用front-to-back的方式渲染。
>优化skybox的渲染,skybox有两种渲染方式,一、最后渲染,进行depth test但是disable depth write。二、最先渲染,disable depth test和write。如果天空大部分被遮挡,使用第一种渲染技术,否则使用第二种。
>只在需要的时候使用浮点frame-buffer。
>尽可能使用16-bit的depth-buffer,在小规模的室内场景考虑使用16-bit,使用RTT的时候,优先考虑16-bit。
>尽可能使用16-bit color,比如使用RTT时也应该优先纳入考虑范围
三、DirectX 10的GPU架构
3.1 相较于DirectX 9的性能上的优势和引入的新特性
3.1.1 DirectX 9的一些缺陷
1、由于设计上DirectX 9相对臃肿,所以相对具有很高的状态切换成本,不仅对渲染性能有影响,而且限制了渲染的视觉丰富性。每个状态切换都会引起很明显的性能的下降,所以想要通过切换这些状态(textures, shaders, shader parameters, vertex formats, blending modes)达到丰富渲染外观的目的是不切实际的。
2、过多的参数可用于调节GPU的功能(capabilities)。
3、频繁的CPU和GPU的同步需求
4、在指令类型上限制,不支持integer类型指令
5、资源上的限制
3.1.2 DirectX 10新特性和对DirectX 9的改进
1、DirectX 10在重新架构时,一个主要设计目标是减少CPU overhead(CPU的依赖成本),DX10从三个方面解决这个问题:一、完全重新设计core API中对性能影响最重要的部分,以减少draw call和状态切换的代价。二、引入新的特性以减少对CPU的依赖。三、允许一个command可以完成更多的处理。
> 重新设计了更轻便更干净的Runtime,更抽象,和硬件层的结合更紧密,完全移除了Fixed-function commands。其中一个关键的改变是Validation(验证),validation是在draw call之前需要执行的操作,用于确定程序的command和data的有效性。如下图所示:
DX10只在resouce创建的时候进行validation的操作,相较于DX9每次使用resouce时都要Validation,这里节省的成本是巨大的。
> 更少的CPU交互。DX10引入了三个新特性来减少和CPU的交互——texture arrays,predicated draw和stream output。
texture arrays是texture atlas更完美的替代方案,texture altlas所有缺点都被修正了。
predicated draw,是指复杂的物体首先用一个简单AABB盒替代渲染,通过occlusion query技术来决定物体是否需要渲染,和DX9相比
这个处理完全在GPU上完成,并不需要和CPU进行交互。
sream output,允许vertex或者geometry shader输出结果到显存里,因为有了stream output,产生的这些结果可以不需要CPU的干
预,直接在GPU里进行多次迭代处理。
> 在一个command中做更多的处理。DX10引入了两个新构造——state objects和constant buffers,允许普通的操作集合在一起以batch模
式执行。
state objects,是一种新的,更高层次的状态管理模式。是将DX9下的涉及到整个流水线 细粒度的大量的状态(states)合并为
五个state objects:InputLayout(vertex buffer layout),Sampler,Rasterizer,DepthStencil,Blend。极大的减少了状态切换的成本
constant buffers,是将许多的constants存储在一个buffer里(上限为4096),可以通过一个函数调用更新其中的constant。
2、Shader Model 4.0,有如下几个重要更新。
> 一个新的可编程stage,geometry shader,允许进行逐图元的操作。
>统一着色架构,使用统一指令和资源管理,以适用于vertex,geometry,pixel shaders每个阶段。和DX9分离式的vertex shader和
pixel shader相比,不需要特别对每个stage了解其限制,因为在统一着色架构里,一个着色单元可以动态的执行vertex,geometry,
pixel任何一个程序片段。
>shader resouce的全面性的改善,如下表所示:
3、两种新的HDR格式,DX10提供了新的HDR格式可达到与F16相同表示范围但是减半存储空间——R11G11B10,用于优化浮点的render target,第二种是RGBE(5-bit指数E,RGB每个部分表示9-bit的尾数),用于浮点纹理。
3.2 新的Graphics Pipeline
如下图
1、Input Assembler(IA):通过附着在vertex buffers上的上限为8的输入流(input stream)采集顶点数据,并将数据转换为标准格式(如float32)。每一个input stream和一个特定的vertex structure(每一个structure最多可含有16个元素,每个元素是具有1-4个数据元的同型元组)关联。IA原生支持instance机制,可以将从一个对象复制出n份副本。
2、Vertex Shader:VS和其他可编程部件(stage)共享同一功能集。
3、Geometry Shader:一、以单个图元(point、line、triangle,etc)作为输入,产出0个或多个图元。二、能过实时添加新的顶点,可以使用displacement mapping。三、可以读取一个图元毗邻的顶点信息。
4、Stream Output:拷贝有序的顶点信息的子集到至多4个output buffers里,理想的SO的输出数据格式应该和IA的输入数据格式完全一样,但是实际上,SO写入的是32-bit的数据,而IA一般读取8-bit或16-bit。数据格式的转换和数据的打包,可以在GS中实现。
5、Set-up and Rasterization Stage:接受顶点数据输入,输出一系列经过或不经过透视校正和插值处理的fragment。这个阶段,一般有这些处理——clipping,culling,perspective divide,viewport transform,primitive set-up,scissoring,depth offset,early-z
6、Pixel Shader:接收单个fragment,产出单个由1-8个属性值(对应8个render target,一般还有depth属性)的fragment。
7、Output Merger:进行传统的stencil和depth测试,8个render target公用一个depht/stencil buffer
(注:更详细的说明,参看下一篇译文(The Direct3D 10 system——by David Blythe))