# 第十二課:OpenGL擴展
## 擴展
GPU的性能隨著更新換代一直在提高,支持渲染更多的三角形和像素點。然而,原始性能不是我們唯一關心的。NVIDIA, AMD和Intel也通過增加功能來改善他們的顯卡。來看一些例子。
### ARB_fragment_program
回溯到2002年,GPU都沒有頂點著色器或片斷著色器:所有的一切都硬編碼在芯片中。這被稱為固定功能流水線(Fixed-Function Pipeline (FFP))。同樣地,當時最新的OpenGL 1.3中也沒有接口可以創建、操作和使用所謂的“著色器”,因為它根本不存在。接著NVIDIA決定用實際代碼描述渲染過程,來取代數以百計的標記和狀態量。這就是ARB_fragment_program的由來。當時還沒有GLSL,但你可以寫這樣的程序:
~~~
!!ARBfp1.0 MOV result.color, fragment.color; END
~~~
但若要顯式地令OpenGL使用這些代碼,你需要一些還不在OpenGL里的特殊函數。在進行解釋前,再舉個例子。
### ARB_debug_output
好,你說『ARB_fragment_program太老了,所以我不需要擴展這東西』?其實有不少新的擴展非常方便。其中一個便是ARB_debug_output,它提供了一個不存在于OpenGL 3.3中的,但你可以/應該用到的功能。它定義了像GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB或GL_DEBUG_SEVERITY_MEDIUM_ARB之類的字符串,和DebugMessageCallbackARB這樣的函數。這個擴展的偉大之處在于,當你寫了一些不正確的代碼,例如:
~~~
glEnable(GL_TEXTURE); // Incorrect ! You probably meant GL_TEXTURE_2D !
~~~
你能得到錯誤消息和錯誤的精確位置。總結:
- 即便在現在的OpenGL 3.3中,擴展仍舊十分有用。
- 請使用ARB_debug_output !下文有鏈接。

### 獲取擴展 – 復雜的方式
『手動』查找一個擴展的方法是使用以下代碼片斷 (轉自OpenGL.org wiki):
~~~
int NumberOfExtensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &NumberOfExtensions);
for(i=0; i<NumberOfExtensions; i++) {
const GLubyte *ccc=glGetStringi(GL_EXTENSIONS, i);
if ( strcmp(ccc, (const GLubyte *)"GL_ARB_debug_output") == 0 ){
// The extension is supported by our hardware and driver
// Try to get the "glDebugMessageCallbackARB" function :
glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC) wglGetProcAddress("glDebugMessageCallbackARB");
}
}
~~~
### 獲得所有的擴展 – 簡單的方式
上面的方式太復雜。若用GLEW, GLee, gl3w這些庫,就簡單多了。例如,有了GLEW,你只需要在創建窗口后調用glewInit(),不少方便的變量就創建好了:
~~~
if (GLEW_ARB_debug_output){ // Ta-Dah ! }
~~~
(小心:debug_output是特殊的,因為你需要在上下文創建的時候啟用它。在GLFW中,這通過glfwOpenWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1)完成。)
### ARB vs EXT vs …
擴展的名字暗示了它的適用范圍:
GL*:所有平臺;GLX*:只有Linux和Mac下可使用(X11);WGL_:只有Windows下可使用。
EXT:通用的擴展。ARB:已經被OpenGL架構評審委員會的所有成員接受(EXT擴展沒多久后就經常被提升為ARB)的擴展。NV/AMD/INTEL:顧名思義 =)
## 設計與擴展
### 問題
比方說,你的OpenGL 3.3應用程序需要渲染一些大型線條。你能夠寫一個復雜的頂點著色器來完成,或者簡單地用[GL_NV_path_rendering](http://www.opengl.org/registry/specs/NV/path_rendering.txt),它能幫你處理所有復雜的事。
因此你可以這樣寫代碼:
~~~
if ( GLEW_NV_path_rendering ){
glPathStringNV( ... ); // Draw the shape. Easy !
}else{
// Else what ? You still have to draw the lines
// on older NVIDIA hardware, on AMD and on INTEL !
// So you have to implement it yourself anyway !
}
~~~
### 均衡考量
當使用擴展的益處(如渲染質量、性能),超過維護兩種不同方法(如上面的代碼,一種靠你自己實現,一種使用擴展)的代價時,通常就選擇用擴展。
例如,在時空幻境(Braid, 一個時空穿越的二維游戲)中,當你干擾時間時,就會有各種各樣的圖像變形效果,而這種效果在舊硬件上沒法渲染。
而在OpenGL 3.3及更高版本中,包含了99%的你可能會用到的工具。一些擴展很有用,比如GL_AMD_pinned_memory,雖然它通常沒法像幾年前使用GL_ARB_framebuffer_object(用于紋理渲染)那樣讓你的游戲看起來變好10倍。
如果你不得不兼容老硬件,那么就不能用OpenGL 3+,你需要用OpenGL 2+來代替。你將不再能使用各種神奇的擴展了,你需自行處理那些問題。
更多的細節可以參考例子[OpenGL 2.1版本的第14課 – 紋理渲染](http://code.google.com/p/opengl-tutorial-org/source/browse/tutorial14_render_to_texture/tutorial14.cpp?name=2.1%20branch#152),第152行,需手動檢查GL_ARB_framebuffer_object是否存在。常見問題可見FAQ。
## 結論Conclusion
OpenGL擴展提供了一個很好的方式來增強OpenGL的功能,它依賴于你用戶的GPU。
雖然現在擴展屬于高級用法(因為大部分功能在核心中已經有了),了解擴展如何運作和怎么用它提高軟件性能(付出更高的維護代價)還是很重要的。
## 深度閱讀
- debug_output tutorial by Aks 因為有GLEW,你可以跳過第一步。
- [The OpenGL extension registry](http://www.opengl.org/registry/) 所有擴展的規格說明。圣經。
- [GLEW](http://glew.sourceforge.net/) OpenGL標準擴展庫
- [gl3w](https://github.com/skaslev/gl3w) 簡單的OpenGL 3/4核心配置加載