glew工作原理
OpenGL版本演进
在介绍glew工作原理之前需要了解一下OpenGL版本相关的信息。
自OpenGL 3.0就开始酝酿deprecation model(废止模型),标记了未来版本会从Core去掉的特性(此前每次OpenGL协议更新基本都在增加特性,但这次开始决定去掉包袱,从核心特性中删减一些内容)。但3.0并没有实际去删减函数,而是利用设置glXCreateContextAttribsARB函数GLX_CONTEXT_FLAGS_ARB属性的GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB标志(在glfw中是配置glfwWindowHint函数的GLFW_OPENGL_FORWARD_COMPAT标志),将OpenGL context分为两种: forward compatible context(向前兼容上下文)与full context(完整上下文),顾名思义,前者是去掉过时特性的OpenGL上下文;而后者还是保持与之前版本的兼容性,具有完整的特性,根据specs中可以看出这一点。
GLXContext glXCreateContextAttribsARB(
Display *dpy, GLXFBConfig config,
GLXContext share_context, Bool direct,
const int *attrib_list);
/*With the advent of new versions of OpenGL which deprecate features
and/or break backward compatibility with older versions, there is a
need and desire to indicate at context creation which interface will
be used. These extensions add a new context creation routine with
attributes specifying the GL version and context properties
requested for the context, and additionally add an attribute
specifying the GL profile requested for a context of OpenGL 3.2 or
later. It also allows making an OpenGL 3.0 or later context current
without providing a default framebuffer.*/
OpenGL 3.1版发生大变动,API做了巨大调整,去掉了此前3.0版中标记的函数,对之前版本的兼容性自然不是很好。因此在OpenGL 3.1中,增加了GL_ARB_compatibility拓展,借此即可使context保留所有函数(包括过时或删减的函数在内)的入口点和各种OpenGL的枚举项等(也就是通过这种方式仍可加载所有的OpenGL方法)。从这里来说,OpenGL 3.1是一个分水岭。
OpenGL3.2 版本后,OpenGL每版的协议分为“Core Profile”与“Compatibility Profile”两种,即本版的核心函数集,以及初代至今的全部函数集。而OpenGL的实现则必须(must)能创建出对应的“Core context”,而或许(may,可见后者的实现不是必备的)能创建“Compatibility context”。加入此机制的原因也很简单,即取代GL_ARB_compatibility拓展(从specs也能看出,此拓展只出现在OpenGL 3.1)。要设置此项,可调整glXCreateContextAttribsARB的GLX_CONTEXT_PROFILE_MASK_ARB属性(在glfw中是借助glfwWindowHint函数调整GLFW_OPENGL_PROFILE属性)。
众所周知,要利用OpenGL就需要其头文件、函数库以及GPU硬件驱动。驱动通常由硬件厂家与操作系统开发商配合开发。而OpenGL的函数库实现则是五花八门:有硬件公司自己做的闭源库(通常是随闭源驱动一块安装),操作系统自带的OpenGL库(如Windows),以及开源库(如mesa3d)等。
OpenGL头文件的变迁也比较复杂,初期版本的OpenGL把函数定义与宏等内容都放在<GL/gl.h>头文件中。后来支持的平台增多,就不能保证该头文件的实现都与OpenGL最新协议保持同步、一致。
比如,在Windows平台上,自带OpenGL实现并没有按OpenGL协议更新,而是仅维持在OpenGL 1.1版本上(意图很明显)。对于跨平台这件事儿上就出了问题。Windows的<GL/gl.h>停在了OpenGL 1.1版本上,这个<GL/gl.h>的统一平台策略也就失效了。为了应对这个问题,OpenGL ARB给出了这样的解决方案:提供了<GL/glext.h>作为OpenGL 1.2及其以上版本的compatibility profile与拓展接口,配合<GL/gl.h>即完整的最新OpenGL compatibility profile与扩展定义。又提供了<GL/glcorearb.h>,即完整的最新OpenGL core profile与ARB扩展定义。
OpenGL头文件的变迁也比较复杂,初期版本的OpenGL把函数定义与宏等内容都放在<GL/gl.h>头文件中。后来支持的平台增多,就不能保证该头文件的实现都与OpenGL最新协议保持同步、一致。比如,在Windows平台上,自带OpenGL实现并没有按OpenGL协议更新,而是仅维持在OpenGL 1.1版本上(意图很明显)。对于跨平台这件事儿上就出了问题。Windows的<GL/gl.h>停在了OpenGL 1.1版本上,这个<GL/gl.h>的统一平台策略也就失效了。为了应对这个问题,OpenGL ARB给出了这样的解决方案:提供了<GL/glext.h>作为OpenGL 1.2及其以上版本的compatibility profile与拓展接口,配合<GL/gl.h>即完整的最新OpenGL compatibility profile与扩展定义。又提供了<GL/glcorearb.h>,即完整的最新OpenGL core profile与ARB扩展定义。
glew有自己的<glew.h>头文件与链接库libGLEW.so.x.o.z(由glew.c编译而成)。<glew.h>囊括了最新的OpenGL全家桶(即定义了profile中的所有函数与此外的一切拓展),这一点也被一些人所诟病,如加载速度慢等。而glad则根据用户的选择(OpenGL版本等),量体裁衣生成对应的OpenGL加载库。无分好坏,择己所需即可。
glewInit()工作流程
根据glew的名称(The OpenGL Extension Wrangler Library)可知,其关键功能在于两部分,一是加载profile,二是加载扩展。(从某种角度来说,每代profile中添加的函数都是前一代的拓展,因此可将profile中的函数看作拓展函数)另外,glewInit()也负责加载glx拓展函数。
其工作原理也很简单,加载当前系统中存在的所有扩展,并利用数组记录下来。待用户使用时,先查找数组记录,确定扩展存在后方可调用。
首先要确保glewInit()函数在glXMakeCurrent()函数之后调用(即glfw中的glfwMakeContextCurrent),也就是在执行glewInit()前,需要将此前以GLX创建的OpenGL context设置为当前线程中的GLX渲染上下文,并将其与目标窗口绑定,这样才能实现与对应低层驱动的关联,从而拿到系统中相应OpenGL的context上下文。
const GLubyte *s = glGetString(GL_VERSION);借助s解析出所采用的OpenGL版本,major,minor。假设我们采用的是OpenGL 3.2,则major = 3且minor = 2。
用bool类型的__GLEW_VERSION_4_6、__GLEW_VERSION_4_5等作标志,根据上述查到的版本号标记出采用的OpenGL功能集,如major = 3且minor = 2,则将__GLEW_VERSION_3_2至__GLEW_VERSION_1_1都标记为true。用bool类型的__GLEW_VERSION_4_6、__GLEW_VERSION_4_5等作标志,根据上述查到的版本号标记出采用的OpenGL功能集,如major = 3且minor = 2,则将__GLEW_VERSION_3_2至__GLEW_VERSION_1_1都标记为true。
glew中存在三种数组,用来描述当前平台对拓展的支持情况,分别为:_glewExtensionString[]、_glewExtensionLookup[]与_glewExtensionEnabled[],三种数组大小相同,其中相同的索引指向的是相同的拓展项。功能如下:
定义好加载函数集合之后,照理就可以开始加载函数了。但是考虑到有些处于实验阶段中的驱动(experimental driver)以及预览版驱动(pre-release driver)里的扩展,仅按照标准的gl机制可能查询不到,所以定义了glewExperimental标志,将此标志设置为true即可利用glXGetProcAddressARB函数将特定的专有拓展或实验性质的拓展试着加载一遍。因此,当设glewExperimental=GL_TRUE;后,会将系统中所有能加载成功扩展(包括getStringi()不能查找到的)的_glewExtensionEnabled[]对应项设为true,表明“环境中能成功加载的所有拓展函数,我已经依次加载,并都列在_glewExtensionEnabled[]中了”。
简而言之,如果加入glewExperimental=GL_TRUE;语句,则_glewExtensionEnabled[]中的拓展更全面,即glewIsSupported()函数的查找结果完整。而_glewExtensionString[]中标记的仅是由glGetStringi()能找到的拓展。若不加上述语句,则_glewExtensionEnabled[]与_glewExtensionString[]中的拓展基本一致,也就是glewIsSupported()与glewGetExtension()的查询结果基本一致。另,glew文档提到:glew 1.3.0 release版开始 glewGetExtension()已被glewIsSupported()替代。
首先,根据定义的core profile加载函数,上面我们假设加载OpenGL 3.2,根据前面的执行流程可知,此时glew将__GLEW_VERSION_1_1至__GLEW_VERSION_3_2依次设置为true,则:
// 伪代码,<glew.h>头文件中有OpenGL 1.1函数的定义,而Linux下则借助-lGL将libGL.so中的函数暴露
// 出来,所以glew.c中从OpenGL 1.2函数开始加载
if (glewExperimental || __GLEW_VERSION_1_2)
// 加载profile 1.2中的任一函数失败则表示不支持此版OpenGL
if (glXGetProcAddressARB(OpenGL_1_2_functions))
_glewExtensionEnabled[1_2_idx] = true;
else
_glewExtensionEnabled[1_2_idx] = false;
if (glewExperimental || __GLEW_VERSION_1_3)
// 加载profile 1.3中的任一函数失败则表示不支持此版OpenGL
if (glXGetProcAddressARB(OpenGL_1_3_functions) )
_glewExtensionEnabled[1_3_idx] = true;
else
_glewExtensionEnabled[1_3_idx] = false;
......
if (glewExperimental || __GLEW_VERSION_3_2)
// 加载profile 3.2中的任一函数失败则表示不支持此版OpenGL
if (glXGetProcAddressARB(OpenGL_3_2_functions))
_glewExtensionEnabled[3_2_idx] = true;
else
_glewExtensionEnabled[3_2_idx] = false;
……
// 根据glewExperimental或用户定义的__GLEW_VERSION_x_o,最多依次加载到最新版OpenGL 4.6
if (glewExperimental || __GLEW_VERSION_4_6)
// 加载profile中的任一函数失败则表示不支持此版OpenGL
if (glXGetProcAddressARB(OpenGL_4_6_functions))
_glewExtensionEnabled[4_6_idx] = true;
else
_glewExtensionEnabled[4_6_idx] = false;
接下来加载专有拓展或实验拓展等:
if (glewExperimental || __GLEW_3DFX_tbuffer)
// 加载GL_3DFX_tbuffer中的任一函数失败则表示不支持此扩展
if (glXGetProcAddressARB(3DFX_tbuffer_functions))
_glewExtensionEnabled[3DFX_tbuffer_idx] = true;
else
_glewExtensionEnabled[3DFX_tbuffer_idx] = false;
if (glewExperimental || __GLEW_AMD_debug_output)
// 加载GL_AMD_debug_output中的任一函数失败则表示不支持此扩展
if(glXGetProcAddressARB(AMD_debug_output_functions))
_glewExtensionEnabled[AMD_debug_output_idx] = true;
else
_glewExtensionEnabled[AMD_debug_output_idx] = false;
可见,如果将glewExperimental设置为true,则遍历系统中存在的一切拓展。
加载完这一系列拓展后,OpenGL拓展部分也就结束了。用户可先利用glewIsSupported()与glewGetExtension()查找待执行的拓展函数在当前系统中是否存在,随后再进行调用。亦可采用if (GLEW_{拓展名,如ARB_vertex_program})或
if (GLEW_VERSION_{core版本,如3_2})来检测OpenGL扩展函数与核心函数是否存在(GLEX_XOXO其实就是_glewExtensionEnabled[]中bool项的封装)。
接下来是加载glx函数部分。
// 由于glXGetCurrentDisplay函数在glx 1.2版中加入,因此借助它来初始化glx 1.2的核心函数
glewGetProcAddress((const GLubyte*)"glXGetCurrentDisplay");
// 检测当前环境下的X display是否可正常使用
display = glXGetCurrentDisplay();
if (display == NULL)
blablabla……
// 查询display支持的glx版本
glXQueryVersion(display, &major, &minor);
// 如果minor = 4, 则将__GLXEW_VERSION_1_4至__GLXEW_VERSION_1_0设置为true
// 如果minor = 3, 则将__GLXEW_VERSION_1_3至__GLXEW_VERSION_1_0设置为true
// 基本现在系统中支持的glx版本均为1.4,所以这里假设采用minor = 4这一选项
// 借助glXGetClientString返回系统支持的glx扩展名
glx_ext = (const GLubyte*)glXGetClientString(display, GLX_EXTENSIONS);
返回的扩展字符串glx_ext形如"GLX_ARB_context_flush_control GLX_ARB_create_context GLX_ARB_create_context_profile……"(即“扩展0 扩展1 扩展2 ……”的形式)借助这一形式即可解析出其中的扩展名,用于比对、确定是否可加载某一拓展。
前面已经提到,利用glXGetCurrentDisplay加载了glx 1.2。所以再来尝试加载glx 1.3。
if (glewExperimental || __GLXEW_VERSION_1_3)
// 加载GLX_VERSION_1_3中的任一函数失败则表示不支持此扩展
if (glXGetProcAddressARB(GLX_VERSION_1_3_functions))
__GLXEW_VERSION_1_3 = true;
else
__GLXEW_VERSION_1_3 = false;
后面就是加载glx有关的零七八碎的专有扩展或实验拓展等,如:
// 伪代码,从之前获得的系统支持的拓展名中,查找是否支持GLX_AMD_gpu_association
__GLXEW_AMD_gpu_association = search_ext("GLX_AMD_gpu_association", glx_ext);
if (glewExperimental || __GLXEW_AMD_gpu_association)
if (glXGetProcAddressARB(GLX_AMD_gpu_association_functions))
__GLXEW_AMD_gpu_association = true;
else
__GLXEW_AMD_gpu_association = false;
完成这部分处理后,用户可先执行glxewIsSupported()检测当前可使用的glx扩展,随即调用对应函数。亦可用if (GLXEW_{glx扩展名,如AMD_gpu_association})来进行检测(GLXEW_XOXO其实就是glx拓展bool项(如__GLXEW_AMD_gpu_association)的封装)。
至此,glew库完成其加载OpenGL与GLX拓展的任务。
glewInit()和OpenGL context的关系
Depending on the GLEW build being used the watertight method is to call glewInit after each and every context change!
With X11/GLX functions pointers are invariant.
But in Windows OpenGL function pointers are specific to each context. Some builds of GLEW are multi context aware, while others are not. So to cover that case, technically you have to call it, everytime the context did change.
参考资料
https://www.douban.com/note/708074485/
https://stackoverflow.com/questions/42975479/are-loaded-opengl-functions-context-or-thread-specific-windows