由于OpenGL被设计成独立于硬件,独立于窗口系统,因此OpenGL的用户不需要考虑如何对计算机图形设备中的显示面(display surfaces)进行初始化和管理,这些工作交由操作系统帮助完成。虽然如此,任何想要使用好OpenGL的开发人员,都应该理解在Windows平台下(如果你的程序运行于Windows平台下的话),一副三维图形在显示面上呈现的具体原理。正所谓“连路都不会走,跑是必然要摔跟头的”,所以沉下心来,一一搬开你路上的石头。
这里主要讨论像素格式和渲染设备描述表(RC)的管理原理。能成功管理这些对象,就建立了硬件与图形接口之间的桥梁。在Windows平台下提供有两种机制来完成硬件与OpenGL之间的接连——像素格式操作API和WGL API。其中后者用于管理RC。好,下面一一介绍。
2.软件渲染模式与硬件加速模式
在Windows平台上,OpenGL驱动可能有三种模式:纯软件、MCD和ICD。
*** 纯软件模式是由微软提供一个OpenGL的软件实现,所有渲染操作均由CPU完成,速度很慢。如果安装系统时使用Windows自带的显卡驱动程序,那么OpenGL程序就会运行在软件模式下。而且由于微软有自己的Direct3D,所以对OpenGL的支持很消极,它的OpenGL纯软件实现只支持OpenGL1.1,而目前OpenGL的最新版本为 1.4。
*** MCD(Mini Client Driver)是早期微软在Windows NT上支持OpenGL时,为了简化驱动开发时使用的一个模型。MCD虽然可以简化驱动开发,但是功能限制太大,现在市面上的3D加速卡均支持硬件变换和光照,MCD却不能利用这一特性,看上去MCD已经没有存在的价值。
*** ICD(Installable Client Driver)是一个完整的OpenGL驱动模型,比MCD复杂得多。硬件厂商要实现完整的OpenGL渲染管线,如变换、光照、光栅化等,因此只要硬件支持,ICD可以硬件加速整个OpenGL渲染管线。我们通常说的OpenGL硬件加速就是指的通过ICD模型获得的硬件加速,而现在硬件厂商提供的OpenGL驱动程序也都是依照ICD模型开发的。(主要硬件厂商的ICD已经可以支持OpenGL的最新版1.4)
Windows怎么实现OpenGL硬件加速呢?OpenGL32.dll是微软的OpenGL 1.1纯软件实现,我们的程序都要动态链接到这个dll。如果安装3D芯片厂商的驱动程序,会将一个不同名字的dll放到Windows系统目录下,比如在Windows 2000下安装nVIDIA GeForce2 MX的驱动程序,会在系统目录下放置一个nvoglnt.dll(这就是nVIDIA的OpenGL驱动),并在注册表中登记nvoglnt.dll,让 Windows知道硬件加速OpenGL驱动的名字,以后运行OpenGL程序,OpenGL32.dll就会把OpenGL调用直接转到 nvoglnt.dll进行处理。nvoglnt.dll则将OpenGL和 WGL指令打包发送给3D芯片厂商的驱动程序,这些驱动程序与支持绘图渲染的基础函数库联接,这样以来,OpenGL程序可以最大限度地利用显卡对OpenGL渲染的支持,从而极大地提高渲染速度。
在Windows平台上,一个OpenGL程序是否使用硬件加速由三个因素决定,这三个因素缺一不可,否则程序都会运行于纯软件模式:
*** 是否有一块3D加速卡
*** 是否安装了显卡厂商提供的最新的驱动程序,Windows自带的显卡驱动程序并不会提供OpenGL硬件加速能力
*** 指定的像素格式是否被显卡硬件所支持
3.像素格式管理
图形的整体显示由单个的像素点所组成。每个像素在内存中都分配了一定字节数的内存空间。用于保存所有像素信息的内存就是缓存。缓存可以有多种形式,分别用于保存不同的信息,比如深度缓存保存每个像素的深度值等。计算机屏幕上所有像素的字节的某一位组成的矩阵是一个位面(bitplane),系统中所有的缓存统称为帧缓存。
帧缓存有四种,一种是颜色缓存,其内包含了该像素的颜色信息,数据可以是颜色索引值(color index),也可以是颜色的RGBA值;一种是深度缓存,其内包含了每个像素的深度信息,如果两个像素占用同一个位置,那么较低深度值的像素用覆盖较高深度值的像素;一种是模板缓存,利用它可以限制屏幕上绘图的区域;最后一种是累积缓存,它主要用于保存各像素的RGBA值,用于反走样等复杂的效果。
在Windows对OpenGL的软件实现中,包含了这些缓存区的设置。软件实现支持单缓存和双缓存模式,支持深度缓存、模板缓存和积累缓存,但层级缓存还不支持。为了有效地利用这些缓存,必须设置好像素格式。每个OpenGL程序窗口都有一套像素格式,下面讨论结构、功能和使用方法。
3.1像素格式结构体
PIXELFORMATDESCRIPTOR是像素格式结构体,用以描述Windows平台下的像素格式的各类属性。某些硬件厂商可能会为加强OpenGL的渲染能力而支持某此软件加速并不支持的像素格式属性。结构体定义如下:
typedef struct tagPIXELFORMATDESCRIPTOR
{
WORD nSize; //定义数据结构体的大小,可以这样给值:pfd.nsize = sizeof(PIXELFORMATDESCRIPTOR)
WORD nVersion; //直接设置为1
DWORD dwFlags; //一系列位标识符,可以相互取或联接,具体有哪些功能标识符,查看MSDN
BYTE iPixelType; //定义像素数据的类型,是索引值(PFD_TYPE_COLORINDEX)还是RGBA值(PFD_TYPE_RGBA)
BYTE cColorBits; //每个颜色缓存中的位面数,如果是RGBA模式,除去alpha位面后的位面数就是该值
BYTE cRedBits; //颜色缓存中,红色信息所占用的位面数
BYTE cRedShift; //红色位面数在缓存区中的移位数,即移多少位可以找到红色的RGB信息
BYTE cGreenBits; //颜色缓存中,绿色信息所占用的位面数
BYTE cGreenShift; //绿色位面数在缓存区中的移位数,即移多少位可以找到绿色的RGB信息
BYTE cBlueBits; //颜色缓存中,蓝色信息所占用的位面数
BYTE cBlueShift; //蓝色位面数在缓存区中的移位数,即移多少位可以找到蓝色的RGB信息
BYTE cAlphaBits; //软件加速不支持, 颜色缓存中,alpha信息的位面数
BYTE cAlphaShift; //软件加速不支持, 颜色缓存中,alpha信息移位数
BYTE cAccumBits; //积累缓存中的总位面数
BYTE cAccumRedBits; //积累缓存中的红色信息位面数
BYTE cAccumGreenBits; //积累缓存中的绿色信息位面数
BYTE cAccumBlueBits; //积累缓存中的蓝色信息位面数
BYTE cAccumAlphaBits; //积累缓存中的alpha信息的位面数
BYTE cDepthBits; //深度缓存保存的深度值
BYTE cStencilBits; //模板缓存中保存的深度值
BYTE cAuxBuffers; //软件加速并不使用该项,辅助缓存区的数目
BYTE iLayerType; //1.0版本的OpenGL只能置为PFD_MAIN_PLANE
BYTE bReserved; //必须置为0
DWORD dwLayerMask;
DDWOR dwVisibleMask;
DWORD dwDamageMask;
} PIXELFORMATDESCRIPTOR;
;
其中以每一个像素的颜色缓存区为例子说明一下各参数的意义,如下图所示。
图:像素格式结构体中每一像素的颜色缓存区(RGBA模式)
3.2像素格式的种类
Windows对OpenGL的软件实现中支持24种不同的像素格式,虽然每一种都由唯一的标识(1-24)所区别,但并非这么简单。某一种像素格式,主要还是由其内所具有的属性来归类,如下图:
图:像素格式的主要属性
由上面的图中可以看出,对一个像素格式进行归类,主要看它的BPP,即每个像素的字节位数。这里支持5种位面组成:32BPP、24BPP、16BPP、8BPP和4BPP。如果按照这种分类方法,24种定义的像素格式中,有8种是专门为显示硬件所设计的,显示硬件驱动程序定义了每个像素应该具有的字节位数,这些像素格式被称为是与硬件相匹配的“本地像素格式”(native format),余下的16种像素格式叫作“非本地像素格式”,这些像素格式支持对位图的操作。
还可以按照像素数据类型来分,RGBA和颜色索引值。然后可以按单缓存或是双缓存分类,最后可以按照深度缓存的深度分类(32或者16)。这样算起来,像素格式的各类不下40种,但是16种非本地像素格式被限制使用,因为它们对位图的双缓存区不起作用。各种像素格式的各类,具体可以参见MSDN。
3.3列举像素格式
像素格式各类比较多,开发者应该为自己的应用程序精心挑选一款适合自己具体情况的像素格式。可以写一个程序来列举所有的像素格式。代码如下:
void CPixForm::OnClickedLastPfd()
{
COpenGL gl;
PIXELFORMATDESCRIPTOR pfd;
//
//得到视类窗口的句柄.
//
HWND hwndview = GetViewHwnd();
//
//得到与视类窗口相关联的DC句柄
//
HDC hdc = ::GetDC(hwndview);
int nID = (m_nNextID > 1) ? m_nNextID-- : 1;
//
//得到像素格式的具体内容,如果不可用,那么查看下一个,并更新对话框内容,否则什么都不做
//
if (gl.DescribePixelFormat(hdc, nID, sizeof(PIXELFORMATDESCRIPTOR), &pfd))
UpdateDlg(&pfd);
//
//释放DC
//
::ReleaseDC(hwndview, hdc);
}
3.4管理像素格式的函数
有4个函数用于像素格式的管理。它们都是Win32函数。它们分别是:
ChoosePixelFormat
SetPixelFormat
GetPixelFormat
DescribePixelFormat
使用这些函数的一般方法由下图给出:
图:像素格式管理函数的使用
一个应用程序可以从上图中的最上面的方块(也就是PIXELFORMATDESCRIPTOR结构体中)知道一些基本的信息,比如说程序应用使用双缓存、应该对窗口进行OpenGL渲染、可以支持GDI等等。应用程序要么调用ChoosePixelFormat函数,要么调用它自定义的像素格式匹配函数。其中前者将开发者要求的像素格式与软件或者硬件支持的像素格式进行比较,并返回最匹配的某一款像素格式,下面的步骤具体描述了这种匹配的过程:
*** 首先,ChoosePixelFormat( )函数查找那些满足开发者定义的像素格式的属性,如:
*** PFD_DRAW_TO_WINDOW
*** PFD_DRAW_TO_BITMAP
*** PFD_SUPPORT_GDI
*** PFD_SUPPORT_OPENGL
*** PFD_TYPE_RGBA
*** PFD_TYPE_COLORINDEX
*** PFD_DOUBLEBUFFER
*** PFD_STEREO
*** 然后,函数尝试与下面的属性值进行匹配:
*** cColorBits
*** cAlphaBits
*** cAccumBits
*** cDepthBits
*** cStencilBits
*** cAuxBuffers
*** iLayerType
*** 最后,在返回的可匹配的像素格式中,如果既有硬件支持的又有软件支持的,函数会优先选择前者作为最佳结果返回给开发者。
一旦你有了一款合适的像素格式,就调用SetPixelFormat( )函数,将窗口的像素格式设为选择结果。一旦窗口的像素格式设置成功,它就不能再被修改了。
3.5硬件支持的像素格式识别
要识别一款像素格式是否被硬件支持,可以通过下面的代码实现,它主要是利用像素格式结构体中的dwFlags域来查看像素格式的相关信息。
BOOL COpenGL::IsDeviceIndex(HDC hdc, int idx)
{
ASSERT (hdc);
ASSERT (idx > 0);
BOOL bRet = FALSE;
PIXELFORMATDESCRIPTOR pfd;
int ipfdmax = DescribePixelFormat(hdc, idx, sizeof(PIXELFORMATDESCRIPTOR),
&pfd);
if (!(pfd.dwFlags & PFD_GENERIC_FORMAT))
bRet = TRUE;
return (bRet);
}
如果PFD_GENERIC_FORMAT位被置1,那么像素格式是软件支持。否则为硬件支持的。那么查看一款像素格式是否为本地像素格式,也可以用类似的方法完成:
BOOL COpenGL::IsNativeIndex(HDC hdc, int idx)
{
ASSERT (hdc);
ASSERT (idx > 0);
BOOL bRet = FALSE;
PIXELFORMATDESCRIPTOR pfd;
int ipfdmax = DescribePixelFormat(hdc, idx, sizeof(PIXELFORMATDESCRIPTOR),
&pfd);
if (pfd.dwFlags & PFD_DRAW_TO_WINDOW)
bRet = TRUE;
return (bRet);
}
如果PFD_DRAW_TO_WINDOW被置1,那么像素格式就是本地像素格式。否则,它是非本地像素格式,于是可用以支持位图操作。
4.RC管理
4.1OpenGL与DC
在使用OpenGL与DC之前,有以下注意事项:
*** 窗口的像素格式被设置后,就不能再修改;
*** DC可用以创建RC,它可以被释放或者删除,所有的DC依次被收回或者创建的时候,必须有正确的像素格式索引号与之对应。
为了取得当前使用的像素格式的索引号,可以使用GetPixelFormat( )函数,下面的代码显示了如何应用这个函数:
int COpenGL::GetCurPFDIndex()
{
int icuridx = GetPixelFormat(wglGetCurrentDC());
return (icuridx);
}
得到的icuridx可以再传入DescribePixelFormat( )函数,以便取得更多的信息。
4.2 OpenGL的RC
在使用RC之前,也有几点注意事项:
*** 在创建RC之前,必须设置好像素格式
*** 在调用任何OpenGL命令之前,RC必须与DC相关联
*** RC与DC相关联期间,DC不得被释放或者删除(除非与该DC对应的窗口类的风格为CS_OWNDC)
有5个函数用以管理RC:
wglCreateContext
wglMakeCurrent
wglGetCurrentContext
wglGetCurrentDC
wglDeleteContext
这些函数都很重要,但是你格外注意一下wglMakeCurrent( )函数,该函数能让所有的绘图在DC上完成。通常,应用程序调用wglCreateContext(),然后将调用wglMakeCurrent()将RC与绘图面相关联,然后OpenGL可以绘图,然后仍然用wglMakeCurrent()将RC与绘图面分离,最后RC由wglDeleteContext()删除。
4.3两种RC的管理机制
既然DC(包括像素格式)与RC如此紧密地联系,那么如何将它们合理地利用并管理呢?首先,用DC创建一个RC,OpenGL利用这个RC在DC上,最终在绘图面上进行绘制。有两种方法利用DC。如下图所示,DC在程序初始化时创建,在程序关闭时释放。这种方法有些让人不安,因为我们并没有在同一个作用域内完成DC的创建和释放。其实这种作法是可行的,没有错误。
图:RC的管理机制一
另一种方法则比较传统,如下图所示。这种方法里面,RC与两个DC进行了关联,同时要调用两次wglMakeCurrent( )函数,这个函数是相当耗时的,所以这种方法在实时渲染和动画设计中,不推崇使用。
图:RC管理机制二
5.总结
Windows平台为OpenGL提供了软件实现,如果没有硬件驱动支持,所有的像素格式和RC的管理都交由GDI处理。如果有硬件驱动支持,那么硬件设备会分担很多管理任务,从而使OpenGL渲染运行地更快。在OpenGL可以绘图之前,窗口、位图或者硬件的像素格式必须初始化。然后创建RC,最后由程序开发人员在此基础上尽情展示自己精彩的程序
参考文献
[1] 刘梢月 图形处理OpenGL硬件加速 http://blog.csdn.net/liusaoyue/archive/2009/12/29/5093949.aspx
[2] Dennis Crain Windows NT OpenGL: Getting Started Microsoft Developer NetWork Technology Group MSDN-2001