[Z]OpenGL Driver Architecture
OpenGL Driver Architecture
Yang Jian
jyang@cad.zju.edu.cn
1 OpenGL Installable Client Driver
Windows下OpenGL Driver有三种类型,可安装的客户端驱动程序(Installable Client Driver
,缩写为ICD),微驱动(minidriver)和独立驱动程序。独立驱动程序主要是为一些特殊的图形系统设计,这些图形系统的能力相对比较强大,整个系统的主要目的为图形应用,目前已经不多见。MiniDriver在3D图形加速卡没有普及之前曾经被使用过,但是有由于其支持的能力太低,自nvi
dia TNT 之后就看不到这类驱动程序。
现在图形芯片供应商都采用ICD的方式提供OpenGL Driver。ICD的最大特点是(1)能够支持OpenGL驱动程序开发者提供完整的OpenGL支持,和OpenGL Extension支持;(2)使得OpenGL能够以client-server的方式运行;(3)对OpenGL应用程序开发者提供统一的API接口。
下面是一个OpenGL ICD驱动程序体系结构的简图。
Application
|
|
GDI32.dll<---OpenGL32.dll ---? winsrv.dll
| |
| |
-------- xxgl.dll(example, nvoglnt.dll, atioglxx.gl, ) ------? DirectDraw5~7
| |
| |----? Direct3D8 or above
3d graphics card(Hardware)
其中
OpenGL32.dll是微软基于软件实现的OpenGL。
Nvntgl.dll或者atiglxx.dll分别是Nvidia和ATI提供的OpenGL ICD驱动程序。
同时OpenGL32.dll和图形芯片供应商的驱动程序都需要访问DirectDraw和GDI32。
个别的OpenGL驱动程序甚至使用了Direct3D8或者更高的版本。
2 确定当前图形加速卡的ICD驱动
对于Windows98/me的系统,可以注册表的下面位置查找:
“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/OpenGLDrivers”
对于Windows 2000/XP的系统,请定位到注册表的下述位置:
“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/OpenGLDrivers”
运行 regedit.exe可以进入注册表。
3 基于ICD的OpenGL数据流分析
我们使用上图作为分析基础。
3.1 像素格式
首先,OpenGL 应用程序检测图形加速卡能够支持的像素格式,主要是Back Color Buffer,Depth Buffer和Stencil Buffer。目前需要图形加速卡都不再支持8-bits的Back Buffer。ATI和Nvidia的许多图形卡均不支持32-bits的Depth
Buffer,一般都是D16,D24和D24S8(24-bitsDepth和8-bits的Stencil)。
当Application调用DescribePixelFormat, GetPixelFormat, SetPixelFormat,
ChoosePixelFormat,应用程序把这些调用传递给OpenGL32,OpenGL32首先显示卡驱动程序检测能够支持的OpenGL像素格式,显示卡驱动程序对应有多个回调函数,DrvDescribePixelFormat(显示驱动), DrvSetPixelFormat(显示驱动)等。
如果显示驱动没有对应的回调函数,OpenGL32.dll会调用ICD驱动中对应的函数完成这个工作。大家可以使用VC6.0中的工具Depends打开nvoglnt.dll(nvidia)或者atioglxx..dll(ATI),我们可以看到他们都实现下面上面两个回调函数:
DrvDescribePixelFormat(ICD驱动)
DrvSetPixelFormat(ICD驱动)
不过大家可能觉得奇怪,nividia或者ATI的OpenGL驱动只有几个函数,而且都是以Drv*开头,下面我会给大家一个解答。
2.2 Context 创建与设置当前Context
当我们的应用程序调用wglCreateContex的时候,OpenGL32.dll的wglCreateContext函数将参数转化后,调用ICD Driver的DrvCreateContext函数,DrvCreateContext函数然后调用ICD Driver的DrvCreateLayerContext,其中有一个参数是HDC,ICD Driver通过HDC
获得当前的像素格式ID,实际上这个像素格式ID就是ICD Driver本身定义的。DrvCreateLayerContext将产生一个OpenGL Context的内部数据结构,同时根据像素格式ID将对应的像素格式设置到OpenGL context的内部数据结构,同时向OpenGL32.dll的wglCreateContext返回这个OpenGL
context的编号,一般是从0开始向上递增。
请注意,这时候,你换无法调用OpenGL API,因为应用程序还没有调用wglMakeCurrent。
我们现在可以调用wglMakeCurrent,因为我们已经创建一个OpenGL Context。wglMakeCurrent将调用ICD Driver的DrvSetContext。DrvSetContext将完成以下工作:
(1) 根据HDC获取当前的窗口句柄,HWND;
(2) 初始化内部数据结构;
(3) 初始化SW OpenGL context的常量和回调函数
(4) 创建back color buffer, Depth Buffer 和stencil Buffer,和其他类型的缓冲区;
(5) 调用硬件OpenGL context 的初始化函数,初始化硬件OpenGL context的常量和回调函数;
(6) HW OpenGL Context 设置正确的glGetString返回值;
(7) 设置一个当前的__glDispatchTable,并且返回这个Dispatchtable。
这些工作中有两项任务需要说明,一是如何创建缓冲区,二是Dispatchtable是何方怪物。
颜色缓冲区,深度缓冲区和模板缓冲区是位于图形加速卡上的一块显示内存区域,它是由图形加速卡的显示驱动程序所管理,OpenGL ICD Driver是无法直接访问和申请的。所以OpenGL ICD
Driver通过IDirectDraw7::CreateSurface申请。不过一些显示芯片供应上通过IDirect3DDevice::CreateBackBuffer或者IDirect3DDevice::CreateDepthStencilBuffer获得,两种方法并没有本质上的差异。另外还有一种方法是通过ExtEscape的方法创建缓冲区,这种方法比较复杂,我就不
讲了。
需要注意的是:目前的硬件体系结构将Depth Buffer和Stencil Buffer结合在一起,它们在显示内存中相互交叠,一般的存储格式为:24-bits Depth, 8-bits Stencil, 24-D, 8-S,….
glDispatchTable是OpenGL ICD 驱动程序最为关键的一个结构。它们OpenGL 所有API函数指针的一个结构。
它基本定义如下:
struct glDispatchTable{
void (APIENTRY *glAccum) (GLenum op, GLfloat value);
void (APIENTRY *glAlphaFunc) (GLenum func, GLclampf ref);
…..
};
OpenGL的每一个函数都都有一个对应的函数指针定义。DrvSetContext负责返回一个glDispatchTable结构指针,将ICD Driver中所有OpenGL API的函数指针赋值给glDispatchTable,使得OpenGL32.dll能够直接调用ICD Driver中对应的OpenGL函数。
例如,应用程序调用glBegin,将会产生如下的调用顺序:
Application Call glBegin == >
OpenGL32.dll glBegin
{
(*__glDispatchTable.glBegin)(); == >
}
ICD Driver Call glBegin
{
….
}
也就是说通过glDispatchTable,所有的OpenGLAPI最终都将进入到ICD Driver对应的函数中。
啰嗦这么多,相信大家该对OpenGL体系结构有一个大致的认识了。下面我将另外一个内容,OpenGL ICD是如何实现Display List。
3.3 ICD Driver中Display List的实现。
OpenGL中Display List是一种加速绘制的方法,那么它内部是如何实现的呢?
其实相当简单。
首先ICD Driver对每一个OpenGL API进行编号,例如,glAccum是0,glAlphaFunc是2,
#define __GL_DLISTOP_ACCUM 0
#define __GL_DLISTOP_ ALPHAFUNC 1
….
当应用程序调用glNewList生成一个新的Display List,ICD Driver分配一个数组,其中每个元素定义如下
struct listop{
int opcode; //对应上面的API编号
void* parameter;
};
如果我们在Display List中调用glVertex3F,那么ICD Driver调用如下代码(真实代码要复杂些),这时候ICD Driver仅仅将这些命令和它们的参数保存数组中,并不处理这个API。
plistop->opcode = __GL_DLISTOP_VERTEX3F;
(GLfoat*)plistop->parameter = x;
((GLfoat*)plistop->parameter)++ = y;
((GLfoat*)plistop->parameter)++ = z;
然后,当应用程序调用一个glCallList的时候,ICD Driver首先找到那个数组,根据每个listop的opcode调用对应的处理函数。
3.4 ICD Driver中纹理的管理
OpenGL ICD Driver无法直接申请显示内存,但是图形硬件需要使用纹理数据实现问题贴图。那么ICD Driver必须能够将纹理数据拷贝到显示内存或AGP memory, 即Local Video Memory ,non-local Video Memory。AGP
memory是操作系统管理的,经过GART映射后图形处理芯片可以访问的特殊内存区域。
当我们调用一个glTexImage2D的函数定义纹理的时候,ICD Driver首先创建一块系统内存区域,将纹理数据拷贝到系统内存。然后调用IDirectDraw7::CreateSurface申请video memory。如果申请失败,而且video
memory存储空间不足,表明现在有太多的应用程序在运行,或者这个应用程序申请了太多的video memory,需要释放一些纹理内存空间,调用IDirctDrawSurface7::Release一些video memory空间。一般采用FIFO的方式实现内存的替换管理。
当video memory申请成功后,调用IDirectDrawSurface7::Lock获得video memory的用户地址,将保存在系统内存中的数据复制到video memory中。图形芯片就能够访问纹理数据了。
3.5 wglMakeCurrent(NULL, NULL)
当应用程序调用wglMakeCurrent(NULL),OpenGL32.dll将调用ICD Driver中的DrvReleaseContext使得所有的OpenGL API都成为空操作,不会产生任何结果。
3.6 SwapBuffer的实现
如果是窗口程序,ICD Driver 的DrvSwapBuffers将调用IDirectDrawSurface7::Blt实现从后台缓冲区到前台缓冲区的内容复制。
如果是全屏幕程序,DrvSwapBuffers将调用IDirectDrawSurface7::flip实现快速的前后缓冲区切换。
一些驱动程序使用IDirect3DDevice8::Present或者IDirect3DDevice9::Present实现缓冲区内容的切换。
3.7 wglDeleteContext
wglDeleteContext将调用DrvDeleteCOntext,它将释放所有申请的系统内存和显示内存(video memory),以及纹理,并且删除硬件OpenGL context和软件OpenGL context。而且所有的OpenGL API将不可用。
4 OpenGL ICD Driver 评价
目前OpenGL ICD Driver都相当成熟,基本上都是沿用SGI提供的参考框架结构。大家可以参考下面的链接获取SGI的OpenGL driver实现:
http://oss.sgi.com/projects/ogl-sample/
目前OpenGL做得最好的是3dlabs,它能够支持OpenGL 2.0。
ATI 和 nvidia都支持 OpenGL 1.4,不过ATI支持更多的OpenGL extension。
其他如SIS, XGI, S3支持的API比nvidia要少一些。
Intel等集成显示卡的驱动自然很差了,硬件能力为OpenGL 1.2或者OpenGL 1.3.
5 Linux的OpenGL Driver体系结构
Linux下为DRI(Direct Render Infrastrcture),有兴趣可以参考它的网站;
http://dri.sourceforge.net/cgi-bin/moin.cgi
jyang@cad.zju.edu.cn
1 OpenGL Installable Client Driver
Windows下OpenGL Driver有三种类型,可安装的客户端驱动程序(Installable Client Driver
,缩写为ICD),微驱动(minidriver)和独立驱动程序。独立驱动程序主要是为一些特殊的图形系统设计,这些图形系统的能力相对比较强大,整个系统的主要目的为图形应用,目前已经不多见。MiniDriver在3D图形加速卡没有普及之前曾经被使用过,但是有由于其支持的能力太低,自nvi
dia TNT 之后就看不到这类驱动程序。
现在图形芯片供应商都采用ICD的方式提供OpenGL Driver。ICD的最大特点是(1)能够支持OpenGL驱动程序开发者提供完整的OpenGL支持,和OpenGL Extension支持;(2)使得OpenGL能够以client-server的方式运行;(3)对OpenGL应用程序开发者提供统一的API接口。
下面是一个OpenGL ICD驱动程序体系结构的简图。
Application
|
|
GDI32.dll<---OpenGL32.dll ---? winsrv.dll
| |
| |
-------- xxgl.dll(example, nvoglnt.dll, atioglxx.gl, ) ------? DirectDraw5~7
| |
| |----? Direct3D8 or above
3d graphics card(Hardware)
其中
OpenGL32.dll是微软基于软件实现的OpenGL。
Nvntgl.dll或者atiglxx.dll分别是Nvidia和ATI提供的OpenGL ICD驱动程序。
同时OpenGL32.dll和图形芯片供应商的驱动程序都需要访问DirectDraw和GDI32。
个别的OpenGL驱动程序甚至使用了Direct3D8或者更高的版本。
2 确定当前图形加速卡的ICD驱动
对于Windows98/me的系统,可以注册表的下面位置查找:
“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/OpenGLDrivers”
对于Windows 2000/XP的系统,请定位到注册表的下述位置:
“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/OpenGLDrivers”
运行 regedit.exe可以进入注册表。
3 基于ICD的OpenGL数据流分析
我们使用上图作为分析基础。
3.1 像素格式
首先,OpenGL 应用程序检测图形加速卡能够支持的像素格式,主要是Back Color Buffer,Depth Buffer和Stencil Buffer。目前需要图形加速卡都不再支持8-bits的Back Buffer。ATI和Nvidia的许多图形卡均不支持32-bits的Depth
Buffer,一般都是D16,D24和D24S8(24-bitsDepth和8-bits的Stencil)。
当Application调用DescribePixelFormat, GetPixelFormat, SetPixelFormat,
ChoosePixelFormat,应用程序把这些调用传递给OpenGL32,OpenGL32首先显示卡驱动程序检测能够支持的OpenGL像素格式,显示卡驱动程序对应有多个回调函数,DrvDescribePixelFormat(显示驱动), DrvSetPixelFormat(显示驱动)等。
如果显示驱动没有对应的回调函数,OpenGL32.dll会调用ICD驱动中对应的函数完成这个工作。大家可以使用VC6.0中的工具Depends打开nvoglnt.dll(nvidia)或者atioglxx..dll(ATI),我们可以看到他们都实现下面上面两个回调函数:
DrvDescribePixelFormat(ICD驱动)
DrvSetPixelFormat(ICD驱动)
不过大家可能觉得奇怪,nividia或者ATI的OpenGL驱动只有几个函数,而且都是以Drv*开头,下面我会给大家一个解答。
2.2 Context 创建与设置当前Context
当我们的应用程序调用wglCreateContex的时候,OpenGL32.dll的wglCreateContext函数将参数转化后,调用ICD Driver的DrvCreateContext函数,DrvCreateContext函数然后调用ICD Driver的DrvCreateLayerContext,其中有一个参数是HDC,ICD Driver通过HDC
获得当前的像素格式ID,实际上这个像素格式ID就是ICD Driver本身定义的。DrvCreateLayerContext将产生一个OpenGL Context的内部数据结构,同时根据像素格式ID将对应的像素格式设置到OpenGL context的内部数据结构,同时向OpenGL32.dll的wglCreateContext返回这个OpenGL
context的编号,一般是从0开始向上递增。
请注意,这时候,你换无法调用OpenGL API,因为应用程序还没有调用wglMakeCurrent。
我们现在可以调用wglMakeCurrent,因为我们已经创建一个OpenGL Context。wglMakeCurrent将调用ICD Driver的DrvSetContext。DrvSetContext将完成以下工作:
(1) 根据HDC获取当前的窗口句柄,HWND;
(2) 初始化内部数据结构;
(3) 初始化SW OpenGL context的常量和回调函数
(4) 创建back color buffer, Depth Buffer 和stencil Buffer,和其他类型的缓冲区;
(5) 调用硬件OpenGL context 的初始化函数,初始化硬件OpenGL context的常量和回调函数;
(6) HW OpenGL Context 设置正确的glGetString返回值;
(7) 设置一个当前的__glDispatchTable,并且返回这个Dispatchtable。
这些工作中有两项任务需要说明,一是如何创建缓冲区,二是Dispatchtable是何方怪物。
颜色缓冲区,深度缓冲区和模板缓冲区是位于图形加速卡上的一块显示内存区域,它是由图形加速卡的显示驱动程序所管理,OpenGL ICD Driver是无法直接访问和申请的。所以OpenGL ICD
Driver通过IDirectDraw7::CreateSurface申请。不过一些显示芯片供应上通过IDirect3DDevice::CreateBackBuffer或者IDirect3DDevice::CreateDepthStencilBuffer获得,两种方法并没有本质上的差异。另外还有一种方法是通过ExtEscape的方法创建缓冲区,这种方法比较复杂,我就不
讲了。
需要注意的是:目前的硬件体系结构将Depth Buffer和Stencil Buffer结合在一起,它们在显示内存中相互交叠,一般的存储格式为:24-bits Depth, 8-bits Stencil, 24-D, 8-S,….
glDispatchTable是OpenGL ICD 驱动程序最为关键的一个结构。它们OpenGL 所有API函数指针的一个结构。
它基本定义如下:
struct glDispatchTable{
void (APIENTRY *glAccum) (GLenum op, GLfloat value);
void (APIENTRY *glAlphaFunc) (GLenum func, GLclampf ref);
…..
};
OpenGL的每一个函数都都有一个对应的函数指针定义。DrvSetContext负责返回一个glDispatchTable结构指针,将ICD Driver中所有OpenGL API的函数指针赋值给glDispatchTable,使得OpenGL32.dll能够直接调用ICD Driver中对应的OpenGL函数。
例如,应用程序调用glBegin,将会产生如下的调用顺序:
Application Call glBegin == >
OpenGL32.dll glBegin
{
(*__glDispatchTable.glBegin)(); == >
}
ICD Driver Call glBegin
{
….
}
也就是说通过glDispatchTable,所有的OpenGLAPI最终都将进入到ICD Driver对应的函数中。
啰嗦这么多,相信大家该对OpenGL体系结构有一个大致的认识了。下面我将另外一个内容,OpenGL ICD是如何实现Display List。
3.3 ICD Driver中Display List的实现。
OpenGL中Display List是一种加速绘制的方法,那么它内部是如何实现的呢?
其实相当简单。
首先ICD Driver对每一个OpenGL API进行编号,例如,glAccum是0,glAlphaFunc是2,
#define __GL_DLISTOP_ACCUM 0
#define __GL_DLISTOP_ ALPHAFUNC 1
….
当应用程序调用glNewList生成一个新的Display List,ICD Driver分配一个数组,其中每个元素定义如下
struct listop{
int opcode; //对应上面的API编号
void* parameter;
};
如果我们在Display List中调用glVertex3F,那么ICD Driver调用如下代码(真实代码要复杂些),这时候ICD Driver仅仅将这些命令和它们的参数保存数组中,并不处理这个API。
plistop->opcode = __GL_DLISTOP_VERTEX3F;
(GLfoat*)plistop->parameter = x;
((GLfoat*)plistop->parameter)++ = y;
((GLfoat*)plistop->parameter)++ = z;
然后,当应用程序调用一个glCallList的时候,ICD Driver首先找到那个数组,根据每个listop的opcode调用对应的处理函数。
3.4 ICD Driver中纹理的管理
OpenGL ICD Driver无法直接申请显示内存,但是图形硬件需要使用纹理数据实现问题贴图。那么ICD Driver必须能够将纹理数据拷贝到显示内存或AGP memory, 即Local Video Memory ,non-local Video Memory。AGP
memory是操作系统管理的,经过GART映射后图形处理芯片可以访问的特殊内存区域。
当我们调用一个glTexImage2D的函数定义纹理的时候,ICD Driver首先创建一块系统内存区域,将纹理数据拷贝到系统内存。然后调用IDirectDraw7::CreateSurface申请video memory。如果申请失败,而且video
memory存储空间不足,表明现在有太多的应用程序在运行,或者这个应用程序申请了太多的video memory,需要释放一些纹理内存空间,调用IDirctDrawSurface7::Release一些video memory空间。一般采用FIFO的方式实现内存的替换管理。
当video memory申请成功后,调用IDirectDrawSurface7::Lock获得video memory的用户地址,将保存在系统内存中的数据复制到video memory中。图形芯片就能够访问纹理数据了。
3.5 wglMakeCurrent(NULL, NULL)
当应用程序调用wglMakeCurrent(NULL),OpenGL32.dll将调用ICD Driver中的DrvReleaseContext使得所有的OpenGL API都成为空操作,不会产生任何结果。
3.6 SwapBuffer的实现
如果是窗口程序,ICD Driver 的DrvSwapBuffers将调用IDirectDrawSurface7::Blt实现从后台缓冲区到前台缓冲区的内容复制。
如果是全屏幕程序,DrvSwapBuffers将调用IDirectDrawSurface7::flip实现快速的前后缓冲区切换。
一些驱动程序使用IDirect3DDevice8::Present或者IDirect3DDevice9::Present实现缓冲区内容的切换。
3.7 wglDeleteContext
wglDeleteContext将调用DrvDeleteCOntext,它将释放所有申请的系统内存和显示内存(video memory),以及纹理,并且删除硬件OpenGL context和软件OpenGL context。而且所有的OpenGL API将不可用。
4 OpenGL ICD Driver 评价
目前OpenGL ICD Driver都相当成熟,基本上都是沿用SGI提供的参考框架结构。大家可以参考下面的链接获取SGI的OpenGL driver实现:
http://oss.sgi.com/projects/ogl-sample/
目前OpenGL做得最好的是3dlabs,它能够支持OpenGL 2.0。
ATI 和 nvidia都支持 OpenGL 1.4,不过ATI支持更多的OpenGL extension。
其他如SIS, XGI, S3支持的API比nvidia要少一些。
Intel等集成显示卡的驱动自然很差了,硬件能力为OpenGL 1.2或者OpenGL 1.3.
5 Linux的OpenGL Driver体系结构
Linux下为DRI(Direct Render Infrastrcture),有兴趣可以参考它的网站;
http://dri.sourceforge.net/cgi-bin/moin.cgi
____________________________
本博客文章主要供博主学习交流用,所有描述、代码无法保证准确性,如有问题可以留言共同讨论。