GPU随想——OpenGL函数加载流程

导航: GLX基本流程 OpenGL函数的分发 到底什么是context?

-----------------------------读前须知-------------------------

历代dri:dri1、dri2、dri3 根据wikipedia词条等整理。另,对X窗口系统方面有兴趣的读者可以阅览Keith Packard(80年代就开始参与设计并开发X窗口系统了,无需赘言……btw,另一位厉害的人物是Kristian Høgsberg)的博客,wikipedia中的不少内容也借鉴与此。

dri1: 在这最早的dri架构中,由于彼时的显存大小有限,因此其中只存在一份“前台/后台/深度/模板缓冲区”实例。所有的应用程序都会在这唯一的实例中,小心维护自己的缓冲区子集。这种工作模式完全是排他的(exclusive),在某一应用开始渲染前,必须锁定硬件,将其他应用锁在外面,哪怕某些有些应用的操作与之毫无冲突也要排除在外。与X server同步的时候,要利用信号与共享内存缓冲区(此缓冲区也被称为SAREA)。(试想你清早起床想去撇大条,而附近的厕所里只有一个坑儿,你排除万难,抢到优先权,势必先回身锁好门,防止有人强行窃取革命胜利的果实,神圣的利益不容侵犯!任门外的街坊跳脚骂街也无所畏惧!!就算是有人要去小便池小便也管不得了!!!) dri1效率的低效是一目了然的,所以这个模型现在被彻底废弃了。

 

GPU随想——OpenGL函数加载流程_第1张图片

dri1模式下,众应用抢占渲染权

dri2: 到了由Kristian Høgsberg实现的dri2,情况发生了大转变。 每个应用都可以分配到自己的缓冲区,所以无需加锁即可进行渲染,因而也就不必为此而争得鼻青脸肿了。 彼时开始流行composite窗口管理器(或称compositor),也就是为每个不同的应用程序赋予一个窗口的“离屏缓冲区(off-screen buffer)”,令其将各自的窗口内容绘制到此缓冲区中,再根据这些窗口的前后顺序等信息,通过compositor,将它们“复合”(PS?)成一张“图片”,最后送至显存,绘制并程序到显示器中。在X窗口系统中,composite功能是由扩展的形式来实现的。。 对此,dri2的策略是:当dri2客户端(也就是用户的应用程序)将数据渲染至前台缓冲区时,其实绘制到的是一个“伪前台缓冲区(fake front buffer)”。而实际的“前台缓冲区”则由X窗口系统管理,在特定的“同步点(synchronization points)”上,再把“伪前台缓冲区”中的数据自动复制到“真·前台缓冲区”中。 另外,如果当server端实现有DRI2SwapBuffers功能时,也可以采用此方法来交换前后台缓冲区(X server当前有实现)。 另外,还可以利用DRI2CopyRegion函数,在“伪·前台缓冲区”与“真·前台缓冲区”之间复制数据。但是,此方法较之DRI2SwapBuffers方法更难以管理;再者,由于使用DRI2CopyRegion时并没有同步的机制,因此可能会造成画面撕裂(tearing)的现象。(比如,下一帧画面还没有复制完成,就将帧缓冲区中的数据刷新到屏幕上了……) 关于dri2的更多细节请参见dri2proto项目。

GPU随想——OpenGL函数加载流程_第2张图片

dri2模式下,各回各家,完美

dri3:

Wayland: 前面提到,当前显示图像的常见方式是将不同窗口的内容composite成一张图片,最后显示在屏幕上。X中,compositor是以扩展的形式出现,而且由于历史包袱等原因,其处理流程复杂、效率低下。它的工作简图如下(可将evdev简单地理解为用户的输入,这会影响窗口间的关系,如前后关系,这样就将触发CS的一系列互动,从而使显示内容发生变化):

GPU随想——OpenGL函数加载流程_第3张图片

X系统显示基本流程(from Wayland官网wayland.freedesktop.org)

