DirectX是为Visual C++的用户准备的,因此要编制DirectDraw游戏程序,最好对VC要有一定的了解。不愿意使用VC的用户也可以利用Arakelian Soft公司开发的专门针对Visual Basic5.0用户的ActiveX控件DirectStudio98或Tegosoft公司的TegoSoft ActiveX for Visual Basic。不过,如果想充分发挥DirectX的性能,并且希望保持程序的兼容,那么最好还是使用Visual C++。 为了叙述方便,假定已经安装了DirectX5.0 SDK 和Visual C++ 5.0,其目录分别是C:\DX5SDK和C:\Program Files\DevStudio。如果你使用了另一种编译器或安装到了其它目录下,必须将下面的例子做适当的修改才能运行。有人安装了DirectX SDK后却不知怎样使用,因为它是基于Visual C++的,却没有一个界面友好的集成开发环境,因此必须对Visual C++进行适当的配制。
一 配置DirectX SDK 1.1 配置Microsoft Developer Studio 为了编译DirectX SDK提供的例子,需要打开一个新的project workspace,插入适当的文件,设置环境变量使得编译器能够找到需要的链接库和包含文件,下面描述了设置的全部过程。启动Microsoft Developer Studio,安装下述步骤创建工程: .在File菜单,选择New; .在New对话框中选择Project中的Win32 Application,在Project Name输入DDEX1 .在Location文本框输入放置工程文件的位置,点OK按钮 .一个新的DDEX1 Classes文件夹就出现在workspace窗口的左边了。 创建了工程后,需要使用如下步骤向工程插入适当的文件: .在Project菜单选择Add toProject|Files .浏览到C:\DX5SDK\SDK\SAMPLES\DDEX1 目录,选择所有的文件 .选择OK,该目录下的DDEX1.CPP、DDEX1.RC、RESOURCE.H就加入到工程了。 然后设置包含文件的路径: .在Tools菜单,选择Options,就弹出Options对话框 .选择Directories ,在Show Directories For列表框选择Include files .在Directories:列表框双击列表底部的空白行,输入C:\DX5SDK\SDK\INC. .同样再加入另一个路径C:\DX5SDK\SDK\SAMPLES\MISC .选择OK按钮, 设置链接库目录: .在Show Directories For列表框选择Library files .在Directories:列表框双击底部空白行,输入C:\DX5SDK\SDK\LIB. .单击OK按钮。 最后设置建立应用程序时链接的模块: .在Project菜单单击Settings. .选择Link .在Category下拉框选择General. .在Object/Library模块列表框加入Ddraw.lib和Winmm.lib. .单击OK. 1.2 配制NMAKE路径 有时候命令行的方式比集成环境更加方便,所以许多有经验的程序员更愿意用命令行的方式来建立应用程序。下面是包含文件和链接库模块的路径: @echo off set PATH=C:\Program Files\DevStudio\SharedIDE\Bin; C:\Program Files\DevStudio\Vc\Bin;%PATH%set INCLUDE=C:\Program Files\DevStudio\Vc\include; C:\Program Files\DevStudio\Vc\Mfc\include;C:\DX5SDK\SDK\INC;%INCLUDE%set LIB= C:\Program Files\DevStudio\SharedIDE\Vc\lib; C:\Program Files\DevStudio\Vc\Mfc\lib; C:\DX5SDK\SDK\LIB;%LIB%set INIT= C:\Program Files\DevStudio;%INIT%将上述内容加入Autoexec.bat。在例子的目录下输入 NMAKE 将会在当前目录下创建一个DEBUG目录,并将生成的可执行文件放在该目录下。 为了在学习的过程中熟悉DirectX SDK,我们将按照DirectX SDK提供的范例程序的顺序由浅入深,循序渐进。 1.3 为Borland C++5.0配置DirectX SDK 尽管DirectX 5 SDK是主要为Visual C++用户准备的,但Microsoft并未忘记众多的BorlandC++用户,所以在DirectX SDK中也提供了DirectX的Borland C++库。不过,可能出于竞争的缘故(猜测而已),安装后的DirectX SDK中并没有Borland C++库。这就需要用户自己来处理这一恼人的问题了。我们知道,DirectX 5 SDK是以一个IDX5SDK.EXE发布的,运行IDX5SDK后,它先将压缩的文件全部解压到某个目录下(如D:\DX5SDK),然后再运行该目录下的SETUP.EXE安装DirectX SDK(假设目录为C:\DX5SDK)。实际上,在解压后的目录中包含了一个D:\DX5SDK\SDK\LIB\BORLANDC目录,该目录下就是Borland C++的链接库文件。但在SETUP安装时,安装程序并没有把该目录复制到安装目录中。解决方法很简单,即SETUP安装完成后,再建立一个C:\DX5SDK\SDK\LIB\Borland,将目录D:\DX5SDK\SDK\LIB\Borland下的所有文件都复制到C:\DX5SDK\SDK\LIB\Borland目录下。然后在Borland C++5.0的集成环境中如同配置Visual C++5.0那样配置工程文件。
二 第一个DirectDraw实例 要使用DirectDraw,首先必须创建DirectDraw对象的一个实例来表征计算机上的显示适配卡,然后使用接口方法来处理对象。另外还需要创建一个或多个DirectDrawSurface对象的实例来显示游戏。DDEX1首先创建一个DirectDraw对象,再创建一个主表面(primary surface)和一个后台缓冲区(back buffer),然后在表面之间转换。DDEXx例子都是用C++写成的,如果你使用的是C编译器,必须将代码做适当改动,至少要加入虚表和指向接口方法的指针。 2.1 首先初始化DirectDraw对象 DDEX1 程序在doInit函数包含了DirectDraw 的初始化代码:
// 创建主DirectDraw 对象 ddrval = DirectDrawCreate( NULL, &lpDD, NULL ); if( ddrval == DD_OK ) { // 获取独占模式 ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE| DDSCL_FULLSCREEN ); if(ddrval == DD_OK ) { ddrval = lpDD->SetDisplayMode(640, 480, 8 ); if( ddrval == DD_OK ) { //创建带有一个后台缓冲区的主表面 ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |DDSCAPS_FLIP |DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ); if( ddrval == DD_OK ) { //获取后台缓冲区的指针 ddscaps.dwCaps = DDSCAPS_BACKBUFFER; ddrval=lpDDSPrimary->GetAttachedSurface(&ddscaps,&lpDDSBack); if( ddrval == DD_OK ) { // 画出一些文本 if (lpDDSPrimary->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) ); lpDDSPrimary->ReleaseDC(hdc); } if (lpDDSBack->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) ); lpDDSBack->ReleaseDC(hdc); } // 创建翻转页面的计时器if( SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL ) ) { return TRUE; } } } } } }
wsprintf(buf, "Direct Draw Init Failed (%08lx)\n", ddrval ); ............
下面详细说明初始化DirectDraw 对象的创建和准备表面的每一步骤。
2.2 创建DirectDraw 对象 创建DirectDraw 对象的一个实例,应该用DirectDrawCreate API 函数,也可以用COM中的CoCreateInstance函数。DirectDrawCreate用一个全局统一标志符GUID(Globally Unique IDentifier)来表征显示设备,在大多数情况下GUID为NULL(使用系统的缺省显示设备,既“空设备”);指针指向DirectDraw对象的地址;第三个参数总是NULL(供将来扩展使用)。下述代码表明了如何创建一个DirectDraw对象,并且检验是否成功。
ddrval = DirectDrawCreate( NULL, &lpDD, NULL ); if( ddrval == DD_OK ) { // lpDD is是合法的DirectDraw 对象 } else { // DirectDraw对象不能被创建 }
2.3 设置显示模式 设置DirectDraw 应用程序的显示模式需要两步:首先调用IDirectDraw::SetCooperativeLevel方法来设定该模式下的要求,一旦确定了要求,再用IDirectDraw::SetDisplayMode方法来选择显示分辨率。 在改变显示分辨率之前,还必须通过IDirectDraw::SetCooperativeLevel方法来指定DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN标志。 这样能使游戏程序完全控制显示设备,其它的应用程序不能同时共享显示设备。DDSCL_FULLSCREEN标志表示将程序设为全屏模式。下面的代码显示了如何使用IDirectDraw::SetCooperativeLevel方法:
HRESULT ddrval; LPDIRECTDRAW lpDD; // already created by DirectDrawCreate ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); if( ddrval == DD_OK ) { // 全屏独占方式设置成功 } else { // 调用不成功,但程序仍然能继续运行 }
如果IDirectDraw::SetCooperativeLevel不返回DD_OK,你仍然可以运行该程序,但不是全屏模式,有时可能产生一些无法预料的错误。因此你应该显示一条错误信息,让用户知道发生了什么事,由用户来决定是否继续运行游戏。 使用IDirectDraw::SetCooperativeLevel时,必须向窗口 (HWND)传送一个句柄,让窗口决定何时非正常地终止应用程序。例如,若发生了GP错误或GDI被翻转(flip)到了后台缓冲区,用户就无法访问当前屏幕。为了避免这种情况,DirectDraw有一个后台等待进程,它俘获所有发往该窗口的消息,用这些消息来确定应用程序何时终止。如果创建了新的窗口,必须确定该窗口为活动的,否则,就会有一系列的事件无法继续工作。
2.4 改变显示模式 一旦选择了应用程序的工作模式,就可以使用IDirectDraw::SetDisplayMode方法来改变显示模式,下面的代码将显示模式设置成640x480x256:
HRESULT ddrval; LPDIRECTDRAW lpDD; // already created ddrval = lpDD->SetDisplayMode( 640, 480, 8 ); if( ddrval == DD_OK ) { // 改变模式成功 } else { // 显示模式不能改变 // 系统可能不支持该模式 }
当设定显示模式时,应该确保如果用户的设备不支持更高的分辨率,应用程序应该返回系统支持的标准模式。如果显示示配卡不支持设计的分辨率,IDirectDraw::SetDisplayMode返回一个DDERR_INVALIDMODE错误值。因此,在设置分辨率时,应该先用IDirectDraw::EnumDisplayMode方法检测用户的显示设备的性能。
2.5 创建可翻转表面(Flippable Surface) 设定了显示模式后,必须创建放置应用程序的表面。在DDEX1例中,我们使用IDirectDraw::SetCooperativeLevel方法将程序设成独占全屏模式,然后可以创建翻转表面。如果使用IDirectDraw::SetCooperativeLevel设成DDSCL_NORMAL模式,就只能创建块写方式的表面了。
2.6 定义表面要求 创建可翻转表面的的第一步是在DDSURFACEDESC结构中定义表面的要求。下面的代码描述了结构的定义及创建可翻转表面所需要的标志:
// 创建带有一个后台缓冲区的主表面 ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1;
例中,成员变量dwSize被设为DDSURFACEDESC结构的大小。dwFlags标志指定DDSURFACEDESC结构中哪些域可存放有效信息。 在DDEX1例中,dwFlags指出了需要使用DDSCAPS结构(DDSD_CAPS)并创建一个后台缓冲区 (DDSD_BACKBUFFERCOUNT)。dwCaps指出 DDSCAPS结构会用到的标志,本例中它指定了一个主表面(DDSCAPS_PRIMARYSURFACE),一个翻转表面(DDSCAPS_FLIP)和一个复杂表面(DDSCAPS_COMPLEX)。 最后,程序指定了一个后台缓冲区。后台缓冲区是背景图像和人物将写入的位置,它可以转化为主表面。 本例中,后台缓冲区的数目为1,事实上,只要有足够的显示内存,可以创建任意多个后台缓冲区,一般每1M的显示内存只能用来创建一个后台缓冲区。表面的内存既可以是显示内存,也可以是系统内存。 DirectDraw在使用完了显示内存时(例如在仅有1M的显示内存创建了2个后台缓冲区)会自动使用系统内存。你可以通过将DDSCAPS结构中的dwCaps设定为DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY来指定只使用系统内存或只使用显示内存。如果指定了DDSCAPS_VIDEOMEMORY又没有足够的显示内存来创建表面,IDirectDraw::CreateSurface 将返回一个DDERR_OUTOFVIDEOMEMORY错误。
2.7 创建表面 填完了DDSURFACEDESC结构,就可以使用该结构和lpDD了,lpDD是用DirectDrawCreate 方法创建的DirectDraw 对象的指针,下面的代码显示了这一过程:
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL); if( ddrval == DD_OK ) { // lpDDSPrimary points to new surface } else { // surface was not created return FALSE; }
如果调用成功,IDirectDraw::CreateSurface函数就返回指向主表面的指针lpDDSPrimary。若主表面指针可用,就可以调用IDirectDrawSurface::GetAttachedSurface 方法取得后台缓冲区的指针,如下所示:
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;ddrval = lpDDSPrimary->GetAttachedSurface( &ddcaps, &lpDDSBack); if( ddrval == DD_OK ) { // lpDDSBack points to the back buffer } else { return FALSE; }
如果IDirectDrawSurface::GetAttachedSurface调用成功,通过提供主表面的地址和设置DDSCAPS_BACKBUFFER标志,lpDDSBack 参数就指向后台缓冲区。
2.8 着色表面 创建了主表面和后台缓冲区后,DDEX1使用标准的WindowsGDI 函数将一些文本提交到主表面和后台缓冲区,代码如下:
if (lpDDSPrimary->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg)); lpDDSPrimary->ReleaseDC(hdc); } if (lpDDSBack->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg)); lpDDSBack->ReleaseDC(hdc); }
例中使用了IDirectDrawSurface::GetDC方法来设备上下文的句柄且锁定该表面。如果不想使用要求句柄的Windows函数,还可以使用IDirectDrawSurface::Lock和IDirectDrawSurface::Unlock方法来锁定和解锁后台缓冲区。锁定表面的内存(不管是整个表面还是其中的一部分)能确保你的应用程序和系统不会同时访问这块内存。另外,除非给内存解锁,程序不能翻转表面。本例在锁定表面后使用Windows GDI 函数SetBkColor来设置背景颜色,使用SetTextColor来设置文本颜色,然后使用TextOut将文本输出到表面。当文本写入缓冲区后,例中使用了IDirectDrawSurface::ReleaseDC方法来解锁表面并释放句柄。良好的习惯是,向后台缓冲区写数据完成后,马上调用IDirectDrawSurface::ReleaseDC或IDirectDrawSurface::Unlock。 一般来讲,当向表面写数据时,该表面就是后台缓冲区,然后将缓冲区翻转成主表面显示出来。在DDEX1中,第一次翻转表面之前有一个重要的延迟。于是DDEX1就将数据写入主缓冲区,避免开始显示时有太长的时间间隔。后面将会讲到,DDEX1只在WM_TIMER期间向后台写数据。初始化函数或标题头可能会写入主缓冲区。应该注意的是,一旦使用IDirectDrawSurface::Unlock对表面解锁,指向表面的指针就变成无效,必须再次使用IDirectDrawSurface::Lock方法才能获取该表面内存的有效指针。
2.9 写表面及翻转表面 完成了初始化后,DDEX1开始处理消息循环。在消息循环的过程中,完成锁定后台缓冲区——写入新的文本——解锁后台缓冲区——翻转表面的过程。WM_TIMER包含了写数据和翻转表面的大部分代码。 WM_TIMER消息的前半部分用于向后台缓冲区写数据,“phase”变量决定是写主缓冲区消息还是写后台缓冲区消息。如果phase为1,表示写主缓冲区的消息,然后将phase改变为0;若为0,表示写后台缓冲区的消息,然后将phase改变为1。注意,两种情况中的消息都是写向后台缓冲区。后台缓冲区写入了消息后,使用IDirectDrawSurface::ReleaseDC方法解锁。下面的代码实现了这一点:
case WM_TIMER: // Flip surfaces if( bActive ) { if (lpDDSBack->GetDC(&hdc)== DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); if( phase ) { TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) ); phase = 0; } else { TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) ); phase = 1; } lpDDSBack->ReleaseDC(hdc); }
表面内存解锁后,使用IDirectDrawSurface::Flip方法将后台缓冲区翻转成主表面,代码如下:
while( 1 ) { HRESULT ddrval; ddrval = lpDDSPrimary->Flip( NULL, 0 ); if( ddrval == DD_OK ) { break; } if( ddrval == DDERR_SURFACELOST ) { if(ddrval = lpDDSPrimary-&g>val != DD_OK ) { break; } } if( ddrval != DDERR_WASSTILLDRAWING ) { break; } }
例中,lpDDSPrimary指明了主表面及其后台缓冲区。调用IDirectDrawSurface::Flip方法后,主表面和后表面交换。调用成功后,返回DD_OK,程序终止While循环;如果返回DDERR_SURFACELOST,表明可能是表面丢失,需要用IDirectDrawSurface::Restore方法恢复该表面,若恢复成功,就再一次调用IDirectDrawSurface::Flip方法;如果失败,程序终止While循环并返回一个错误值。另外,如前所述,即使调用IDirectDrawSurface::Flip成功,交换也不是立即完成,它将等到系统中在此之前的表面交换都完成后才进行。例如,前一次的表面翻转还未发生时,IDirectDrawSurface::Flip就返回DERR_WASSTILLDRAWING。本例中,IDirectDrawSurface::Flip继续循环直到返回DD_OK.。
2.10 释放DirectDraw 对象 当按了F12后,DDEX1程序在退出之前先处理WM_DESTROY消息,该消息调用了finiObjects函数,而finiObjects函数包含了所有的Iunknown Release的调用,代码如下:
static void finiObjects( void ) { if( lpDD != NULL ) if( lpDDSPrimary != NULL) { lpDDSPrimary->Release();lpDDSPrimary = NULL; } lpDD->Release(); lpDD = NULL; } } /* finiObjects */
程序检测DirectDraw对象的指针(lpDD)和DirectDrawSurface对象的指针(lpDDSPrimary) 是否等于NULL,本例中显然不为NULL。然后DDEX1调用 IDirectDrawSurface::Release方法将DirectDrawSurface对象的参考值减1,这将会使得其参考值变为0,DirectDrawSurface 对象就被释放了,DirectDrawSurface的指针被设为NULL值,然后撤消。程序又调用IDirectDraw::Release就DirectDraw对象的参考值减1变为0,释放DirectDraw对象及其指针。 上述的DDEX1是DirectDraw最基本的应用,它首先创建DirectDraw对象和DirectDrawSurface对象,创建一个主表面及其后台缓冲区,将文本输出到后台缓冲区,然后转化表面。第二个例子DDEX2扩展了DDEX1的功能,它可以将一个位图文件调入后台缓冲区。第三个例子DDEX3则更进一步,除了一个主表面及后台缓冲区外,还创建了两个屏外表面,将位图调入每一个屏外表面,然后使用IDirectDrawSurface::BltFast方法将一个屏外表面的内容位块传输到后台缓冲区,然后翻转表面并将另一个屏外表面的内容位块传输到后台缓冲区。下面将详细讨论这些功能。
2.11 将位图调入表面 如DDEX1中一样,doInit函数是DDEX2的初始化函数,两者的实质一样,一直到下面的代码:
lpDDPal = DDLoadPalette(lpDD, szBackground); if (lpDDPal == NULL) goto error; ddrval = lpDDSPrimary->SetPalette(lpDDPal); if( ddrval != DD_OK ) goto error; // load a bitmap into the back buffer. ddrval = DDReLoadBitmap(lpDDSBack, szBackground); if( ddrval != DD_OK ) goto error;
代码的第一行从函数DDLoadPalette返回一个值,该函数在C:\DX5SDK\SDK\SAMPLES\MISC中的Ddutil.cpp文件中,因此编译DDEX2时需要将Ddutil.cpp和Ddutil.h加入过程。大部分的DirectDraw程序都需要该文件。在DDEX2中,DDLoadPalette函数从Back.bmp文件中创建一个DirectDrawPalette对象。DDLoadPalette函数首先检查用于创建调色板的文件或资源十分存在,如果不存在,就创建一个缺省调色板。在DDEX2中,它从位图文件中抽取调色板信息并存储在由ape指向的结构,然后如下创建DirectDrawPalette对象:
pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL); return ddpal;
当IDirectDraw::CreatePalette方法返回时,ddpal就指向该DirectDrawPalette对象。ape是指向一个结构的指针,该结构能包含2/4/16/256个线性的实体,实体的数目由IDirectDraw::CreatePalette调用的dwFlags参数决定。本例中,dwFlags设定为DDPCAPS_8BIT,这表示该结构中有256个实体,每个实体有四个字节(红色、绿色、蓝色和标志字节)。
2.12 设置调色板,将位图调入后台缓冲区 创建了调色板之后,可以通过调用IDirectDrawSurface::SetPalette方法将DirectDrawPalette 对象的指针ddpal传送给主表面,代码如下:
ddrval = lpDDSPrimary->SetPalette(lpDDPal); if( ddrval != DD_OK ) // SetPalette failed
调用了IDirectDrawSurface::SetPalette方法之后,DirectDrawPalette对象就和DirectDrawSurface对象挂(hook)在一起了,需要改变调色板时,只需要创建一个新的调色板对其进行设置就可以了。 DirectDrawPalette对象同DirectDrawSurface对象挂在一起后,DDEX2使用以下代码将Back.bmp文件装入后台缓冲区:
// load a bitmap into the back buffer. ddrval = DDReLoadBitmap(lpDDSBack, szBackground); if( ddrval != DD_OK ) // Load failed
DDReLoadBitmap是Ddutil.cpp中的另一个函数,它将位图从文件或资源中调入已经存在的DirectDraw表面。在本例中,它将szBackground指向的Back.bmp装入lpDDSBack指向的后台缓冲区。DDReLoadBitmap调用DDCopyBitmap函数将文件拷贝到后台缓冲区并延展为适当的尺寸。DDCopyBitmap函数将位图拷入内存,使用GetObject函数获取位图的大小,然后用下述代码获取将要放置位图的后台缓冲区的大小:
// get size of surface. ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH; pdds->GetSurfaceDesc(&ddsd);
ddsd 是指向DDSURFACEDESC结构的指针,该结构储存了DirectDraw表面的当前描述。DDSURFACEDESC结构的成员描述了由DDSD_HEIGHT 和DDSD_WIDTH指定了表面的高和宽。IDirectDrawSurface::GetSurfaceDesc方法使用合适的值调入结构,例中高为480,宽为640。DDCopyBitmap函数锁定表面并将位图拷贝到后台缓冲区,然后用StretchBlt函数对位图进行拉伸或压缩,代码如下:
if ((hr = pdds->GetDC(&hdc)) == DD_OK) { StretchBlt(hdc, 0, 0, ddsd.dwWidth,ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY); pdds->ReleaseDC(hdc); }
2.13 从屏外表面位块传输 DDEX2同DDEX1基本相同。DDEX2打开一个位图文件并将它送往后台缓冲区,然后翻转后台缓冲区和主表面。但这对显示位图并不特别理想,DDEX3扩展了DDEX2的功能,它加入了两个屏外缓冲区,每个缓冲区都存储一个位图。下面是DDEX3中的doInit函数的一部分,功能是创建两个屏外缓冲区:
// Create an offscreen bitmap. ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwHeight = 480; ddsd.dwWidth = 640; ddrval = lpDD->CreateSurface( &ddsd, &lpDDSOne, NULL); if( ddrval != DD_OK ) { return initFail(hwnd); } // Create another offscreen bitmap. ddrval = lpDD->CreateSurface( &ddsd, &lpDDSTwo, NULL); if( ddrval != DD_OK ) { return initFail(hwnd); }
从代码中可以看到,dwFlags指明了程序使用DDSCAPS结构,并设置缓冲区的高和宽。由DDSCAPS结构中的DDSCAPS_OFFSCREEN标志指定该表面是屏外缓冲区,在DDSURFACEDESC结构中将高和宽设为480和640,然后使用IDirectDraw::CreateSurface方法来创建表面。因为两个屏外表面的大小一样,创建第二个缓冲区只需要再运行一次IDirectDraw::CreateSurface即可(当然要用不同的指针)。你还可以在DDSCAPS中设置DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY 来指定屏外缓冲区放在显示内存还是系统内存。将位图存放在显示内存可以加快后台缓冲区与屏外表面之间的数据传输速度,这在位图动画中非常重要。但是,如果你为屏外缓冲区指定了DDSCAPS_VIDEOMEMORY又没有足够的显示内存调入整个位图,当创建该表面时,程序就会返回一个DDERR_OUTOFVIDEOMEMORY的错误值。
2.14 将位图文件调入屏外表面 创建了两个屏外表面后,DDEX3使用了InitSurfaces函数将位图从Frntback.bmp文件装入到两个表面。InitSurfaces函数使用了DDCopyBitmap函数调入两个位图,代码如下:
// Load our bitmap resource. hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); if (hbm == NULL) return FALSE; DDCopyBitmap(lpDDSOne, hbm, 0, 0, 640, 480); DDCopyBitmap(lpDDSTwo, hbm, 0, 480, 640, 480); DeleteObject(hbm); return TRUE;
Frntback.bmp文件由两部分组成,一半在上,一半在下。DDCopyBitmap函数将上半部分调入第一个屏外表面lpDDSOne,下半部分调入第二个表面lpDDSTwo。
2.15 将屏外表面位位块传输到后台缓冲区 WM_TIMER包含了写表面和翻转表面的代码。在DDEX3中,它选择适当的屏外表面,并将其位块传输到后台缓冲区,代码如下:
rcRect.left = 0; rcRect.top = 0; rcRect.right = 640; rcRect.bottom = 480; if(phase) { pdds = lpDDSTwo; phase = 0; } else { pdds = lpDDSOne; phase = 1; }
while( 1 ) { ddrval = lpDDSBack->BltFast(0, 0, pdds, &rcRect, FALSE ); if( ddrval ==DD_OK ) break; }
“phase”决定了准备将哪一个屏外表面位块传输到后台缓冲区,然后调用IDirectDrawSurface::BltFast方法将选定的屏外表面位块传输到后台缓冲区,从左上角的位置0,0开始。rcRect指向定义了屏外表面的左上角和右下角的RECT结构。最后一个参数设为FALSE或0,指明不使用特殊的传输标志。一旦屏外表面被传送到后台缓冲区,就可以利用前面的方法将后台缓冲区和主表面相互翻转了。
三、创建动画 上面的例子都只是将数据写入后台缓冲区,然后将后台缓冲区与主表面翻转,其速度并不太快。下面的例子DDEX4和DDEX5优化了实时功能,使看起来更象一个真正的游戏。DDEX4显示了怎样为表面设置Color key,怎样使用IDirectDrawSurface::BltFast方法将屏外表面各部分拷贝到后台缓冲区以产生动画。DDEX5加入了读取调色板并在动画运行时改变调色板的功能。
3.1 Color Key和位图动画 在DDEX3例中描述了将位图放入屏外缓冲区的一种主要方式。DDEX4则使用了将背景和一系列的精灵(sprite,本例中精灵是圆环)装入屏外表面的技术,然后使用,IDirectDrawSurface::BltFast方法将屏外表面的各部分拷贝到后台缓冲区。doInit函数除了具有前面例子中的功能外,还包括了为精灵设置Color key的代码。Color key是用于设置透明度的颜色值。当使用硬件块写方式时,矩型区域内除了设为color key的像素,其它的像素都被块写,由此在表面上产生非矩型的精灵。设置color key的代码如下:
// Set the color key for this bitmap (black) // NOTE this bitmap has black as entry 255 in the color table. // ddck.dwColorSpaceLowValue = 0xff; // ddck.dwColorSpaceHighValue = 0xff; // lpDDSOne->SetColorKey( DDCKEY_SRCBLT, &ddck); // if we did not want to hard code the palette index (0xff) // we can also set the color key like so... DDSetColorKey(lpDDSOne, RGB(0,0,0)); return TRUE;
例中给出了设置color key的两种不同方法。第一种方法是注释内的3行,先设定DDCOLORKEY 结构中color key的范围,再调用IDirectDrawSurface::SetColorKey方法将color key 设置成黑色(假定位图在颜色表中以黑色作为调色板索引项255)。第二种方法是调用DDSetColorKey函数设置颜色的RGB值来选择color key,黑色就是RGB(0,0,0)。DDSetColorKey 函数调用了DDColorMatch函数,DDColorMatch存储放置于lpDDSOne表面的位图的0,0位置像素的颜色值,然后用提供的RGB值赋给0,0位置的像素,并将该颜色值屏蔽。 完成了这一步骤后,原来的颜色就可重新放回0,0处并用正确的Color Key调用DDSetColorKey函数,调用成功后,color key就放入DDCOLORKEY 结构中的成员变量dwColorSpaceLowValue ,同时也拷贝到dwColorSpaceHighValue成员,然后再调用IDirectDrawSurface::SetColorKey设置Color key。 CLR_INVALID是DDSetColorKey 和DDColorMatch函数中另一个有用的变量。如果在DDSetColorKey中以该值作为color key,位图左上角的像素就会作为color key使用。要想实现这一功能,需要调入位图文件All.bmp,将0,0处的像素值该为黑色,保存更改,然后如下改变对DDSetColorKey的调用: DDSetColorKey(lpDDSOne, CLR_INVALID);重新编译DDEX4,DDEX4就会使用0,0处的像素值作为color key了。
3.2 DDEX4中的动画 DDEX4利用All.bmp中的红色圆环调用updateFrame 函数来创建一个简单的动画。动画由圆环的3个位置组成。例子通过比较Win32中的GetTickCount和上次该函数开始运行的时间来判断是否重画哪个圆环,然后使用IDirectDrawSurface::BltFast方法将背景从屏外表面lpDDSOne位块传输到后台缓冲区,然后再使用已经设定好的color key将圆环块写入后台缓冲区。在所有的圆环都块写到后台缓冲区后,调用IDirectDrawSurface::Flip方法翻转后台缓冲区和主表面。
3.3 动态改变调色板 DDEX5描述了任何在程序运行时动态地改变调色板,尽管在游戏中这并不总是用到。DirectDraw确实能很好地控制调色板。DDEX5中的下述代码将All.bmp文件的下半部分中的调色板装入:
// First, set all colors as unused for(i=0; i<256; i++) { torusColors[i] = 0; } // lock the surface and scan the lower part (the torus area) // and remember all the index's we find. ddsd.dwSize = sizeof(ddsd); while (lpDDSOne->Lock(NULL, &ddsd, 0, NULL) == DDERR_WASSTILLDRAWING); // Now search through the torus frames and mark used colors for( y=480; y<480+384; y++ ) { for( x=0; x<640; x++ ) { torusColors[((BYTE*)ddsd.lpSurface)[y*ddsd.lPitch+x]] = 1; } } lpDDSOne->Unlock(NULL);
数组torusColors用于指定All.bmp中的下半部分调色板的索引值,数组在使用之前都初始化为0。然后锁定屏外表面来检测某颜色索引值是否已用。数组torusColors开始于位图的第0行第480列,数组中的颜色索引值由位图表面放置于内存的位置的一个字节决定,该位置由DDSURFACEDESC 结构中的lpSurface成员变量来决定,lpSurface指向对应于位图(0,480)处的内存地址(y*lPitch+x)。数组中设定的颜色索引值用来检测调色板中哪些颜色被替换。因为背景和红色圆环之间没有公用的颜色,所以只有那些同圆环联在一起的颜色值才会被替换。
3.4 替换调色板 DDEX5中的updateFrame函数同DDEX4中的基本相同,先将背景块写入后台缓冲区,再将3个红色圆环块写到前景。但在翻转表面之前,updateFrame用doInit函数创建的调色板索引值来改变主表面的调色板,代码如下:
// Change the palette if(lpDDPal->GetEntries( 0, 0, 256, pe ) != DD_OK) { return; }
for(i=1; i<256; i++) { if(!torusColors[i]) { continue; }
pe[i].peRed = (pe[i].peRed+2) % 256; pe[i].peGreen = (pe[i].peGreen+1) %256; pe[i].peBlue = (pe[i].peBlue+3) % 256; }
if(lpDDPal->SetEntries( 0, 0, 256, pe) != DD_OK) return;
IDirectDrawPalette::GetEntries方法在DirectDrawPalette对象中查询调色板的值,因为pe指向的调色板实体的值有效,方法就返回DD_OK,程序继续运行。然后循环检测torusColors在初始化中是否被设为1,如果索引值被设为1,由pe指向的调色板的红色、绿色、蓝色的值就被替换。在所有的被标记的调色板实体替换完毕后,再调用IDirectDrawPalette::SetEntries方法来真正改变DirectDrawPalette中的实体。如果该调色板已经设给主表面,上面的改变就会立即完成。完成了这一工作,剩下的就是同前面一样的翻转表面了。
四 使用覆盖表面 本例将使用DirectX SDK包含的Mosquito范例程序一步一步地说明怎样在程序中使用DirectDraw和硬件支持的覆盖表面。Mosquito使用覆盖表面的翻转链而没有位块传输到主表面将运动位图显示在桌面上。Mosquito程序调整覆盖表面的特征以适应硬件的限制。
4.1 创建一个主表面 要使用覆盖表面,必须先要初始化一个主表面,覆盖表面将显示在该主表面上。Mosquito用如下代码创建了一个主表面:
// Zero-out the structure and set the dwSize member. ZeroMemory(&ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); // Set flags and create a primary surface. ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; ddrval = g_lpdd->CreateSurface(&ddsd, &g_lpddsPrimary,NULL );
程序先初始化将要使用的DDSURFACEDESC结构,然后设定适当的标志调用IDirectDraw2::CreateSurface方法创建主表面。在对该方法的调用中,第一个参数是描述将要创建的表面的DDSURFACEDESC结构的指针;第二个参数是一个变量的指针,如果调用成功,该变量将接收IDirectDrawSurface接口的指针;第三个参数设为NULL表明没有COM集合。
4.2 检测硬件对覆盖的支持 初始化DirectDraw后,需要检测设备是否支持覆盖表面。因为DirectDraw不能仿真覆盖,所以如果硬件不支持覆盖,就不能继续下面的工作。你可以用IDirectDraw2::GetCaps方法获取硬件设备驱动程序的能力检测覆盖支持。在调用该方法之后,查看DDCAPS结构中的dwFlags成员是否包含有DDCAPS_OVERLAY标志。若有就表明支持覆盖,否则就不支持。 下面的代码是Mosquito程序中的一部分,它表明了怎样检测硬件的覆盖支持能力:
BOOL AreOverlaysSupported() { DDCAPS capsDrv; HRESULT ddrval; // Get driver capabilities to determine Overlay support. ZeroMemory(&capsDrv, sizeof(capsDrv)); capsDrv.dwSize = sizeof(capsDrv); ddrval = g_lpdd->GetCaps(&capsDrv, NULL); if (FAILED(ddrval)) return FALSE; // Does the driver support overlays in the current mode? // (Currently the DirectDraw emulation layer does not support overlays. // Overlay related APIs will fail without hardware support). if (!(capsDrv.dwCaps & DDCAPS_OVERLAY)) return FALSE;
return TRUE; }
程序首先调用IDirectDraw2::GetCaps方法获取设备驱动程序的能力。第一个参数是DDCAPS结构的地址指针;因为程序不需要关仿真的信息,所以第二个参数就设为NULL。获取驱动程序的能力后,程序使用了逻辑“与”来检查dwFlags成员是否包含有DDCAPS_OVERLAY标志。若否,程序返回FALSE表明失败。若是,就返回TRUE表明显示设备支持覆盖表面。
4.3 创建一个覆盖表面 如果知道显示设备支持覆盖表面,就可以创建一个。因为没有指明设备怎样支持覆盖表面的标准,所以不能够期望创建任意大小的像素格式的表面。另外,也不要期望第一次创建覆盖表面就会成功。因此,必须作好准备进行多次创建的尝试,直到有一个能够工作为止。 Mosquito程序在创建表面时遵循“best case to worst case”的原则,首先尝试创建一个三缓冲页翻转复杂覆盖表面。如果尝试失败,程序就改变方法尝试用其它通用的迅速格式来配置。下面的代码就是这一思路的表现:
ZeroMemory(&ddsdOverlay, sizeof(ddsdOverlay)); ddsdOverlay.dwSize = sizeof(ddsdOverlay); ddsdOverlay.dwFlags= DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH|DDSD_BACKBUFFERCOUNT| DDSD_PIXELFORMAT; ddsdOverlay.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_FLIP|DDSCAPS_COMPLEX | DDSCAPS_VIDEOMEMORY; ddsdOverlay.dwWidth =320; ddsdOverlay.dwHeight =240; ddsdOverlay.dwBackBufferCount=2; // Try to create an overlay surface using one of the pixel formats in our // global list. i=0; do{ ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i]; // Try to create the overlay surface ddrval = g_lpdd->CreateSurface(&ddsdOverlay,&g_lpddsOverlay, NULL); } while( FAILED(ddrval) && (++i < NUM_OVERLAY_FORMATS));
程序设置DDSURFACEDESC结构中的标志和值以反映三缓冲页翻转复杂覆盖表面,然后执行循环。在循环中,程序尝试用各种常用的像素格式创建要求的表面。如果尝试成功,循环就终止。如果尝试失败,说明很有可能是显示硬件没有足够的显示内存支持三缓冲的方案或者硬件根本就不支持翻转覆盖表面。在这种情况下,在最小要求的配置下使用一个单一的非翻转覆盖表面,代码如下:
// If we failed to create a triple buffered complex overlay surface, try // again with a single non-flippable buffer. if(FAILED(ddrval)) { ddsdOverlay.dwBackBufferCount=0; ddsdOverlay.ddsCaps.dwCaps=DDSCAPS_OVERLAY| DDSCAPS_VIDEOMEMORY; ddsdOverlay.dwFlags= DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT; // Try to create the overlay surface ddrval = g_lpdd->CreateSurface(&ddsdOverlay,&g_lpddsOverlay, NULL); i=0; do { ddsdOverlay.ddpfPixelFormat=g_ddpfOverlayFormats[i];ddrval = g_lpdd->CreateSurface(&ddsdOverlay, &g_lpddsOverlay, NULL); }while( FAILED(ddrval)&& (++i < NUM_OVERLAY_FORMATS) ); // We couldn't create an overlay surface. Exit, returning failure. if (FAILED(ddrval)) return FALSE; }
上面的代码对DDSURFACEDESC结构中的标志和值复原来反映一个单一的非翻转覆盖表面,然后通过像素格式的循环尝试创建表面。如果创建表面成功,循环就停止。如果不成功,程序返回FALSE表明创建表面失败。在成功地创建覆盖表面之后,就可将位图装入其中以供显示。
4.4 显示覆盖表面 创建了覆盖表面之后就可以显示它了。通常,硬件在用于显示覆盖的矩形的位置和像素格式上加上对齐约束。另外,还需要经常通过调整目的矩形的宽度来说明最小要求的拉伸因子以成功地显示覆盖表面。Mosquito程序按照以下的步骤准备和显示覆盖表面。
4.4.1、检测显示的最小要求 大部分的显示硬件在显示覆盖时都会加上约束。你必须很仔细地调整覆盖使之满足这些约束。可以通过调用IDirectDraw2::GetCaps方法获得有关这些约束的信息。该方法填充的结构DDCAPS包含了有关覆盖能力和使用约束的信息。不同硬件的约束是不同的,因此必须始终要查看包含在dwFlags成员的标志以确定附加的是哪一种约束。 Mosquito程序开始时先获取硬件的能力,然后采用基于最小拉伸因子的方法,如下所示:
// Get driver capabilities ddrval = g_lpdd->GetCaps(&capsDrv, NULL); if (FAILED(ddrval)) return FALSE;
// Check the minimum stretch and set the local variable accordingly. if(capsDrv.dwCaps & DDCAPS_OVERLAYSTRETCH) uStretchFactor1000 = (capsDrv.dwMinOverlayStretch>1000) ? capsDrv.dwMinOverlayStretch : 1000; else uStretchFactor1000 = 1000;
上面的代码调用IDirectDraw2::GetCaps方法获取硬件的能力。在本例中,第一个参数是DDCAPS结构的指针;第二个参数是NULL,表明了不需要获取有关仿真的信息。程序在一个临时变量中保留了最小拉伸因子以备以后之用。如果驱动程序报告出的拉伸因子大于1000,就表明驱动程序要求所有的目的矩形沿X轴的方向拉伸。例如,若拉伸因子是1.3,源矩形宽320个像素,目的矩形就必须至少要有416(320X1.3=416)个像素的宽。如果驱动程序报告出的拉伸因子小于1000,就表明驱动程序能够显示比源矩形小的覆盖,但不能伸展覆盖。 下面的代码是测定描述驱动程序的大小对齐约束的值:
// Grab any alignment restrictions and set the local variables acordingly. uSrcSizeAlign = (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC)?capsDrv.dwAlignSizeSrc:0; uDestSizeAlign= (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC)
?capsDrv.dwAlignSizeDest:0; 例中使用了更多的临时变量来保存从dwAlignSizeSrc和dwAlignSizeDest成员中获得的大小对齐约束。这些值提供了有关像素宽度对齐约束的信息,并且在以后设定源矩形和目的矩形的大小时需要用到。源矩形和目的矩形必须是这些值的倍数。 最后,程序测定描述目的矩形边界对齐约束的值:
// Set the "destination position alignment" global so we won't have to // keep calling GetCaps() every time we move the overlay surface. if (capsDrv.dwCaps & DDCAPS_ALIGNBOUNDARYDEST) g_dwOverlayXPositionAlignment= capsDrv.dwAlignBoundaryDest; else g_dwOverlayXPositionAlignment= 0;
上面的代码使用了一个全局变量来保存目的矩形边界约束的值,该值是从dwAlignBoundaryDest成员中的得来的,在以后程序重新放置覆盖时将会用到。你必须设定目的矩形左上角的X坐标在像素格式上同该值对齐。也就是说,如果该值是4,就只能指定左上角的X坐标为0,4,8,12等像素宽的目的矩形。Mosquito程序首先在0,0处显示覆盖,于是在第一次显示覆盖之前就不需要获取约束信息。但是因为不同应用程序的实现过程可能不同,所以你可能需要在显示覆盖之前检查这些信息以调整目的矩形。
4.4.2、设置源矩形和目的矩形 在获得了驱动程序的覆盖约束之后,就应该设定有关源矩形和目的矩形的值,确保能够正确显示覆盖。下面的代码就设定了源矩形的特征:
// Set initial values in the source RECT. rs.left=0; rs.top=0; rs.right = 320; rs.bottom = 240; // Apply size alignment restrictions, if necessary. if (capsDrv.dwCaps & DDCAPS_ALIGNSIZESRC &&uSrcSizeAlign) rs.right -= rs.right % uSrcSizeAlign;
上面的代码设置了包含整个表面大小的初始值。如果设备驱动程序要求大小对齐,程序就调整源矩形来保证。程序调整了源矩形的宽度使之比初始值要小,这是因为如果不是完全重新创建表面,就不能够扩展宽度。 在设定了源矩形的大小后,需要设置和调整目的矩形的大小。这一过程需要稍微多一点的工作,因为目的矩形可能需要先拉伸再调整以符合大小对齐约束。下面的代码根据最小拉伸因子来设置和调整目的矩形的大小:
// Set up the destination RECT, starting with the source RECT values. // We use the source RECT dimensions instead of the surface dimensions in // case they differ. rd.left=0; rd.top=0; rd.right = (rs.right*uStretchFactor1000+999)/1000; // (Adding 999 avoids integer truncation problems.) // (This isn't required by DDraw, but we'll stretch the // height, too, to maintain aspect ratio). rd.bottom = rs.bottom*uStretchFactor1000/1000;
前面的代码先设置目的矩形的左上角位置,再根据最小拉伸因子设定目的矩形的宽度。根据拉伸因子调整矩形时,注意程序在宽度和拉伸因子的乘积上又加了999,这是为了避免出现整数截断,整数截断会导致矩形同最小拉伸因子的要求不一致。程序在拉伸宽度后也拉伸了矩形的高度。不过,对高度的拉伸并不是必须的,这里只是为了保持位图的长宽比率避免出现失真的现象。 拉伸目的矩形后,程序对其进行调整以保持和大小对齐约束一致,下面是相应的代码:
// Adjust the destination RECT's width to comply with any imposed // alignment restrictions. if (capsDrv.dwCaps & DDCAPS_ALIGNSIZEDEST &&uDestSizeAlign) rd.right = (int)((rd.right+uDestSizeAlign-1)/uDestSizeAlign)*uDestSizeAlign;
程序检测硬件能力的标志,查看驱动程序是否加了矩形大小对齐约束。如果是,就增加目的矩形的宽度使之满足大小对齐约束。这里对矩形的调整是扩展其宽度而不能减少其宽度,因为减少宽度可能会导致目的矩形比最小拉伸因子要求的还要小,从而引起显示覆盖表面失败。
4.4.3、显示覆盖表面 在设置了源矩形和目的矩形后,就可以显示覆盖了。如果显示覆盖之前的准备工作正确,显示覆盖会很简单。Mosquito程序用如下的代码来显示覆盖:
// Set the flags we'll send to UpdateOverlay dwUpdateFlags = DDOVER_SHOW | DDOVER_DDFX; // Does the overlay hardware support source color keying? // If so, we can hide the black background around the image. // This probably won't work with YUV formats if (capsDrv.dwCKeyCaps & DDCKEYCAPS_SRCOVERLAY) dwUpdateFlags |= DDOVER_KEYSRCOVERRIDE; // Create an overlay FX structure so we can specify a source color key. // This information is ignored if the DDOVER_SRCKEYOVERRIDE flag isn't set. ZeroMemory(&ovfx, sizeof(ovfx)); ovfx.dwSize = sizeof(ovfx); ovfx.dckSrcColorkey.dwColorSpaceLowValue=0; // Specify black as the color key ovfx.dckSrcColorkey.dwColorSpaceHighValue=0; // Call UpdateOverlay() to displays the overlay on the screen. ddrval = g_lpddsOverlay->UpdateOverlay(&rs, g_lpddsPrimary, &rd, dwUpdateFlags, &ovfx); if(FAILED(ddrval)) return FALSE;
程序开始在临时变量dwUpdateFlags中设定了DDOVER_SHOW和DDOVER_DDFX标志,指明该覆盖是第一次显示,硬件应该使用包含在DDOVERLAYFX结构中的效果信息完成这一工作。然后,程序检查DDCAPS结构确定覆盖是否支持源Color Key。如果是,DDOVER_KEYSRCOVERRIDE标志就包含在dwUpdateFlags变量中利用源Color Key,程序也据此设置Color Key。 准备工作完成之后,程序调用IDirectDrawSurface3::UpdateOverlay方法来显示覆盖。在对该方法的调用中,第一个参数和第三个参数是已调整的源矩形和目的矩形的地址。第二个参数是覆盖显示在其上的主表面的地址。第四个参数是包括了放置于此前准备的dwUpdateFlags变量中的标志。第五个参数是DDOVERLAYFX结构的地址,该结构中的成员将设定同那些标志相匹配。 如果硬件只支持一个覆盖表面而且该表面正在使用,UpdateOverlay方法就会失败,并返回DDERR_OUTOFCAPS。另外,有可能硬件报告出的最小拉伸因子过小,在UpdateOverlay方法失败后,你就需要尝试减少目的矩形的宽度来应付这种可能性。不过,这种情况很少发生,在Mosquito中也只是简单地返回一个错误信息。
4.5 更新覆盖的显示位置 显示覆盖表面之后,有时可能就不需要对覆盖左其它的操作了。但有些软件还需要重新放置覆盖,改变覆盖的显示位置。Mosquito程序就使用IDirectDrawSurface3::SetOverlayPosition方法重新放置覆盖,代码如下:
// Set X- and Y-coordinates ...... // We need to check for any alignment restrictions on the X position // and align it if necessary. if (g_dwOverlayXPositionAlignment) dwXAligned = g_nOverlayXPos- g_nOverlayXPos % g_dwOverlayXPositionAlignment; else dwXAligned = g_nOverlayXPos; // Set the overlay to its new position. ddrval = g_lpddsOverlay->SetOverlayPosition(dwXAligned,g_nOverlayYPos); if (ddrval == DDERR_SURFACELOST) { if (!RestoreAllSurfaces()) return; }
程序开始对齐矩形以满足可能存在的任何目的矩形边界对齐约束。当程序此前调用IDirectDraw2::GetCaps方法时,全局变量g_dwOverlayXPositionAlignment已经设定为同DDCAPS结构中dwAlignBoundaryDest成员所报告出的值相等。如果存在目的矩形约束,程序就据此调整新的X坐标为像素对齐的。若不满足要求,覆盖表面就不能显示。 在完成了对X坐标的调整之后,程序调用IDirectDrawSurface3::SetOverlayPosition方法重新放置覆盖。在调用中第一个参数是对齐的新的X坐标,第二个参数的新的Y坐标。这些值表明了覆盖左上角新的位置。这里并不需要取得宽度和高度信息,因为DirectDraw在开始用IDirectDrawSurface3::UpdateOverlay方法显示覆盖时就已经获得了表面大小的信息。如果因为一个或多个表面丢失而引起的重新放置覆盖表面的失败,Mosquito程序就调用一个应用定义的函数来恢复这些表面并重新装入它们的位图。 注意,不要使用太靠近目标表面的右、下边界的坐标。因为IDirectDraw2::SetOverlayPosition方法并不执行剪切功能,所以使用那些可能导致覆盖超出目标表面边界的坐标会引起调用的失败,并返回DDERR_INVALIDPOSITION。
4.6 隐藏覆盖表面 如果不再需要一个覆盖表面或只想不让覆盖可见,就可以设定适当的标志调用IDirectDrawSurface3::UpdateOverlay方法来隐藏该覆盖表面。Mosquito用以下代码隐藏覆盖表面并准备关闭应用程序:
void DestroyOverlay() { if (g_lpddsOverlay) { // Use UpdateOverlay() with the DDOVER_HIDE flag to remove an overlay // from the display. g_lpddsOverlay->UpdateOverlay(NULL,g_lpddsPrimary, NULL, DDOVER_HIDE, NULL); g_lpddsOverlay->Release(); g_lpddsOverlay=NULL; } }
在调用IDirectDrawSurface3::UpdateOverlay时,对源矩形和目的矩形指定了NULL,因为在隐藏覆盖的过程中不需要源矩形和目的矩形。 同理,第五个参数也被指定为NULL是因为不使用覆盖效果。第二个参数是目标表面的指针。最后,程序在第四个参数使用DDOVER_HIDE标志表明该覆盖将从视口中取消。 程序在隐藏覆盖之后,释放了它的IDirectDrawSurface3接口,并且将全局变量设为NULL使之变得无效。对于Mosquito程序来说,覆盖就不再需要了。如果在应用程序中还需要使用该覆盖,就只需简单地隐藏覆盖,而不要释放它,然后在需要的时候再重新显示。
五 DirectDraw中其它的DirectDraw范例 要熟练掌握有关DirectDraw的应用,还应该多研究包含在DirectX SDK:之中的下述范例。 1、Stretch 描述了怎样在一个窗口中创建一个非独占模式的动画,它具有剪切位块传输和拉伸剪切位块传输的功能。 2、Donut 描述了多个独占模式的应用同非独占模式应用之间的交互。 3、Wormhole 该范例描述了详细的调色板动画。 4、Dxview 详细描述了怎样获取显示硬件的能力。 其它的还有Duel、Iklowns、Foxbear、Palette和Flip2d等,只要多这些范例多加分析,掌握DirectX最基本的技术DirectDraw是不难的。 |
|
|