OpenGL Performance Optimization

來源:互聯網
上載者:User
OpenGL Performance Optimization
OpenGL Performance Optimization

Yang Jian
jyang@cad.zju.edu.cn

這篇文章比較長,希望大家能夠看完。^_^
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),然後寫入顏色緩衝區,深度緩衝區,樣板緩衝區。整個流程如下:

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

==================================================================
如果我們能夠在流水線中減少一個操作,我們就能夠獲得效能的提高,當然前提是我們能夠繪製正確的映像。

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我們可以分三次繪製,

 //繪製瓶子的本色,例如綠色,
 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。

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.3SwapBuffers
事實上,全螢幕的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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.