这时,dri2的实现者Kristian Høgsberg牵头,新构想了一种显示协议,即Wayland,其compositor参考实现为Weston。在此协议中,Wayland Compositor本身就是服务器,即Wayland Server。另外,Wayland去掉了X背负的历史包袱,精简了客户端与服务器端的交互,并将两端要执行的任务进行更加合理的分配,使显示效率更高,开发起来也更为容易。再者,Wayland还与X保持了一定的兼容方式。根据这些优点不难发现,Wayland应该是Linux下显示方面的发展方向(虽然实际上从2008年开始发展到现在还是磕磕绊绊……)。

GPU随想——OpenGL函数加载流程_第4张图片

Wayland系统显示基本流程(from Wayland官网wayland.freedesktop.org)

 

到底什么是context?

这个词翻译有很多,如上下文、语境、环境等等。我认为把它看作是“资源集合”更好。context其实就是程序要用的资源,执行一个函数可能要用到此时的状态(寄存器、内存等)、前面计算出来的某些数据等等。我们把自己想象成一个木匠,做木工需要有自己的家伙什,锛凿斧锯。而木匠在出外工作(执行函数)时,就要随身带上工具包装好自己趁手的工具(执行函数要用的各种资源打包成“context”),在打磨木材时便打开工具包拿出工具(引用“context”中的各种资源)开始工作。 而OpenGL亦有它自己的context,即OpenGL context。众所周知,OpenGL采用的模型为状态机(所谓状态机可以说是每个函数执行步骤间的影响与联系),因此,OpenGL context里的关键内容之一就是执行过程中的状态。glx即创建OpenGL context的工具。

-----------------------------GLX基本流程-------------------------

GLX是建立X之上的拓展,因此在使用GLX函数之前要建立与X server的连接,并创建好X窗口(详细参见上一篇《GPU随想——OpenGL辅助工具》)。所以,这里分析的前提是满足上述条件。另,此处讨论的是mesa3d中的glx实现。

mesa3d中glx的实现当前位于src/glx文件夹下(mesa3d与其他开源项目差不多,经常更新,所以也经常会有所变动,这里只是针对当前版本进行讨论。其实这种讨论更应该抽离主要流程,而非实际的函数名、文件地址等,但这样讨论起来确实比较直观些,希望再若干年后不会反而成为某种羁绊。简而言之,关键在于处理流程与其中的思想)。从名字可以看出,glxcmds.c为glx调用命令函数集,其实现又依赖于其他文件,如低层根据dri1/dri2/dri3等版本又分为dri2.c/dri2.h等文件。

在执行GLX函数之前,会检测是否对GLX进行初始化,GLX的初始化依赖于当前程序中与X server所建立连接的Display(从GLX大多函数中必有Display参数这一点亦可以看出)。在GLX的实现代码中,我们首先要确定程序中定义了三个宏: GLX_DIRECT_RENDERING(采用GLX直接渲染模式,即绘制任务由GLX client客户端自行绘制),GLX_USE_DRM(利用DRI框架进行GPU硬件加速),HAVE_DRI3(系统中支持dri3)。

既然GLX是X的一种扩展,那就要围绕X中的关键结构体增加扩展信息。如X中Display(也就是连接的X server信息),GLX对其扩展的信息存于glx_display结构体中。

初始GLX的过程,其实就是填写glx_display等结构体的过程,glx_display中保存着与此前Display有关的GLX拓展的私有信息。此结构体关键成员如下:

