OpenGL Loading
什麼是 OpenGL loading?
OpenGL是一份API規範,並不是一個庫。記住這點非常重要!它意味著每一個API背後的具體實現都依賴於你的GPU硬體、作業系統以及顯示卡驅動。
OpenGL規範定義了很多不同的函式,並且規範會定期進行更新,你的顯示卡驅動可能不會支援全部的函式。你的顯示卡和顯示卡驅動的能力決定了你能使用的API規範的子集。這也是不把所有的OpenGL函式靜態定義在一個頭檔案中供你使用的原因。而且,將所有函式靜態連結成一個庫也是不可能的,因為你的應用執行的目標機器上會有各種各樣不同的OpenGL實現。
在 Windows機器上,OpenGL是以dll的方式實現的。在64位Windows作業系統上,64位的dll庫位於 C:\Windows\system32\opengl32.dll,這個dll是顯示卡驅動的一部分,是隨著顯示卡驅動一起釋出的。
再次強調,大部分的 OpenGL函式是不能被定義在某個標準的標頭檔案中,然後靜態連結進你的應用程式的。這也是為什麼OpenGL函式不能直接被呼叫,而需要顯式的宣告和載入。
什麼是 GLEW?
GLEW(OpenGL Extension Wrangler)是一個跨平臺的用於OpenGL函式宣告和載入的庫,它同樣具備在執行時檢查目標機器是否支援某個OpenGL profile(一個profile就是一份指定配置支援特定的OpenGL函式集的保證)。
GLEW非常的簡單,尤其是針對剛開始接觸OpenGL的新手。它把所有的髒活累活都替你做了,你可以在你的系統上自由的呼叫支援的所有有效的OpenGL函式。
在 StackOverflow上,所有關於OpenGL loading的問題下,都有人建議你使用OpenGL Loading Library,停止自己定製OpenGL loading去使用GLEW。甚至OpenGL官方的wiki都強烈建議你使用OpenGL loading library。
但是像 GLEW這樣的OpenGL loading library有它們自己的缺點,下面就以GLEW為例,來詳細講下這些缺點
為什麼你最好不使用GLEW?
你可以用以下兩種方式使用 GLEW庫:
- Dynamically linking動態連結:一個OpenGL應用或者引擎釋出後,我們希望使用者可以很方便地在他們的機器上進行構建,GLEW使用動態連結的話,我們要麼為它要單獨建立一個編譯工程,要麼我們自己要針對不同的目標機器,提前編譯好各種不同機器上的dll或so庫,這樣可以說很麻煩
- Shipping the source連同程式碼一起釋出:將GLEW庫同OpenGL應用靜態編譯在一起,這種方式可以完美避開以上方式的缺點,唯一的缺點是GLEW庫的程式碼量很大,它的存在增加了程式碼的複雜度,同時增加了程式碼的編譯時間。GLEW如此龐大的原因是它維護了所有的OpenGL函式,而我們的應用可能只需要其中很小的一個子集,比如我們只需要modern opengl,也就是其core profile。
如何定製自己的OpenGL Loader?
github上有很多自定義的OpenGL loader,有很多短小精幹,非常好用,比如 https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a ,在這個的基礎上,可以增加一些跨平臺的巨集定義。
#if defined(__linux__) #include <dlfcn.h> #define GLDECL// Empty define #define PAPAYA_GL_LIST_WIN32// Empty define #endif // __linux__ #if defined(_WIN32) #include <windows.h> #define GLDECL WINAPI #define GL_ARRAY_BUFFER0x8892 #define GL_ARRAY_BUFFER_BINDING 0x8894 ... typedef char GLchar; typedef ptrdiff_t GLintptr; typedef ptrdiff_t GLsizeiptr; #define PAPAYA_GL_LIST_WIN32 \ /* ret, name, params */ \ GLE(void, BlendEquation, GLenum mode) \ GLE(void, ActiveTexture, GLenum texture) \ /* end */ #endif // _WIN32
上面程式碼我們首先包含不同平臺的動態庫呼叫的標頭檔案。然後定義了 windows平臺下的函式呼叫規範的巨集GLDECL。我們還需要包含GL/gl.h的標頭檔案,但是這個檔案在Linux和Windows上是不同的。Windows SDK包含的gl.h版本是非常老的(OpenGL 1.1),所以它並沒有包含所有的OpenGL typedef,常量以及函式宣告,但是Linux平臺上是最新的。所以在Windows系統上需要手動新增這些定義。你可以直接包含這個檔案 https://www.khronos.org/registry/OpenGL/api/GL/glext.h ,它包含這些常量和函式的定義。
最後就是定義你需要的函數了,程式碼如下:
#define BINKGL_LIST \ /*ret, name, params */ \ GLE(void,LinkProgram,GLuint program) \ GLE(void,GetProgramiv,GLuint program, GLenum pname, GLint *params) \ GLE(GLuint,CreateShader,GLenum type) \ GLE(void,ShaderSource,GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length) \ GLE(void,CompileShader,GLuint shader) \ GLE(void,GetShaderiv,GLuint shader, GLenum pname, GLint *params) \ GLE(void,GetShaderInfoLog,GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) \ GLE(void,DeleteShader,GLuint shader) \ GLE(GLuint,CreateProgram,void) \ GLE(void,AttachShader,GLuint program, GLuint shader) \ GLE(void,DetachShader,GLuint program, GLuint shader) \ GLE(void,UseProgram,GLuint program) \ GLE(void,DeleteProgram,GLuint program) \ GLE(void,GenVertexArrays,GLsizei n, GLuint *arrays) \ GLE(void,BindVertexArray,GLuint array) \ GLE(void,BufferData,GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage) \ GLE(void,GenBuffers,GLsizei n, GLuint *buffers) \ GLE(void,BindBuffer,GLenum target, GLuint buffer) \ GLE(void,DeleteBuffers,GLsizei n, const GLuint *buffers) \ GLE(void,TexParameteri,GLenum target, GLenum pname, GLint param) \ GLE(void,ActiveTexture,GLenum texture) \ GLE(void,BindAttribLocation,GLuint program, GLuint index, const GLchar *name) \ GLE(GLint,GetUniformLocation,GLuint program, const GLchar *name) \ GLE(void,Uniform4f,GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) \ GLE(void,Uniform4fv,GLint location, GLsizei count, const GLfloat *value) \ GLE(void,DeleteVertexArrays,GLsizei n, const GLuint *arrays) \ GLE(void,EnableVertexAttribArray, GLuint index) \ GLE(void,VertexAttribPointer,GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer) \ GLE(void,Uniform1i,GLint location, GLint v0) \ /* end */ #define GLE(ret, name, ...) typedef ret GLDECL name##proc(__VA_ARGS__); static name##proc * gl##name; BINKGL_LIST #undef GLE static void *GLhandle; bool gl_lite_init() { #if defined(__linux__) void* libGL = dlopen("libGL.so", RTLD_LAZY); if (!libGL) { printf("ERROR: libGL.so couldn't be loaded\n"); return false; } #define GLE(ret, name, ...)\ gl##name = (name##proc *) dlsym(libGL, "gl" #name);\ if (!gl##name) {\ printf("Function gl" #name " couldn't be loaded from libGL.so\n"); \ return false;\ } BINKGL_LIST #undef GLE #elif defined(_WIN32) HINSTANCE dll = LoadLibraryA("opengl32.dll"); typedef PROC WINAPI wglGetProcAddressproc(LPCSTR lpszProc); if (!dll) { OutputDebugStringA("opengl32.dll not found.\n"); return false; } wglGetProcAddressproc* wglGetProcAddress = (wglGetProcAddressproc*)GetProcAddress(dll, "wglGetProcAddress"); #define GLE(ret, name, ...)\ gl##name = (name##proc *)wglGetProcAddress("gl" #name);\ if (!gl##name) {\ OutputDebugStringA("Function gl" #name " couldn't be loaded from opengl32.dll\n"); \ return false;\ } BINKGL_LIST #undef GLE #else #error "GL loading for this platform is not implemented yet." #endif return true; }