在先前的章節中,咱們已經討論OpenGL基本的一些渲染技術。這些基本的技巧足夠渲染簡單的圖像,然而在渲染精細的畫面逼真的畫面的時候(很是多的頂點和紋理),若是使用以前的方式渲染(當即模式)速度就很慢了,考慮到性能的緣由(特別是實時渲染)咱們須要以更快的方式完成畫面的渲染。精細的畫面有大量的數據須要CPU和GPU去處理,並且把數據從應用程序發送到顯卡有帶寬和顯存的瓶頸。 數組
到目前爲止,咱們的圖元都是在一對glBegin/glEnd之間調用glVertex來組裝的。這種方式是很是靈活(能夠動態地修改數據)並且易於使用的。但考慮性能的時候,以這種方式傳送圖元到顯卡性能是最差。考慮下面的步驟:畫一個受光照的帶紋理的三角形 多線程
glBegin(GL_TRIANGLES); glNormal3f(x, y, z); glTexCoord2f(s, t); glVertex3f(x, y, z); glNormal3f(x, y, z); glTexCoord2f(s, t); glVertex3f(x, y, z); glNormal3f(x, y, z); glTexCoord2f(s, t); glVertex3f(x, y, z); glEnd();
就一個簡單的三角形就有11個函數調用。函數調用須要壓棧,出棧等操做。若是一個複雜的圖形有1萬個或者更多三角形組成,那這些函數調就會消耗許多CPU時間。能夠想象顯卡空閒着等待CPU收集這些數據而後發送這批圖元。這種批量提交圖元的方式稱爲當即模式。OpenGL提供了更好的方式了處理這些數據。 異步
OpenGL是圖形硬件的一個軟件接口。你能夠想象成OpenGL的命令被轉換爲一些指定的硬件的命令和驅動的操做,而後傳到顯卡上當即執行。實際上,這些命令不是當即傳到圖形顯卡上執行的,而是有一個本地的緩衝區,當這個緩衝區的命令累積到一個臨界值的時候,就會被清空(flush)到硬件上去。 函數
之因此這麼作的主要緣由是與圖形硬件圖形須要花費較長的CPU時間。這個時間對於咱們來講多是很是短的,但對於一個一秒鐘運行幾十億個週期的CPU來講是至關緩慢的。打個比方有一艘從美國的英國的輪船,咱們不會在一我的登船以後,就啓程到英國,而後返回再接另外一我的。而是會盡量地等到輪船滿載了以後纔出發。對於計算機來講,經過系統總線一次性發送大量的數據,比分屢次發送少許的數據要快。 性能
發送緩衝數據到圖形硬件是一個異步操做,這樣CPU就能夠空閒出來去處理其餘的事情而不用等到這批渲染命令傳送完成。這樣圖形硬件和CPU的並行是很是高效的。 ui
有三個事件會出發緩衝區的刷新(flush)。第一個是緩衝區滿了的時候,會把這批命令發給圖形硬件。咱們沒有權限去訪問這個緩衝區和調整這個緩衝區的大小。第二個是執行交換緩衝區命令(swapbuffer)的時候。緩衝區交換是告訴驅動程序,表面已經完成了一個特定的場景,全部的命令應該被執行去渲染場景。若是是使用單緩衝區的,則須要調用手工調用刷新: spa
void glFlush(void); .net
有些OpenGL的命令並不會進行緩衝,例如glReadPixels和glDrawPixels。這些函數要訪問幀緩衝區進行直接的讀寫數據。在讀寫幀緩衝區是命令隊列必須被清空。還有一種是強制刷新命令緩衝區,而後等待渲染任務的完成。函數調用以下: 線程
void glFinish(void); 翻譯
這個函數較少用到,通常用於多線程或者多渲染環境。
OpenGL的命令要從高級的命令語言被翻譯成低級的機器級的命令才能被機器所理解。若是命令很是多,那麼翻譯也須要消耗必定的時間。因此爲了提升性能,有些萬年不變的OpenGL命令,能夠翻譯後就保存起來,而不是每次都去翻譯。例如畫一個圓環(gltDrawTorus)的命令和頂點數據老是相同的,圓環是有一系列的三角形組成的,其中須要用到一些三角函數的命令。 咱們只是經過改變模型視圖矩陣,來變換圓環的位置而已。
一個較好的解決方案是把這些命令和數據先進行預處理,而後保存起來,之後調用的時候能夠快速地拷貝到命令緩衝區而後執行。在OpenGl中這些預編譯的命令列表稱爲顯示列表。OpenGL使用glNewList/glEndList來包括顯示列表。
glNewList(<unsigned integer name>, GL_COMPILE);
…
//some OpenGL code
glEndList();
glNewList的第一個參數是顯示列表的名稱,用unsigned int類型來表示。第二個參數GL_COMPILE則告訴OpenGL編譯這些列表,但不當即執行他們。你能夠指定GL_COMPILE_AND_EXCUTE這樣就編譯後當即執行一次(在渲染場景的函數可能會這樣作)。通常在初始化的時候,咱們會預先編譯(GL_COMPILE)好這些顯示列表,而後在渲染場景時調用。
顯示列表的名稱能夠是任意的無符號整數。當名稱命名重複時,後一個顯示列表會覆蓋掉前一個。爲防止這種狀況發生,咱們可使用OpenGL提供的函數
GLuint glGenLists(GLsizei range);
這個函數會生成指定個數的顯示列表的名稱。顯示列表的名稱是按數字順序保留的,返回值是第一個名稱。例如:glGenLists(3); 若是返回值是5,那就表明5,6,7這三個名稱是保留的,供給你使用的。下次再調用glGenLists就是從8開始了。執行顯示列表命令的函數:
void glCallList(GLuint list);
或者執行一組顯示列表命令:
void glCallLists(GLsizei n, GLenum type, const GLvoid *lists);
n表明顯示列表的個數,type是顯示列表數組lists的類型,lists是指向顯示列表名稱的指針。
在這裏修改以前的第八章的sphereworld的例子,使用顯示列表來渲染球體,圓環和地面。顯示列表很是容易使用,只須要對這個例子作一個簡單的修改便可。首先在初始化(SetupRC)產生一些顯示列表的名稱(unsigned int),而後把繪製球體,圓環和地面的代碼進行預編譯。代碼以下:
static unsigned int spherelist_1; static unsigned int spherelist_2; static unsigned int torulist; static unsigned int groundlist; void SetupRC() { .... ... //給顯示列表命名 spherelist_1 = glGenLists(4); spherelist_2 = spherelist_1 + 1; torulist = spherelist_1 + 2; groundlist = spherelist_1 + 3; //預編譯這些顯示列表 glNewList(spherelist_1, GL_COMPILE); glutSolidSphere(0.3, 20, 20); glEndList(); glNewList(spherelist_1, GL_COMPILE); glutSolidSphere(0.3, 20, 20); glEndList(); glNewList(torulist, GL_COMPILE); gltDrawTorus(0.25f, 0.15f, 25, 25); glEndList(); glNewList(groundlist, GL_COMPILE); RenderGround(); glEndList(); }
在添加一個右鍵菜單,來切換兩種模式。在渲染時進行判斷。執行顯示列表的函數是 void glCallList(GLuint list)
if (iMode == LISTMODE) { glCallList(groundlist); } else { RenderGround(); } .... if (iMode == LISTMODE) glCallList(torulist); else gltDrawTorus(0.25f, 0.15f, 25, 25); ...記錄渲染一次所花費處理器時間,並顯示在窗口標題上。僅僅經過一一次渲染的時間就可以對比出不一樣了。
static void RenderScene() { clock_t t = clock(); .... .... glutSwapBuffers(); t = clock() - t; char buffer[128] = {0,}; wsprintf(buffer, "render clicks is %d", t); glutSetWindowTitle(buffer); }
看一下結果:
正常模式下,花的處理器時間是5左右:
顯示列表模式下是1左右:
就這麼一個簡單的場景就有了5倍的差距。可見使用顯示列表能極大的提升性能。但顯示列表有一個缺點,就是數據得是靜態的,靈活性差。
下面段落摘自(http://blog.csdn.net/bingcaihuang/article/details/5663110)
注意:並非全部的OpenGL函數均可以在顯示列表中存儲且經過顯示列表執行。通常來講,用於傳遞參數或返回數值的函數語句不能存入顯示列表,由於這張表有可能在參數的做用域以外被調用;若是在定義顯示列表時調用了這樣的函數,則它們將按瞬時方式執行而且不保存在顯示列表中,有時在調用執行顯示列表函數時會產生錯誤。如下列出的是不能存入顯示列表的OpenGL函數:
glDeleteLists() glIsEnable() glFeedbackBuffer() glIsList() glFinish() glPixelStore() glGenLists() glRenderMode() glGet*() glSelectBuffer()