# 第十二課:OpenGL擴展
# 第十二課:OpenGL擴展
## 擴展
GPU的性能隨著更新換代一直在提高,支持渲染更多的三角形和像素點。然而,原始性能不是我們唯一關心的。NVIDIA, AMD和Intel也通過增加功能來改善他們的顯卡。來看一些例子。
### ARB\_fragment\_program
回溯到2002年,GPU都沒有頂點著色器或片斷著色器:所有的一切都硬編碼在芯片中。這被稱為固定功能流水線(Fixed-Function Pipeline (FFP))。同樣地,當時最新的OpenGL 1.3中也沒有接口可以創建、操作和使用所謂的“著色器”,因為它根本不存在。接著NVIDIA決定用實際代碼描述渲染過程,來取代數以百計的標記和狀態量。這就是ARB\_fragment\_program的由來。當時還沒有GLSL,但你可以寫這樣的程序:
```
<pre class="calibre16">```
<span class="token">!</span><span class="token">!</span>ARBfp1<span class="token1">.</span><span class="token6">0</span> MOV result<span class="token1">.</span>color<span class="token1">,</span> fragment<span class="token1">.</span>color<span class="token1">;</span> 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這樣的函數。這個擴展的偉大之處在于,當你寫了一些不正確的代碼,例如:
```
<pre class="calibre16">```
<span class="token3">glEnable</span><span class="token1">(</span>GL_TEXTURE<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Incorrect ! You probably meant GL_TEXTURE_2D !</span>
```
```
你能得到錯誤消息和錯誤的精確位置。總結:
- 即便在現在的OpenGL 3.3中,擴展仍舊十分有用。
- 請使用ARB\_debug\_output !下文有鏈接。

### 獲取擴展 – 復雜的方式
『手動』查找一個擴展的方法是使用以下代碼片斷 ([轉自OpenGL.org](http://xn--OpenGL-9k3qp87f.org) wiki):
```
<pre class="calibre16">```
int NumberOfExtensions<span class="token1">;</span>
<span class="token3">glGetIntegerv</span><span class="token1">(</span>GL_NUM_EXTENSIONS<span class="token1">,</span> <span class="token">&</span>NumberOfExtensions<span class="token1">)</span><span class="token1">;</span>
<span class="token4">for</span><span class="token1">(</span>i<span class="token">=</span><span class="token6">0</span><span class="token1">;</span> i<span class="token"><</span>NumberOfExtensions<span class="token1">;</span> i<span class="token">++</span><span class="token1">)</span> <span class="token1">{</span>
const GLubyte <span class="token">*</span>ccc<span class="token">=</span><span class="token3">glGetStringi</span><span class="token1">(</span>GL_EXTENSIONS<span class="token1">,</span> i<span class="token1">)</span><span class="token1">;</span>
<span class="token4">if</span> <span class="token1">(</span> <span class="token3">strcmp</span><span class="token1">(</span>ccc<span class="token1">,</span> <span class="token1">(</span>const GLubyte <span class="token">*</span><span class="token1">)</span><span class="token5">"GL_ARB_debug_output"</span><span class="token1">)</span> <span class="token">==</span> <span class="token6">0</span> <span class="token1">)</span><span class="token1">{</span>
<span class="token2">// The extension is supported by our hardware and driver</span>
<span class="token2">// Try to get the "glDebugMessageCallbackARB" function :</span>
glDebugMessageCallbackARB <span class="token">=</span> <span class="token1">(</span>PFNGLDEBUGMESSAGECALLBACKARBPROC<span class="token1">)</span> <span class="token3">wglGetProcAddress</span><span class="token1">(</span><span class="token5">"glDebugMessageCallbackARB"</span><span class="token1">)</span><span class="token1">;</span>
<span class="token1">}</span>
<span class="token1">}</span>
```
```
### 獲得所有的擴展 – 簡單的方式
上面的方式太復雜。若用GLEW, GLee, gl3w這些庫,就簡單多了。例如,有了GLEW,你只需要在創建窗口后調用glewInit(),不少方便的變量就創建好了:
```
<pre class="calibre16">```
<span class="token4">if</span> <span class="token1">(</span>GLEW_ARB_debug_output<span class="token1">)</span><span class="token1">{</span> <span class="token2">// Ta-Dah ! }</span>
```
```
(小心: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),它能幫你處理所有復雜的事。
因此你可以這樣寫代碼:
```
<pre class="calibre16">```
<span class="token4">if</span> <span class="token1">(</span> GLEW_NV_path_rendering <span class="token1">)</span><span class="token1">{</span>
<span class="token3">glPathStringNV</span><span class="token1">(</span> <span class="token1">.</span><span class="token1">.</span><span class="token1">.</span> <span class="token1">)</span><span class="token1">;</span> <span class="token2">// Draw the shape. Easy !</span>
<span class="token1">}</span><span class="token4">else</span><span class="token1">{</span>
<span class="token2">// Else what ? You still have to draw the lines</span>
<span class="token2">// on older NVIDIA hardware, on AMD and on INTEL !</span>
<span class="token2">// So you have to implement it yourself anyway !</span>
<span class="token1">}</span>
```
```
### 均衡考量
當使用擴展的益處(如渲染質量、性能),超過維護兩種不同方法(如上面的代碼,一種靠你自己實現,一種使用擴展)的代價時,通常就選擇用擴展。
例如,在時空幻境(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核心配置加載