struct glx_display { // 指向此glx_display描述的Display Display *dpy; // X对于所有拓展都指定了对应的编码,这里用于存储GLX的拓展协议编码 XExtCodes *codes; // 指向下一个glx_display,用于构建glx_display类型链表 struct glx_display *next; // glx拓展的major opcode编号。X的拓展有各自的major opcode,范围为128-255,根据X server的具 // 体实现版本动态分配 int majorOpcode; // X服务器端支持的GLX拓展版本号 const char *serverGLXversion; // 此Display中所有显示设备screen的对应glx信息 struct glx_screen **screens; // 只有开启GLX_DIRECT_RENDERING才会使用以下几项 // driswDisplay即dri sw(software rasterizer)display,用于创建dri软光栅器的glx_screen和销毁GLX // 扩展数据 // driDisplay, 用于创建dri1的glx_screen和销毁dri1的GLX扩展数据 // driDisplay2,用于创建dri2的glx_screen和销毁dri2的GLX扩展数据 // driDisplay3,用于创建dri3的glx_screen和销毁dri3的GLX扩展数据 __GLXDRIdisplay *driswDisplay; __GLXDRIdisplay *driDisplay; __GLXDRIdisplay *dri2Display; __GLXDRIdisplay *dri3Display; };

根据

typedef struct __GLXDRIdisplayRec __GLXDRIdisplay;

struct __GLXDRIdisplayRec { void (*destroyDisplay) (__GLXDRIdisplay * display); struct glx_screen *(*createScreen)(int screen, struct glx_display * priv); };

可知,__GLXDRIdisplay结构体中其实保存的是对应dri版本的destroyDisplay()与createScreen()函数实现。destroyDisplay()用于销毁对应Display的dri拓展(其实就是销毁其对应结构体中的数据)。createScreen()则用来为对应的screen显示设备进行设置,并创建对应的glx扩展数据。(根据前面文章可知,Display即客户端与服务端的连接,而screen则是对应Display中的显示设备,Display中的显示设备也许并非唯一,所以需要指定。其实也能看出这是对具体硬件的一层抽象……)

GLX针对screen的扩展数据用结构体glx_screen来表示,列出主要成员:

struct glx_screen { // 此glx_screen所附属的glx_display struct glx_display *display; // 此显示设备的显示模式,如帧缓冲区有关配置等 struct glx_config *visuals, *configs; }

struct glx_config { // 指向下一个显示模式的指针 struct glx_config * next; // 仅列出部分关键属性,知此结构用意即可 GLboolean rgbMode; GLboolean floatMode; GLboolean colorIndexMode; GLuint doubleBufferMode; GLuint stereoMode; GLint redBits, greenBits, blueBits, alphaBits; /* bits per comp */ GLuint redMask, greenMask, blueMask, alphaMask; GLint rgbBits; /* total bits for rgb */ GLint indexBits; /* total bits for colorindex */ GLint accumRedBits, accumGreenBits, accumBlueBits, accumAlphaBits; GLint depthBits; GLint stencilBits; …… } GLX中维系着一个glx_display类型的链表: static struct glx_display *glx_displays;

每当调用GLX函数时,都会首先遍历此链表,看是否存在与调用的GLX函数参数相同的Display(例如,glXDestroyContext(Display * dpy, GLXContext ctx)中的第一个参数Display * dpy),如果存在则采用此Display所在的glx_display结构体实现GLX函数的执行逻辑;否则以GLX参数中传来的Display创建对应的glx_display,将创建的glx_display加入这个链表中,再实现GLX函数的执行逻辑。这便是GLX初始化的全部工作了。

-----------------------------__glXInitialize()基本流程-------------------------

__glXInitialize()初始化函数的主要流程(由于OpenGL可以支持多线程,因此会涉及多线程处理机制,后面我们为了简化描述GLX与OpenGL的逻辑流程,去掉了多线程部分,但在关键处仍会提及。错误处理机制亦是如此,能省则省。另外,为了直观可能会用到伪代码等描述方式):

struct glx_display *dpyPriv; for (dpyPriv = glx_displays; dpyPriv; dpyPriv = dpyPriv->next) { // 如存在与GLX函数传来的Display相同的glx_display,则借此实现GLX函数执行逻辑 // 否则根据此Display创建对应的glx_display,并加入到glx_displays之中,再将其返回实现GLX函数 if (dpyPriv->dpy == dpy) { return dpyPriv; } } // glx_displays中不存在对应的glx_display,那我们就来为之对应地创建一个 dpyPriv = calloc(1, sizeof *dpyPriv); // 利用GLX函数参数传入的Display *dpy来初始化此对应X server连接上的GLX扩展 dpyPriv->codes = XInitExtension(dpy, "GLX");

dpyPriv->dpy = dpy; dpyPriv->majorOpcode = dpyPriv->codes->major_opcode;

Linux中有几种环境变量会影响mesa dri驱动的调用,如LIBGL_ALWAYS_INDIRECT(设置为1、true或yes则采用dri indirect间接渲染)、LIBGL_ALWAYS_SOFTWARE(设置为1、true或yes则全程采用CPU软光栅器进行渲染)、LIBGL_DRI3_DISABLE(设置为1、true或yes则禁用dri3渲染)。

而我们则假设并不在环境变量中设置这三种变量,也就是默认皆为0。

依次为每个dri版本创建对应的__GLXDRIdisplay:

dpyPriv->dri3Display = dri3_create_display(dpy); dpyPriv->dri2Display = dri2CreateDisplay(dpy); dpyPriv->driDisplay = driCreateDisplay(dpy); dpyPriv->driswDisplay = driswCreateDisplay(dpy);

xxooCreateDisplay除了创建__GLXDRIdisplay以外,还负责为对应的dri拓展填写其他数据信息,以及挂载其他对应的低层调用函数。以下针对dri2CreateDisplay进行讨论。

-----------------------------dri2CreateDisplay基本流程-------------------------

dri2CreateDisplay的实际工作其实就是填写dri2_display结构体,顾名思义,此结构体描述的即display对应的dri2扩展信息。

dri2_display结构体中的关键成员如下:

struct dri2_display { __GLXDRIdisplay base; // dri2也分细分具体版本,早期版本的X server不支持后面加入的一些特性,这里我们不多加讨论 // 假设当前实现的dri2即为最高版本1.4 int driMajor; int driMinor; // dri2中的扩展函数 const __DRIextension *loader_extensions[5]; } struct dri2_display *pdp; // 为dri2_display结构体开辟内存空间 pdp = malloc(sizeof *pdp);

// 查询当前display支持的dri2具体版本,这一步需要与X server进行交互 DRI2QueryVersion(dpy, &pdp->driMajor, &pdp->driMinor); // 挂载dri2对应__GLXDRIdisplay中的函数 pdp->base.destroyDisplay = dri2DestroyDisplay; pdp->base.createScreen = dri2CreateScreen; // 告知dri2扩展函数的实现版本 i = 0; pdp->loader_extensions[i++] = &dri2LoaderExtension.base; pdp->loader_extensions[i++] = &dri2UseInvalidate.base; pdp->loader_extensions[i++] = &driBackgroundCallable.base; pdp->loader_extensions[i++] = NULL; 其中, static const __DRIdri2LoaderExtension dri2LoaderExtension = { .base = { __DRI_DRI2_LOADER, 3 },

.getBuffers = dri2GetBuffers, .flushFrontBuffer = dri2FlushFrontBuffer, .getBuffersWithFormat = dri2GetBuffersWithFormat, }; static const __DRIuseInvalidateExtension dri2UseInvalidate = { .base = { __DRI_USE_INVALIDATE, 1 } }; static const __DRIbackgroundCallableExtension driBackgroundCallable = { .base = { __DRI_BACKGROUND_CALLABLE, 2 },

.setBackgroundContext = driSetBackgroundContext, .isThreadSafe = driIsThreadSafe, }; 上述3种结构体中的成员分两部分,.base中2项的为当前结构中的拓展名及其实现版本;后面则挂载具体的实现函数。 最后, return &pdp->base; dri2CreateDisplay函数返回设置好的__GLXDRIdisplay结构体,回传给dpyPriv->dri2Display,即赋予其实际的dri2版destroyDisplay()与createScreen()执行函数。

-----------------------------dri2CreateDisplay基本流程再见-------------------------

接下来调用AllocAndFetchScreenConfigs(dpy, dpyPriv)函数,此函数为glx函数参数Display *dpy中管控的显示设备screen分配配置内存空间,并为之填写实际配置数据。

-----------------------------AllocAndFetchScreenConfigs基本流程------------------------- // 此Display中有多少显示设备? screens = ScreenCount(dpy); // 为这些设备的配置信息分配空间 priv->screens = malloc(screens * sizeof *priv->screens); // 此Display支持的GLX版本号? priv->serverGLXversion = __glXQueryServerString(dpy, priv->majorOpcode, 0, GLX_VERSION); for (i = 0; i < screens; i++, psc++) { psc = NULL; // 这里采用前面xoxoCreateDisplay设置好的createScreen()函数配置显示设备并创建对应的glx扩展数 // 据 if (priv->dri3Display) psc = (*priv->dri3Display->createScreen) (i, priv); if (psc == NULL && priv->dri2Display) psc = (*priv->dri2Display->createScreen) (i, priv); if (psc == NULL && priv->driDisplay) psc = (*priv->driDisplay->createScreen) (i, priv); // 再失败就用软光栅器进行绘制 if (psc == NULL && priv->driswDisplay) psc = (*priv->driswDisplay->createScreen) (i, priv); // 还不行就只好交给X server进行绘制 if (psc == NULL) psc = indirect_create_screen(i, priv); // 保存每个显示设备的对应配置数据 priv->screens[i] = psc; } SyncHandle(); return GL_TRUE; -----------------------------AllocAndFetchScreenConfigs基本流程再见------------------------- // 其实就是根据上述设置流程配置显示给服务器端的客户端扩展信息(如GLX等等) // (不过经粗看之后,这个函数似乎并没有起什么作用) __glX_send_client_info(dpyPriv); // 将我们本次创建的struct glx_display *dpyPriv;加入全局glx_displays链中(略过多线程处理流程) dpyPriv->next = glx_displays; // 给需要此display拓展结构体的glx函数实现后续流程 return dpyPriv;

-----------------------------__glXInitialize()基本流程再见-------------------------

最后,我们再讨论*priv->dri2Display->createScreen,根据上文可知,也就是dri2CreateScreen函数。

-----------------------------dri2CreateScreen基本流程-------------------------

首先,dri2CreateScreen函数参数有二: int screen; // 当前正在设置的显示设备编号 struct glx_display * priv; // 当前创建的Display glx扩展结构体

struct dri2_screen *psc; psc = calloc(1, sizeof *psc); // 分配dri2_screen空间,此为dri2对显示设备的扩展结构体

psc->fd = -1;

// 与X server交互,获取对应显示设备的信息,并将其填写至相应的glx扩展结构体中 // 要填写的关键glx扩展结构体是struct glx_config,即显示设备的显示模式 glx_screen_init(&psc->base, screen, priv);

// 返回与当前显示设备screen关联的窗口所对应的驱动名driverName与设备名deviceName // 即从X server端返回的驱动与设备信息 DRI2Connect(priv->dpy, RootWindow(priv->dpy, screen), &driverName, &deviceName);

// 打开DRM设备(DRM device,针对DRM子系统,将GPU抽象而映射出来的文件, // 地址通常为/dev/dri/cardX,X是系统中GPU的编号) psc->fd = loader_open_device(deviceName);

// dri2的安全验证机制,只有通过此验证,客户端才能访问内核渲染管理器 drmGetMagic(psc->fd, &magic); DRI2Authenticate(priv->dpy, RootWindow(priv->dpy, screen), magic);

// 这次靠mesa自己来根据fd获取驱动名,也就是凭借客户端本地信息来获取最为适用的驱动, // 如果能找到就以此作为当前待加载的驱动名 char *loader_driverName; loader_driverName = loader_get_driver_for_fd(psc->fd); if (loader_driverName) { free(driverName); driverName = loader_driverName; }

// 加载dri驱动,即名为xo_dri.so的文件,如i965_dri.so psc->driver = driOpenDriver(driverName);

// 挂载驱动对应的扩展 const __DRIextension **extensions; extensions = driGetDriverExtensions(psc->driver, driverName);

for (i = 0; extensions[i]; i++) { // 挂载dri核心扩展 if (strcmp(extensions[i]->name, __DRI_CORE) == 0) psc->core = (__DRIcoreExtension *) extensions[i]; // 挂载dri2扩展 if (strcmp(extensions[i]->name, __DRI_DRI2) == 0) psc->dri2 = (__DRIdri2Extension *) extensions[i]; }

// 创建对应屏幕的私有驱动信息 psc->driScreen = psc->dri2->createNewScreen2(screen, psc->fd, (const __DRIextension **)&pdp->loader_extensions[0], extensions, &driver_configs, psc);

至于createNewScreen2在哪里? 在dri_util.c文件中有:

/** DRI2 interface */ const __DRIdri2Extension driDRI2Extension = { .base = { __DRI_DRI2, 4 }, .createNewScreen = dri2CreateNewScreen, .createNewDrawable = driCreateNewDrawable, .createNewContext = driCreateNewContext, .getAPIMask = driGetAPIMask, .createNewContextForAPI = driCreateNewContextForAPI, .allocateBuffer = dri2AllocateBuffer, .releaseBuffer = dri2ReleaseBuffer, .createContextAttribs = driCreateContextAttribs, .createNewScreen2 = driCreateNewScreen2, };

// 为struct dri2_screen *psc挂载扩展的实现函数,并标明当前平台对这些扩展的实际支持情况 dri2BindExtensions(psc, priv, driverName);

// 根据刚创建dri2 screen,将其显示模式转换为的glx_config格式 configs = driConvertConfigs(psc->core, psc->base.configs, driver_configs); visuals = driConvertConfigs(psc->core, psc->base.visuals, driver_configs);

// 由于每个显示设备实际仅采用一种显示模式,因此仅保留当前环境中使用的这一种模式,而销毁其他 // 模式 glx_config_destroy_list(psc->base.configs); psc->base.configs = configs; glx_config_destroy_list(psc->base.visuals); psc->base.visuals = visuals;

// 记录所创建dri2 screen的驱动信息 psc->driver_configs = driver_configs;

// vtable的类型为glx_screen_vtable,其中包含4个函数指针,分别为:create_context、 // create_context_attribs、query_renderer_integer、query_renderer_string // 另外,在dri2的实现中,dri2_screen_vtable的定义为: // static const struct glx_screen_vtable dri2_screen_vtable = { // .create_context = dri2_create_context, // .create_context_attribs = dri2_create_context_attribs, // .query_renderer_integer = dri2_query_renderer_integer, // .query_renderer_string = dri2_query_renderer_string, // }; psc->base.vtable = &dri2_screen_vtable;

// __GLXDRIscreen结构体中包括的函数有:destroyScreen、createContext、createDrawable、 // swapBuffers、copySubBuffer、getDrawableMSC、waitForMSC、waitForSBC、setSwapInterval、 // getSwapInterval、getBufferAge __GLXDRIscreen *psp; psp = &psc->vtable; psc->base.driScreen = psp; // 开始挂载各种函数…… psp->destroyScreen = dri2DestroyScreen; psp->createDrawable = dri2CreateDrawable; psp->swapBuffers = dri2SwapBuffers; psp->getDrawableMSC = dri2DrawableGetMSC; psp->waitForMSC = dri2WaitForMSC; psp->waitForSBC = dri2WaitForSBC; psp->setSwapInterval = dri2SetSwapInterval; psp->getSwapInterval = dri2GetSwapInterval; psp->getBufferAge = NULL;

// 以下拓展用来实现精确垂直同步,详见拓展文档GLX_OML_sync_control.txt // 什么?configQueryb函数在哪?见文件dri_util.c: // const __DRI2configQueryExtension dri2ConfigQueryExtension = { // .base = { __DRI2_CONFIG_QUERY, 1 }, // .configQueryb = dri2ConfigQueryb, // .configQueryi = dri2ConfigQueryi, // .configQueryf = dri2ConfigQueryf, // }; unsigned char disable; // 根据平台对GLX_OML_sync_control扩展的支持情况,而开启此扩展 if (psc->config->configQueryb(psc->driScreen, "glx_disable_oml_sync_control", &disable) || !disable) __glXEnableDirectExtension(&psc->base, "GLX_OML_sync_control");

// 挂载dr2的copySubBuffer函数实现 psp->copySubBuffer = dri2CopySubBuffer; // 开启GLX_MESA_copy_sub_buffer扩展 __glXEnableDirectExtension(&psc->base, "GLX_MESA_copy_sub_buffer");

// 驱动对应的函数已加载完毕,释放其字符串内存 free(driverName); free(deviceName);

return &psc->base;

-----------------------------dri2CreateScreen基本流程再见------------------------- again,大多glx函数的实现都依赖glx_display,也就是Display的对应glx扩展。所以在glx函数实现中都会先调用__glXInitialize()索取glx_display。__glXInitialize()会在全局glx_displays链中查找对应的glx_display结构体,如果不存在(如第一次调用glx函数),则会根据传入的Display *参数构造出对应的glx_display,加入全局glx_displays链供后续使用,并将此glx_display返回供glx函数执行后续逻辑。

有人可能会问,既然是glx direct + dri直接渲染模式为什么还有那么多代码涉及X的交互?那是因为dri只管GPU加速渲染,而实际的显示工作却是在X的管控下,因此,当然也就需要有X部分参与进来~

-----------------------------正题-------------------------

解决完__glXInitialize()后,现在按《OpenGL辅助工具》里OpenGL应用程序中glx函数的调用顺序简单进行讲解。

glXGetProcAddressARB()函数可用于加载glXCreateContextAttribsARB()。在mesa的实现中,glx维护了一个下列结构体构成的表: struct name_address_pair { const char *Name; GLvoid *Address; }; 第一项Name即glx函数名,如glXCreateContextAttribsARB。第二项Address则是其地址。这样,glXGetProcAddressARB()即可借助此name_address_pair表来查询要加载的函数,并返回其实际地址。此函数还可加载其他glx或gl函数,此暂不讨论。

glXGetFBConfigs()、 glXGetFBConfigAttrib()与glXGetVisualFromFBConfig()三个函数的流程都很简单,即根据__glXInitialize()中与X server交互所得到并记录的平台显示设备信息(如显示模式、帧缓冲区的设置组合),来获取平台所支持的设定,从而令程序员根据查询结果针对OpenGL应用进行相应的设置。

接下来轮到一个关键函数glXCreateContextAttribsARB(),目前位于src/glx/create_context.c文件中。

-----------------------------glXCreateContextAttribsARB()你好-------------------------

-----------------------------glXCreateContextAttribsARB()再见-------------------------

btw,Windows平台上窗口系统与GPU加速渲染(D3D)都是微软自己供应,且限制在一个平台上,所以后面好多流程都不用用户操心。像swapchain交换链这样的操作其实是需要窗口系统协作的,在Linux系统需要X与GLX、mesa3d(OpenGL)的交互,而Windows中抽象为DXGI,用户只需轻松调用几个函数,了解下基本的工作机制就能上手了。这也就是统一平台的优势吧!

你可能感兴趣的:(GPU随想——OpenGL函数加载流程)