3. 设定Direct 3D应用程序中的DirectDraw
DirectDraw是Microsoft DirectX API的成员之一,用来让您直接控制显示装置的特性,如下列例子:
- 主要绘图页(Primary surface),也就是您在屏幕上看到的东西所对应的 内存。
- 屏幕外绘图页(Off-screen surfaces),是用来存放可以转移到可视区域的图像(包括在3D程序中的贴图)。
- 硬件迭合支持(Hardware overlay support),可以让2D图像显示在显示区上方而不会影响主要绘图页的内容。
- 深度缓冲区(Depth buffers),用来储存绘制3D景物的相关深度信息。
- 图块搬移硬件(Hardware blitter),用来作图块搬移(blitting),也就是将2D数据从一个地方复制到另一个地方的过程。
- 切页支持( Flipping-surface support,也称为page flipping ),用来在主要的可视显示绘图页和内存(后缓冲区,back buffer)中的多个检视(views)间切换,以避免屏幕画面切割(tearing,这个会在后面的章节中说明)或是在画面间闪烁。
- 立体模式支持,意谓左眼看到一个影像,而右眼看到另一种不同的影像。
- 在窗口或全屏幕模式下的裁切(clipping)。
整体来说,DirectDraw采用了一种与装置无关的作法来存取显示装置,同时也是可处理所有2D绘图指令的DirectX API。它也是Direct3D的基础,所以即使是「纯3D」的程序也需要用到DirectDraw。
DirectDraw会管理它所建立的所有对象并且记录已(未)配置的资源。它也会控制下列特性:
- 预设的色彩键(color key),这是用来建立透明区,就如同电影中将屏幕变蓝一般。
- 硬件显示模式,包括系统执行中的分辨率、色彩深度及更新频率。
- 预设调色盘(可用的颜色),是当主要绘图页是在每个像素8位的模式下时。
DirectDraw允许您列举基本硬件的功能和使用可支持的硬件加速特性。
DirectDraw可和许多种显示硬件搭配,包括标准的SVGA显示器、头戴显示屏,以及新的先进系统来处理裁切,以及RGB以外的色彩格式与延伸(stretching)。和其它的DirectX API一样,DirectDraw会尽可能地来仿真任何系统硬件不提供的功能。
DirectDraw和Direct3D列举了所有目标平台上的硬件特性来决定是否支持各种功能的硬件加速。您应该在开发程序时只要求那些能让程序有效执行所必须的功能就好,但是可以检查或利用其它可用的特性。举例来说,只有新硬件才支持单次多贴图(single-pass multitexturing),所以在您的程序中使用这个功能会让使用旧系统的使用者无法适用;另一方面,拥有新且强大硬件的使用者就会希望他们购买的游戏中包含有这种进阶特性的支持。
DirectDraw和多显示屏
Microsoft Windows 98导入在一台计算机中支持多个显示器和显示装置的功能。这个特性有时称为多屏幕(multimon),它允许Windows利用到二个或二个以上显示器的显示区域作为同一个逻辑桌面。在一个多屏幕系统中,您可以将某个窗口从一个显示器移到另一个,或者藉由DirectDraw让程序用到多显示装置。DirectX的一个强大特性是,只要您在系统上安装了一片以上的显示卡,您可以同时建立一个DirectDraw对象的多个实例。即使您的计算机并不支持多显示器的操作系统,例如Microsoft Windows 95或Microsoft Windows NT 4(甚至更早的版本),您还是可以针对每种需要的显示装置建立一个DirectDraw对象。在用DirectDraw列举并建立显示装置时,DirectDraw装置(有时称为DirectDraw驱动程序)就是指那些可支持DirectDraw的显示装置。
当您建立一个预设的DirectDraw对象(它的GUID指针是NULL)的实例时, Windows用到的主要显示装置和DirectX用到的是一样的。您可以用辅助显示装置(如果有的话)的GUID来建立第二个DirectDraw对象,来寻址第二个显示装置。您可以用DirectDrawEnumerateEx函式来要求这个GUID,后面的章节会提到。
在已安装的显示卡中,可能会有一个或多个能支持硬件3D加速。您可以选择是否要在某个DirectDraw装置上使用3D加速,作法是列举它所有的Direct3D装置并从中选择一个。所谓Direct3D装置(会在第四章中进一步探讨),就是一个可以储存成像(rendering)状态信息,并且用来绘制3D景物的Direct3D物件。它和DirectDraw装置的不同点在于Direct3D装置并不真正地代表一个实体装置-它可能是一个软件的绘制器(renderer)或一个硬件加速绘制器。
图3-1显示一个有多个DirectDraw装置的范例系统, 而且在每一个DirectDraw装置上存在着多个Direct3D装置。
图3-1 接到多台屏幕的多片显示卡 |
在执行任何的3D绘图之前,您必须做二个选择:第一,用哪一个显示装置?第二,针对这个显示装置,要用到哪一个3D绘制器?一个Direct3D程序通常会选择合理的预设显示装置并且执行绘图,但是也会允许使用者如果不喜欢默认值时,选择其它的显示装置并且执行绘图。在读完本章及下一章后,您就会学到如何做到这些事。
设定DirectDraw
现在您已经大致看过了DirectDraw,让我们进入本章的精华:针对我们的Direct3D立即模式应用程序设定DirectDraw。(虽然本章主要的焦点集中在DirectDraw上,您还是可以看到一些Direct3D的程序代码,因为3D程序在完全启动DirectDraw之前,需要先取得一些Direct3D的信息)。这个过程包含了以下步骤:
- 建立一个结构来记录所列举的Direct3D装置。
- 列举所有已安装在系统上可用的Direct3D装置,然后在每个DirectDraw装置上执行步骤3到5:
- 建立一个DirectDraw对象。
- 列举显示模式。
- 列举Direct3D装置。
- 挑选一个显示装置和Direct3D装置。
- 将DirectDraw初始化,包括设定共享等级(cooperative sevel),建立前后缓冲区并且连结裁切器。
在下面的段落里,我会描述每个步骤并且说明如何完成。我们会看一些含括以上步骤的Direct3D程序框架中摘录的程序代码。d3denum.cpp包含了步骤1至6的程序,而d3dframe.cpp包含了步骤7的程序。d3dapp.cpp的程序会去呼叫这二个程序的内容来导引整个流程。在本章的结尾,您可以看到如何建立一个Direct3D应用程序的基本架构。您也可以重新利用这些程序代码作为未来自行开发Direct3D程序之用,因此请牢记步骤的顺序和其中包含的基本工作。
建立一个结构来储存所列举的Direct3D装置
在设定RoadRage程序的DirectDraw和Direct3D内容之前,第一步要先定义一个结构,用来记录我们在系统上列举出的所有Direct3D装置的相关信息。这种在自己定义的数据结构中记录信息的方式,使我们可以轻易地用一个对话盒告诉使用者,有哪些装置和模式可用,并且在其间切换。我们所用的结构记录了Direct3D装置信息、DirectDraw装置信息和DirectDraw模式信息。d3denum.h中所定义的D3DEnum_DeviceInfo结构,就能达到这个目的,定义如下:
//-------------------------------------------------------------------
//名称:D3DEnum_DeviceInfo结构
//说明:储存所列举出
// Direct3D装置的信息结构
//-------------------------------------------------------------------
struct D3DEnum_DeviceInfo
{
//Direct3D装置信息
CHAR strDesc [40];
GUID*pDeviceGUID;
D3DDEVICEDESC7 ddDeviceDesc;
BOOL bHardware;
//DirectDraw驱动程序信息
GUID*pDriverGUID;
DDCAPS ddDriverCaps;
DDCAPS ddHELCaps;
//DirectDraw模式信息
DDSURFACEDESC2 ddsdFullscreenMode;
BOOL bWindowed;
BOOL bStereo;
//供内部使用(应用程序不应使用这些成员)
GUID guidDevice;
GUID guidDriver;
DDSURFACEDESC2*pddsdModes;
DWORD dwNumModes;
DWORD dwCurrentMode;
BOOL bDesktopCompatible;
BOOL bStereoCompatible;
};
我们会用本章所提到的程序将系统硬件特性相关的信息填入这个结构中。
列举所有DirectDraw装置
在DirectX中,列举功能(如DirectDrawEnumerateEx)的作用是针对清单上的每个项目启动一个回传函式(callback function),以列出一组特别的项目,回传函式会决定目前清单上的项目是否符合欲建立对象的限制条件。在RoadRage程序中,我们会去建立所有满足我们程序限制条件的Direct3D装置的名单。之前提过,程序需要去列举DirectDraw装置,以及针对每个DirectDraw装置列举可用的Direct3D装置。
透过传递DDENUM_ATTACHEDSECONDARYDEVICES、DDENUM_DETACHEDSECONDARYDEVICES和DDENUM_NON-DISPLAYDEVICES等旗标到DirectDrawEnumerateEx程序的作法,我们可以取得已被列举的装置(可再加到可用装置的名单上)。
会用到这些旗标的DirectDrawEnumerateEx函式的定义如下:
HRESULT WINAPI DirectDrawEnumerateEx(
LPDDENUMCALLBACK lpCallback,
LPVOIDl pContext,
DWORD dwFlags
);
参数 |
说明 |
lpCallback |
回传函式的地址。系统会对每个已安装在目标系统上的有效DirectDraw HAL描述呼叫这个函式。 |
lpContext |
程序定义关联的地址。每一次呼叫时都会传给列举回传函式。 |
dwFlags |
指定列举范围的旗标。如果值为0,函式只会列举主要显示装置。这个参数也可以是以下旗标的组合: DDENUM_ATTACHEDSECONDARYDEVICES 列举主要装置以及其它连结到桌面的显示装置 DDENUM_DETACHEDSECONDARYDEVICES 列举主要装置以及其它没有连结到桌面的显示装置 DDENUM_NONDISPLAYDEVICES 列举主要装置以及其它非显示装置,如没有2D功能的3D加速卡 |
在Direct3D程序框架中,列举作业会以d3denum.cpp中的函式D3DEnum_EnumerateDevices开始。这个程序的程序代码如下:
//-------------------------------------------------------------------
//名称:D3DEnum_EnumerateDevices
//说明:列举所有的驱动程序、装置和模式。.
// 每一个装置都会呼叫回传函式,以确认
// 装置支持了应用程序必须的功能集合。
//-------------------------------------------------------------------
HRESULT D3DEnum_EnumerateDevices(HRESULT
(*AppConfirmFn)(DDCAPS*,D3DDEVICEDESC7*))
{
//储存装置列举回传函式
g_fnAppConfirmFn =AppConfirmFn;
//列举所有的驱动程序、装置和模式
DirectDrawEnumerateEx(DriverEnumCallback,NULL,
DDENUM_ATTACHEDSECONDARYDEVICES |
DDENUM_DETACHEDSECONDARYDEVICES|
DDENUM_NONDISPLAYDEVICES);
//确定已列举了所有装置
if(0 ==g_dwNumDevicesEnumerated)
{
DEBUG_MSG(_T("No devices and/or modes were enumerated!"));
return D3DENUMERR_ENUMERATIONFAILED;
}
if(0 ==g_dwNumDevices)
{
DEBUG_MSG(_T("No enumerated devices were accepted!"));
DEBUG_MSG(_T("Try enabling the D3D Reference Rasterizer."));
return D3DENUMERR_SUGGESTREFRAST;
}
return S_OK;
}
传给D3DEnum_EnumerateDevices的参数AppConFirmFn是一个函式指标,用来筛选不适用于程序的Direct3D装置,关于这部分会在本章稍后作详细说明。
DirectDrawEnumerateEx会用一个函式指标作它的第一个参数。这个指针会指到应用程序定义的回传函式。在本程序中,它则指到DriverEnumCallback程序。这个函式的形式如下:
BOOL WINPAPI DDEnumCallbackEx(
GUID FAR *lpGUID,
LPSTR lpDriverDescription,
LPSTR lpDriverName,
LPVOID lpContext
HMONITOR hm
);
参数 |
说明 |
lpGUID |
装置GUID的地址 |
lpDriverDescription |
装置描述的地址 |
lpDriverName |
装置名称的地址 |
lpContext |
传给DirectDrawEnumerateEx的使用者定义数据关联的地址 |
hm |
和列举的DirectDraw对象相关的显示屏处理。当列举的DirectDraw对象指的是主要装置,而非显示装置(例如没有2D功能的3D加速卡),以及没有和桌面连接的装置时,这个参数值是NULL。 |
d3denum.cpp中的DriverEnumCallback函式定义如下:
//-------------------------------------------------------------------
//名称:DriverEnumCallback
//说明:用来列举驱动程序的回传函式
//-------------------------------------------------------------------
static BOOL WINAPI DriverEnumCallback(GUID*pGUID,TCHAR*strDesc, TCHAR*strName,VOID*, HMONITOR)
{
D3DEnum_DeviceInfo d3dDeviceInfo;
LPDIRECTDRAW7 pDD;
LPDIRECT3D7 pD3D;
HRESULT hr;
//
//步骤1
//用GUID建立DirectDraw对象。
//
hr =DirectDrawCreateEx(pGUID,(VOID**)&pDD,IID_IDirectDraw7,NULL );
if(FAILED(hr))
{
DEBUG_MSG(_T("Can't create DDraw during enumeration!"));
return D3DENUMRET_OK;
}
//
//步骤2
//建立Direct3D对象以列举d3d装置
//
hr =pDD->QueryInterface(IID_IDirect3D7,(VOID**)&pD3D);
if(FAILED(hr))
{
pDD->Release();
DEBUG_MSG(_T("Can't query IDirect3D7 during enumeration!"));
return D3DENUMRET_OK;
}
//
//步骤3
//
//将数据复制到装置信息结构中。
ZeroMemory(&d3dDeviceInfo,sizeof(d3dDeviceInfo));
lstrcpyn(d3dDeviceInfo.strDesc,strDesc,39);
d3dDeviceInfo.ddDriverCaps.dwSize =sizeof(DDCAPS);
d3dDeviceInfo.ddHELCaps.dwSize =sizeof(DDCAPS);
pDD->GetCaps(&d3dDeviceInfo.ddDriverCaps,
&d3dDeviceInfo.ddHELCaps);
if(pGUID)
{
d3dDeviceInfo.guidDriver =(*pGUID);
d3dDeviceInfo.pDriverGUID =&d3dDeviceInfo.guidDriver;
}
strcpy(D3Ddevicename,d3dDeviceInfo.strDesc);
//记录装置可否绘制到桌面窗口中。
if(d3dDeviceInfo.ddDriverCaps.dwCaps2
&DDCAPS2_CANRENDERWINDOWED)
if(NULL ==d3dDeviceInfo.pDriverGUID)
d3dDeviceInfo.bDesktopCompatible =TRUE;
//
//