OpenGL性能优化

来源:互联网 发布:秦岭地形数据 编辑:程序博客网 时间:2024/05/19 20:21

这篇文章比较长,希望大家能够看完。^_o

  • OPenGL State Machine
  • Typical D3D9 Hardware architecture
  • Less State Change
  • GL_TRIANGLE_STRIP instead of GL_TRIANGLES
  • Texture Loading
  • Texture Composite
  • Texture MipMap
  • Multi Pass vs. Single Pass
  • Texture Compression
  • Avoid Pixel Operations
  • Vertex Array, Display List
  • Vertex Buffer Object
  • Advanced Tech: VS and Ps.
  • Less Operations for Depth Test, Stencil Test, Alpha Test,
  • Fast Shadow
  • MISC: LOD, cull, SwpaBuffers, wglMakeCurrent etc

上一个讲座是关于OpenGL Driver体系结构,估计大家都有很多疑问,而且我自己又看了一遍,发现一些问题讲得不够清晰,而且没有交代讲驱动程序体系结构的目的。其主要目的是,当我们了解了驱动程序的体系结构,我们更好地写出一个OpenGL应用程序框架结构。

今天我将结合OpenGL状态机和一个典型的D3D9硬件体系结构探讨如何对OpenGL应用程序的性能进行优化。MSDN的OpenGL帮助也提到了关于性能优化方面的问题,但是这已经是多年以前的article,而随着图形加速硬件的发展,许多新的技术不断涌现,我们应该跟上时代发展的步伐。
我今天讲的内容应该是不全面的,希望大家踊跃指正和补充。

 

1、OpenGL状态机(State Machine)
OpenGL状态机的目前只有1.1版本,也是最经典的,大家可以参考下述链接:
ftp://ftp.sgi.com/opengl/doc/opengl1.1/state.pdf
ftp://ftp.sgi.com/opengl/doc/opengl1.1/state.ps

它们是内容相同而格式不同的状态机表达。整个文件中只有一张Postscript的图。这张图实际上就是SGI RealityEngine的硬件程序流程描述。

首先硬件接受应用程序输入的顶点信息,(Color, Normal, Texture, EdgeFlag,Vertex, ),经过世界坐标变换(glTranslate, glRotate, glScale),接着进行User Clip Plane,之后进入视图变幻和裁减(Projection Matrix),然后视口变换(ViewPort),经过Primitive Setup,光栅化处理(Flat或Phong)生成片断Fragment,下面的对每个依次作纹理贴图计算,纹理混合(Texture Blend),深度测试(Depth Test),模板测试(Stencil测试),透明测试(Alpha Test),透明混合(Apha Blend),然后写入颜色缓冲区,深度缓冲区,模板缓冲区。整个流程如下:

  1. Application
  2. Vertex Information (Material , Normal,  Textcoord,  EdgeFlag, Vertex Position)
  3. Lighting 
  4. World Matrix Transform
  5. User Clip Plane Clipping
  6. Projection Matrix Transform and Clip
  7. ViewPort
  8. Primitive Setup (  point, Line, Triangle)
  9. Rasterization( Flat or Phong ) == > Generate Fragment
  10. Fragment Texture Addressing ()   ==  Texture In Video memory
  11. Fragment Texture Blend ( blend Diffuse, Specular and Texture  of Fragment )
  12. Depth Test   == with Depth Buffer
  13. Stencil Test  == with Stencil Buffer
  14. Alpha Test   == with alpha channel of color buffer
  15. Alpha Blend  == with color buffer
  16. Fragment write to FrameBuffers

我们可以看到OpenGL每处理一个几何图元,需要经过大量的处理过程。大家应该对这个图的每个步骤地工作相当清晰。这里有几个概念需要说明。

第一个概念是Fragment,片断或者片元。每一个片断对应屏幕上的一个像素点,它是光栅化(Rasterization)引擎使用FLAT shading或 Phong Shading生成的。Rasterization引擎产生的片断包含一下信息:
屏幕坐标;
颜色信息,Diffuse和Specular;
深度信息和模板信息;
纹理坐标,u,v

第二个概念是纹理混合(Texture Blend),它是指纹理颜色和片断颜色(Diffuse和Specular)合成的方式。就是指glTexEnv的效果,根据不同的参数决定片断只保留Texel(纹理元)还是使用Texel(纹理元)和片断的颜色做混合。

第三个概念是透明融合Alpha Blend。如果一个片断经过深度测试,模板测试和透明测试,那么它将和缓冲区对应位置的像素作透明融合。

相信大家对OpenGL 状态机有了一定的了解,实际上这也是Direct3D8以前的图形流水线的主要参考模型(graphics processing pipeline)。

==================================================================
如果我们能够在流水线中减少一个操作,我们就能够获得性能的提高,当然前提是我们能够绘制正确的图像。


2、典型的D3D9硬件体系结构
上面的OpenGL状态机实际上就是SGI的Reality Engine和其他Direct3D7及其一下版本的图形硬件流水线结构。下面我向大家介绍D3D9的典型硬件体系结构(或者说Direct3D9的参考模型)。

Application 
IDirect3DDevice9::DrawIndexedPrimitive
D3D  Driver (Display Driver ) Send Commands to Hardware by AGP
following is hardware
Command Interpreter
“Fetch” Indexed Primitive data to Vertex Shader Cache  (access index buffer and Vertex Buffer)
“Put“ Cached data to Vertex Shader Input 
 Vertex Shader do Transform, Light and Vertex Blend
Vertex Shader Output Vertices in Screen coordinate Space,
Screen Pos, Diffuse,  Texture Coord
User Clip plane
Guard band clip
Primitive Setup (Point, Line, Triangle)
Rasterizaiton(flat or Phong) 
             |
            Pixel Shader (Texture addressing and texture blend)
             |
            Depth Test
             |
           Stencil Test
             |
             Alpha Test
             |
             Alpha Blend
             |
           Frame Buffers

 

我们可以看到D3D9的流水线和OpenGL 1.1的流水线有很大的不同。
OpenGL的顶点数据是通过调用OpenGL API一个个的送到流水线的几何变换处理单元,立即模式(immediate mode),而D3D9通过 Fetch和Put两步工作,从Vertex Buffer中读出送入Vertex Sahder的Input寄存器

OpenGL 1.1的光照计算和几何变换是通过传统的固定流水线(TnL: Transform and Lighting)完成的—fixed function graphics processing(FGP),而D3D9时通过Vertex Shader实现,它比FFGP更为复杂,可以完成更多的功能

OpenGL 1.1的Texture mapping和Texture Blend独立的两个步骤,而D3D9是通过Pixel Shader,PS是可编程的(Programmable Graphics Processing)。

D3D8/D3D9的Vertex Shader和Pixel Shader是两个图形体系结构巨大的进步,当然使得图形程序设计更为灵活,也更为困难和复杂。

对于D3D8/D3D9的硬件体系结构,我们的程序优化工作有多了两个内容,优化Vertex Shader和Pixel Shader。

今天我的重点放在传统图形流水线(TnL)的性能优化上。

 

3、基本优化方法

3.1 减少OpenGL的状态变化
如果我们应用程序不断地改变OpenGL的状态,那么驱动程序和AGP数据传输,图形硬件的负担会则增加很多。因为每当我们改变一个OpenGL状态,可能会涉及到硬件的多个寄存器的数据,那么驱动程序就必须将修改的硬件寄存器通过AGP总线发送到硬件,占用大量的CPU资源和AGP带宽和硬件命令解释器时间。

Advice1:尽可能将状态相近的图形绘制命令放在一起,减少OpenGL状态变化。
Advice2:使用状态集合,降低驱动程序的CPU处理时间, 


3.2 避免光照计算特别是高光计算(Specular)
Specular的计算是光照计算中最为耗时的运算之一。Diffuse计算相对比较普通,一般图形硬件都会对Diffuse运算进行优化。

3.3 图元类型优化
我们使用的大多数图元类型都是Triangle。如果我们每次都是用GL_TRIANGLES,我们将浪费大量的CPU时间和AGP带宽和图形硬件资源。原因如下:
(1)使用GL_TRIANGLES,我们每绘制一个三角形,我们就会发送三个定点的数据,如果我们使用G:_TRIANGLE_FAN或者GL_TRIANGLE_STRIP,那么我们可以平均每个三角形一个顶点。
(2)一般的硬件设计中都开辟一定的Cache区域,如果使用GL_TRIANGLE,我们将无法使用图形硬件的Cache,浪费大量的图形硬件TnL时间。
(3)使用GL_TRIANLGES将比GL_TRIANGLE_STRIP多耗费200%的硬件TnL时间。

根据测试,我三年前在Geoforce 3和 Geoforce Quadro 3上对OpenGL做的测试,GL_TRIANGLE_STRIP比GL_TRIANLGES 快100% ~ 200%。

建议:尽可能地使用GL_TRIANGLE_STRIP替代GL_TRIANGLES。
三角形Stripe的成熟软件:
http://www.cs.sunysb.edu/~stripe/

3.4 光照条件下使用glMaterial替代glColor
在光照条件下,如果程序使用glMaterial,那么驱动程序只加载Material属性一遍到硬件,使用glColor将使得驱动程序对每个定点加载颜色信息。将会占用更多的CPU时间和AGP带宽。

 

4、纹理优化
这方面的话题比较多,所以我把它作为一个独立的话题。

4.1 优化纹理加载
初学OpenGL一个常见的性能优化方面的问题是每次使用一个纹理的时候,都重新设置纹理参数并且调用glTexImage2D函数。事实上,OpenGL对纹理和Display List都有一个命名机制,glBindTexture,glDeleteTexture,glBindTexture。下面我们比较一下效果。

   方法一:每次使用纹理前调用glTexImage2D,并重新设置纹理参数。那么驱动程序将不断地调用IDirectDraw7::CreateSurface并且将数据从用户内存区拷贝到驱动程序系统内存区,然后再从系统内存区域复制到video memory。
方法二:使用glTexEnv和glTexImage2D设置当前的纹理参数和纹理内容,,然后调用glBindTexture,例如5号纹理;如果需要使用该纹理,再次调用glBindTexture函数,glBindTexture会把5号纹理设置为当前的纹理,并且参数上次设置的参数,你可以根据需要决定是否修改参数。方法二的主要优点在于应用程序仅仅调用glTexImage2D,从而节省大量的CPU和AGP时间,因为从CPU往video memory复制是最耗时,overhead is very high。

Advice:
当应用程序需要多个Textures,在调用wglMakeCurrent成功后,调用glGenTextures产生命名纹理,并且使用glBindTexture分别进行纹理绑定;
在wglDeleteContext之前使用glDeleteTexture将所有的纹理从驱动程序内存和video memory释放。
每次需要使用纹理时,再次调用glBindTexture

进一步阅读:
OpenGL Spec & OpenGL manual:
http://www.opengl.org/developers/documentation/specs.html
Glut examples:
http://www.opengl.org/developers/documentation/glut.html

4.2 尽量使用MipMap纹理
一般图形硬件都支持Mipmap,如果应用程序使用Mipmap,那么图形硬件会根据当前的片断对应的纹理LOD计算Texel,这样能够节省大量的纹理元video memory寻址时间,而且图形硬件对纹理元做Cache,mipmap中尺寸较小的纹理(Level比较大的)能够节约大量的计算时间。如果应用程序仅仅提供Level 0的最大的纹理,那么图形硬件每次都将使用这个纹理作纹理元计算,不但会浪费大量的计算资源,而且消耗很多的图形芯片带宽。

Advice:1. 不要使用特别大的纹理. > 256 X 256
            2.使用MipMap。
   Tips: gluBuild*DMipmaps 能够将非2^n的纹理转化带有MipMaps的标准OpenGL纹理。不过gluBuild*DMipMaps不支持压缩纹理的自动Mipmap。

进一步阅读:glu Manual:
ftp://ftp.sgi.com/opengl/doc/opengl1.2/glu1.3.pdf

 4.3 纹理组合
在游戏或者可视化应用中,我们总是会遇到许多非常小的纹理,一种比较好的办法是我们把这些纹理组合成一个比较大的纹理,例如256X256,这样驱动程序在加载纹理的video memory的地址时候,驱动程序仅仅需要加载一次家可以了。这种方法在多个造型软件中也经常见到,例如人体造型软件Pose,它将一个人的头发,脸,眼睛,等组合为一个纹理。

 Advice: 将多个小纹理组合为一个大纹理,然后修改对应三角形定点的纹理坐标,或者使用glMatrixMode(GL_TEXTURE)对定点的纹理坐标作几何变换。

4.4 使用MultiTexture替代Multi-Pass

OpenGL 1.2.1 extension: GL_ARB_multitexture

 Direct3D7(OpenGL .2.1)及更高版本支持的显示卡都支持MutliTexture功能,我们可以充分利用这个特性做多纹理贴图替代Multi-Pass。

