您的位置:首页 > 运维架构

/LGC图形渲染/OpenGL 性能优化

2010-12-28 15:26 495 查看
OpenGL 性能优化

作者:
Yang Jian (jyang@cad.zju.edu.cn)

日期:

2009-05-04

本文从硬件体系结构、状态机、光照、纹理、顶点数组、LOD、Cull等方面分析了如何优化 OpenGL 程序的性能。

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),然后写入颜色缓冲区,深度缓冲区,模板缓冲区。

整个流程如下:

Application

Vertex Information (Material , Normal, Textcoord, EdgeFlag,
Vertex Position)

Lighting

World Matrix Transform

User Clip Plane Clipping

Projection Matrix Transform and Clip

ViewPort

Primitive Setup ( point, Line, Triangle)

Rasterization( Flat or Phong ) == > Generate Fragment

Fragment Texture Addressing () == Texture In Video memory

Fragment Texture Blend ( blend Diffuse, Specular and Texture of Fragment )

Depth Test == with Depth Buffer

Stencil Test == with Stencil Buffer

Alpha Test == with alpha channel of color buffer

Alpha Blend == with color buffer

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)。

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

典型的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)的性能优化上。

基本优化方法

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

建议1:尽可能将状态相近的图形绘制命令放在一起,减少OpenGL状态变化。

建议2:使用状态集合,降低驱动程序的CPU处理时间。

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

图元类型优化
我们使用的大多数图元类型都是Triangle。如果我们每次都是用GL_TRIANGLES,我们将浪费大量的CPU时间和AGP带宽和图形硬件资源。原因如下:

使用GL_TRIANGLES,我们每绘制一个三角形,我们就会发送三个定点的数据,如果我们使用G:_TRIANGLE_FAN或者GL_TRIANGLE_STRIP,那么我们可以平均每个三角形一个顶点。

一般的硬件设计中都开辟一定的Cache区域,如果使用GL_TRIANGLE,我们将无法使用图形硬件的Cache,浪费大量的图形硬件TnL时间。

使用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/

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

纹理优化

优化纹理加载
初学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。

建议

:

当应用程序需要多个Textures,在调用wglMakeCurrent成功后,调用glGenTextures产生命名纹理,并且使用glBindTexture分别进行纹理绑定;

在wglDeleteContext之前使用glDeleteTexture将所有的纹理从驱动程序内存和video memory释放。

每次需要使用纹理时,再次调用glBindTexture

进一步阅读:

OpenGL Spec & OpenGL manual

Glut examples

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

建议



不要使用特别大的纹理. > 256x256

使用MipMap。

Tips

: gluBuild*DMipmaps 能够将非2^n的纹理转化带有MipMaps的标准OpenGL纹理。不过gluBuild*DMipMaps不支持压缩纹理的自动Mipmap。

进一步阅读: glu Manual

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

建议

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

使用MultiTexture替代Multi-Pass
OpenGL 1.2.1 extension: GL_ARB_multitexture

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

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

//绘制瓶子的本色,例如绿色,

glMaterial (…) ;

glDisable(GL_BLEND);

glDepthFunc(GL_LEQUAL);

glBegin(GL_TRIANGLE_STRIP);

//Texture

glNormal();

glVertex(); ….

glEnd();

//绘制里面的标签

glDpethFunc(GL_EQUAL);

glEnable(GL_BLEND);

glBindTexture(0,);

glBegin();

glTextCoord();

glVertex();

glEnd();

//绘制第二层标签

glDpethFunc(GL_EQUAL);

glEnable(GL_BLEND);

glBindTexture(1,);

glBegin();

glTextCoord();

glVertex();

glEnd();

如果使用MutliTexture(OpenGL.2.1扩展),我们只需要Single Pass完成这项工
作:

glMaterial();

glDepthFunc(GL_LEQUAL);

glDisable(GL_BLEND);

glActiveTExtureARB(GL_TEXTURE0_ARB);

glTexEnv(,,GL_MODULATE);

glBindTExture(0);

glActiveTExtureARB(GL_TEXTURE1_ARB);

glTexEnv(,,GL_MODULATE);

glBindTExture(1);

glBegin(GL_TRIANGLE_STRIP);

glNormal();

glMultiTexCoord2fARB (GL_TEXTURE0_ARB,u0,  v0 );

glMultiTexCoord2fARB (GL_TEXTURE1_ARB, u1,  v1);

glVertex();

glEnd();

Mutlitexture的方法将比第一种方法节约流水线的4个运算步骤,Depth Test,Alpha Test,Alpha Blend,和 write to frame Buffers。

建议

:检查OpenGL extension支持,尽可能使用MultiTexture。

进一步阅读

:

OpenGL specs:http://www.opengl.org/developers/documentation/specs.html

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

使用压缩纹理
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

建议

:检查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

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

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

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。原因
如下:

它只需要一次复制到OpenGL申请的video memory,随后驱动程序仅仅每次向图形硬件报告它的物理地址;

而对于立即模式的图元定义,驱动程序每次都需要从内存中把数据复制到AGP non-local video memory,然后通过AGP总线发送到图形硬件处理器。

请参考 OpenGL extension Registry:

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

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

使用 Shader 对渲染管线进行编程,控制渲染过程。

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%的性能提高。

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

Fast Shadow

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

MISC: LOD, cull, SwpaBuffers, wglMakeCurrent

LOD
LOD,很经典的方法,使用较少几何数据量(Vertex)和纹理运算量(Texture
LOD: mipmap)。

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

glEnable(GL_CULL_FACE) ;

glCullFace(GL_BACK);

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

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

wglMakeCurrent
wglMakeCurrent是一个非常耗时的操作,2001年我对Geoforce3
Ti500进行了测试,在最好的情况下,Geoforce3
Ti500能够做5000次/秒。当时的CPU速度好像是800M还是1.4Ghz。我不太清楚了。同时wglMakeCurrent也许会带来副作用,

一些图像可能发生丢失。其中一个典型的测试,indy3D就是采用这种方法,我在跟踪这种程序的时候,觉得Sense8(开发vtk的那个公司)程序设计
能力太糟糕了。

建议

:一定要避免调用wglMakeCurrent。

避免像素操作(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

使用两个三角形产生一个字母或者数字。

Reference

OpenGL Performance Optimization
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: