OpenGL 性能優化


2010-07-07 16:46
作者: Yang Jian ([email protected]
日期: 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 

http://www.linuxgraphics.cn/graphics/opengl_performance_opt.html

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/pizi0475/archive/2010/04/01/5441020.aspx

發佈了2 篇原創文章 · 獲贊 2 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章