例如我们希望会绘制一个可乐瓶子,而且这个可乐瓶子需要两层标签,利用Multi-Pass我们可以分三次绘制,

如果使用MutliTexture(OpenGL.2.1扩展),我们只需要Single Pass完成这项工作:
    
Mutlitexture的方法将比第一种方法节约流水线的4个运算步骤,Depth Test,Alpha Test,Alpha Blend,和 write to frame Buffers。

Advice:检查OpenGL extension支持,尽可能使用MultiTexture。
进一步阅读:
OpenGL specs:
http://www.opengl.org/developers/documentation/specs.html

OpenGL extension Registry:
http://oss.sgi.com/projects/ogl-sample/registry

4.5 使用压缩纹理

OpenGL支持的压缩纹理包括:
GL_COMPRESSED_RGB_S3TC_DXT1_EXT
GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
压缩纹理比非压缩纹理具有更快的运算速度和更小的存储空间要求,而且很容易使用图形硬件纹理Cache。因此能够显著地提高应用程序性能,特别应用程序的纹理数据量巨大。
 
 缺点:要求纹理的色彩空间规律性极强,否则会造成严重的颜色失真。

建议:检查下面的三个OpenGL Extension,尽可能地使用压缩纹理。
GL_ARB_texture_compression
GL_EXT_texture_compression_s3tc
GL_S3_s3tc

Advice:检查OpenGL extension支持,尽可能使用MultiTexture。
进一步阅读:
OpenGL specs:
http://www.opengl.org/developers/documentation/specs.html

OpenGL extension Registry:
http://oss.sgi.com/projects/ogl-sample/registry

我们可以使用DirectX SDK的工具产生压缩纹理dxtex ,或者从nivdia获得工具和Tutorial:
http://developer.nvidia.com/object/nv_texture_tools.html


4.6 合理的纹理尺寸
图形硬件系统一般使用4X4,8X8,最高到64X64的纹理Cache策略,如果你的纹理比较简单,在满足可识感官的要求下,尽可能地使用较小的纹理尺寸。

 

5、Vertex Array

相对于glBegin, glEnd以及Display List, Vertex Array对于驱动程序而言具有最高的内存复制效率,因为驱动程序仅仅需要一次内存数据移动,glBend, glEnd和Display List,则需要三次数据移动。因此尽可能多地使用glDrawArrays和glArrayElement的方式。

 针对Vertex Array,OpenGL 有如下的Extensions:
GL_EXT_vertex_array
GL_ATI_element_array
GL_EXT_draw_range_elements
GL_EXT_compiled_vertex_array
GL_SUN_mesh_array
GL_ATI_vertex_attrib_array_object

其中前面三个是经常使用OpenGL extension,例如QuakeIII, CS, Half Life等。

进一步阅读:
    OpenGL specs:
    http://www.opengl.org/developers/documentation/specs.html

    OpenGL extension Registry:
http://oss.sgi.com/projects/ogl-sample/registry

 

6、Buffer Object

事实上,我上面所讲到的内容都是传统的OpenGL图元定义,本质上都是通过glBegin和glEnd定义,都属于立即模式绘制的一种方法。而Direct3D都是通过Vertex Buffer和Index Buffer实现图元及其组成顶点的属性定义。而Vertex Buffer和Index Buffer都在保存在video memory中,这样应用程序不需要每次都把地顶点数据通过AGP发送给硬件,从而加快了处理速度。为了在弥补这个缺陷,Nvidia和ATI推出了下面的Extension:
GL_ARB_vertex_buffer_object

同时这个extesion也为OpenGL 的Vertex Proram(即 D3D9的Vertex Shader)服务,关于这个Extension相关内容比较多,我就不展开这个讲述了。这里告诉大家,它是比所有立即模式图元定义方法都快的一个OpenGL extension。原因如下:
(1)它只需要一次复制到OpenGL申请的video memory,随后驱动程序仅仅每次向图形硬件报告它的物理地址;
(2)而对于立即模式的图元定义,驱动程序每次都需要从内存中把数据复制到AGP non-local video memory,然后通过AGP总线发送到图形硬件处理器。

请参考
    OpenGL extension Registry:
http://oss.sgi.com/projects/ogl-sample/registry


7、Advanced Tech :Vertex Program 和 Fragment Program( D3D Vertex Shader和 Pixel Shader)

