zoukankan      html  css  js  c++  java
  • OpenGL 性能优化

    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.pdfftp://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: http://www.opengl.org/developers/documentation/specs.html Glut examples: http://www.opengl.org/developers/documentation/glut.html尽量使用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: ftp://ftp.sgi.com/opengl/doc/opengl1.2/glu1.3.pdf纹理组合在游戏或者可视化应用中,我们总是会遇到 许多非常小的纹理,一种比较好的办法是我们把这些纹理组合成一个比较大的纹理,例 如256x256,这样驱动程序在加载纹理的video memory的地址时候,驱动程序仅仅需要加载一次家可以了。这种方法在多个造型软件中也经常见到,例如人体造型软件Pose,它将一个人的头发,脸,眼 睛,等组合为一个纹理。建议: 将多个小纹理组合为一个大纹理,然后修改对应三角形定点的纹理坐标,或者使用glMatrixMode(GL_TEXTURE)对定点的纹理坐标作几何变 换。使用MultiTexture替代Multi-PassOpenGL 1.2.1 extension: GL_ARB_multitextureDirect3D7(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.htmlOpenGL 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.htmlOpenGL 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_arrayGL_ATI_element_arrayGL_EXT_draw_range_elementsGL_EXT_compiled_vertex_arrayGL_SUN_mesh_arrayGL_ATI_vertex_attrib_array_object 其中前面三个是经常使用 OpenGL extension,例如QuakeIII, CS, Half Life等。进一步阅读:OpenGL specs:http://www.opengl.org/developers/documentation/specs.htmlOpenGL extension Registry:http://oss.sgi.com/projects/ogl-sample/registryBuffer 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/registryAdvanced 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, wglMakeCurrentLODLOD,很经典的方法,使用较少几何数据量(Vertex)和纹理运算量(Texture LOD: mipmap)。CULL FaceCULL 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,根据不同的应用,能够获得相当可观的性能提高,大家可以自己算算。 wglMakeCurrentwglMakeCurrent是一个非常耗时的操作,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 Za b c d ….1 2 3 4 5 6 9 8 9 0使用两个三角形产生一个字母或者数字。转 http://blog.csdn.net/chenchao868/archive/2009/11/04/4768716.aspx
  • 相关阅读:
    运行级别和root密码找回
    磁盘情况查询
    linux磁盘分区、挂载
    三、本地负载均衡器与openfeign声明式客户端
    二、Nacos服务注册与发现
    一、微服务架构演变过程
    shell工具
    Netty概述
    垃圾回收概述及算法
    执行引擎
  • 原文地址:https://www.cnblogs.com/adodo1/p/4327156.html
Copyright © 2011-2022 走看看