这篇内容太长长了,我把它放入到D3D9的专题中。

8、Less Operation for Depth Test,Stencil Test和 Alpha Test

事实上,Depth Test,Stencil Test,Alpha Test能够影响到OpenGL 像素填充的30%。也就是说,如果你对他们进行优化,能够获得30%的性能。

 我曾经对quake III的性能优化做过测试,得到下面结果;
   Disable  Depth Test        2%   gain
   Disable  Alpha Test    6%   gain
   Disable  Alpha blend   2%   gain
   Disable  Depth Clear always15%  gain

事实上,Quake III本身能够进一步优化,大家都知道Quake III是最经典的一个游戏引擎,它绘制图形采用BSP的结构,使用多纹理贴图和Alpha Blend获得非常好的光照效果,绘制图元的顺序是从最远处的物体到最近处的物体,由远及近的次序,那么如果QuakeIII把它改作由近及远的次序,Quake III中也少数的三角形遮挡关系,采用由近及远的次序绘制图形的时候,Depth Test将扔掉5%~10%甚至更多的片断(像素),那么流水线后面的操作将不会被执行,从而获得性能的提高,我相信这将会带来5%~15%的性能提高。

那么对于室外场景的漫游,我建议大家采用由近及远的次序。也许会带来极大的性能提高。

9、Fast Shadow
很多人都在做类似的工作,我想以后抛砖引玉,作为一个单独的专题介绍。

10、MISC: LOD, cull, SwpaBuffers, wglMakeCurrent etc

最后一部分的小标题比较古怪,是个大杂烩。

10.1 LOD

这个许多人都知道了,我就不多说了,就是较少几何数据量(Vertex) 和纹理运算量(Texture LOD: mipmap)。

10.2 CULL Face
CULL Face就是北面删除,如果不绘制背面的三角形,理论上可以获得接近50%的性能提高,前提是假设TnL或者Vertex Shader足够的快。
 glEnable(GL_CULL_FACE) ;
 glCullFace(GL_BACK);

在我对QuakeIII的测试中,尽管QuakeIII是基于BSP树的,理论上QuakeIII不应该有背面的物体,我仍然获得了3%~5%的性能提高(不同的CPU和总线速度)。

10.3 SwapBuffers
事实上,全屏幕的OpenGL程序是调用IDirectDrawSurface7::Flip或IDirect3DDevice8::Present,那么每进行FLIP操作将比窗口的OpenGL程序少作1024X768X4 bytes的显示内存数据移动,将设分辨率为1024X768X32bits,根据不同的应用,能够获得相当可观的性能提高,大家可以自己算算。

10.4 wglMakeCurrent
wglMakeCurrent是一个非常耗时的操作,2001年我对Geoforce3 Ti500进行了测试,在最好的情况下,Geoforce3 Ti500能够做5000次/秒。当时的CPU速度好像是800M还是1.4Ghz。我不太清楚了。同时wglMakeCurrent也许会带来副作用,一些图像可能发生丢失。其中一个典型的测试,indy3D就是采用这种方法,我在跟踪这种程序的时候,觉得Sense8(开发vtk的那个公司)程序设计能力太糟糕了。

Advice:一定要避免调用wglMakeCurrent。

写了3.5小时,手指头已经很痛了,休息一下。

 

11、避免像素操作(Pixel)
在OpenGL的实现中,都是使用纯软件的方法实现从系统内存到video memory 的复制,那么这些将中断整个图形流水线的执行,等待硬件空闲后使用CPU完成,它们将大大降低程序的执行效率。
glBitmap,
glDrawPixels
glReadPixels
glCopyPixels

解决办法:使用纹理替代像素操作,例如建设你希望在屏幕输出一行字,例如”Qauke III Arena”, 那首先产生一个纹理,它包含所有的字母和数字,我这里无法贴BMP图像,我画一个存储结构:代表RGBA各式的2D 纹理,这是Quake III的字母纹理顺序。
  A B C D E F G H I J KLM
  N O P Q R S T U V WX Y Z
a b c d ….
1 2 3 4 5 6 9 8 9 0
使用两个三角形产生一个字母或者数字。

补充:4.5 使用压缩纹理
我们可以使用DirectX SDK的工具产生压缩纹理dxtex ,或者从nivdia获得工具和Tutorial:
http://developer.nvidia.com/object/nv_texture_tools.html

原创粉丝点击