DirectDraw程序设计指南
作者 Lan Mader
译 冬草秋叶(Cocoaleaves)
目录
DirectDraw总览
1. DirectX API 成员
2. 什么是DirectDraw
3. 与WinG的关系
4. DirectDraw页面 - 如何进入显存
5. 从翻页(page flipping)到动画
6. DirectDraw与COM
DirectDraw程序设计
1. 编译连接所需的东西
2. 初始化DirectDraw
3. 创建主页面并设置翻页链
4. 载入位图到显存
5. 载入调色板
6. 关键色
7. 组成场景
8. 清除
一些细节
1. 调试DirectDraw
2. 窗口模式运行
3. 页面丢失
4. 进一步学习
DirectDraw总览
本部分给出对DirectDraw的高度总览,解释需要理解的DirectDraw概念。
1.DirectX API 成员
微软DirectX API包括以下成员:
l DirectDraw – 直接操作显存
l DirectSound – 直接操作声音硬件
l DirectPlay – 对多人联机游戏的支持
l DirectInput – 对游戏输入设备如操纵杆的支持
它们被设计成一些API从而让程序设计师直接操作硬件。本文讲解DirectDraw技术,让你更快的编写高质量的程序。
2.什么是DirectDraw
DirectDraw从本质上讲就是显存的管理入口。它最重要的作用就是让程序设计师直接在显存中存储和操作位图。它让你能够利用显示硬件的加速器在显存中进行位图的区块拷贝(blit),这样比从内存拷贝到显存快多了。尤其对于今天64位显卡在显存中进行的64位运算有效。并且,硬件进行区块拷贝操作是独立于CPU的,从而减轻了CPU的负担。另外,DirectDraw支持显示硬件的其他加速功能,比如支持精灵动画和Z轴缓冲。DirectDraw1.0现在适用于Windows95,今年夏天将适用于WindowsNT。
3.与WinG的关系
到现在为止,程序设计师可能应用WinG或CreatDIBSection技术在Windows下创建动画。这种方法让程序设计师直接操作内存中位图从而有效的绘制位图。WinG比用GDI操作位图更好,因为GDI从来不能让游戏设计师编写出好程序。
当用位图组成WinG的场景时,是从内存复制到显存,从而显示出来的。这种技术没有DirectDraw快因为从内存到显存没有从显存到显存快。在复杂游戏或应用程序中DirectDraw和WinG技术都会被用到,因为显存资源是有限的。
4.DirectDraw页面 – 如何进入显存
在DirectDraw中,目标是尽可能多的往显存中存放位图。有了DirectDraw,所有显存对你都可用了。你可以将它用于存储各种各样的位图或当主页面和后缓冲页。这些一块块的显存区域在DirectDraw中称作页面(表面)。当你载入一个代表精灵的位图到显存中,首先要创建一个页面,即在显存中申请一块区域,然后将位图区块拷贝到页面中,这就有效的把位图载入到显存中,你可以使用它,用到什么时候都可。
屏幕上显示的内容在显存中也是一个页面,称为主页面。页面的大小与当前显示模式所需大小相适。如果显示模式是640x480与256色(8位每像素),主页面用了显存中307,200字节显存空间。你要同时创建至少一个与主页面大小相同的后缓冲页用于翻页(Flip)。这意味着你最初就需要614,400字节显存空间,在不载入位图的情况下。
所以,DirectDraw中最重要的硬件资源之一就是显存。当显存不够用时,DirectDraw的页面就要存到内存中,所有硬件加速的好处对内存页面都无效了。当今大多数显卡都带有至少1M的显存,但1M刚刚够用于初始化。事实上2M的显存是让DirectDraw发挥作用的,开发小程序的最低值。
DirectDraw提供在运行时检查显存剩余空间的函数,从而程序可以最优地使用显存。
5.从翻页到动画
场景是把存于显存块(页面)中的位图区块拷贝到另一块称为离屏缓冲区的显存块(页面)中而组成的。然后,通过硬件把可视的主页面翻转到后缓冲页,新组成的场景便显示出来了。这就是翻页(page flipping),这是非常快的,因为这个过程并没有数据的复制,而是简单的告诉显卡显存中哪一块是要刷新到显示器上的信号。DirectDraw保护硬件不被程序设计师操作,就提供一个简单的Flip()函数来做这些工作。用Flip()做动画:
1. 在后缓冲页中组合场景
2. 翻页,把后缓冲页显示出来
3. 之前显示的页面现在成了后缓冲页
4. 重复步骤1
翻页的帧频非常快,仅受限于显示器的刷新频率,即翻页发生于显示器的每次扫描。这意味着如果你的显示器设置为72Hz,你可以达到72帧每秒的帧频。翻页于扫描同步的好处是避免了撕裂DirectDraw页面的画面。撕裂是一种在扫描过程中移动屏幕上精灵所产生的现象。精灵移动到新位置时看起来是被撕裂的。比如,一个图象的上半截出现在某位置但下半截却向左或向右偏移了,看起来像撕开了一样。
图象程序师已经找到了有效的绘制精灵的技术,这些技术依然可用于DirectDraw程序。但是,学习那些技术对学习理解DirectDraw没有帮助。所以,我着眼于“从翻页到动画”。翻页是产生动画最简单的方法,也是最好的演示DirectDraw如何工作的方法。
6.DirectDraw和COM
DirectX是应用COM对象的工具。用DLL中的COM对象比简单的用DLL中的多态的API接口更有好处。DirectDraw设计师需要知道的主要一点就是使用COM对象不比用其他API难,尤其对于C++和面向对象Pascal程序。
你不必参与初始化COM,或调用QueryInterface来得到接口。DirectDraw头文件为不同的DirectX对象声明了C++类。你可以调用不同的函数实例化这些类。所以,关键在于学习不同的成员函数。
DirectDraw程序设计
本部分用示例代码演示DirectDraw如何工作。代码演示全部功能,并尽可能体现本质要点。
1.编译连接所需的东西
首先,你需要DirectX SDK。这目前仅能用于MSDN level II或更高、MS Visual C++4.1。SDK提供帮助文件,并有精彩的程序实例。
为了编译连接一个DirectDraw应用程序,你需要DDRAW.DLL、DDRAW.H,并且你需要用IMPLIB.EXE从DDRAW.DLL制作一个导入的库。DirectX是Win32下的技术,所以你需要一个能产生Win32应用程序的编译器。Borland C++4.52和Borland C++5.0提供一个很好的DirectX开发平台。
为了运行DirectX技术程序,你必须在系统里安装DirectX驱动。既然这是一个常用的游戏技术,你会发现DirectX已经安装在你的系统里了。到Window/系统目录下找找,是不是DDRAW.DLL已经存在了。(记住,DirectX技术不能用于Windows 3.x)
2. 初始化DirectDraw
学习DirectDraw的第一步是将其初始化.阅读下面代码对每一步操作的详细注释。
// 全局变量 (ugh)
LPDIRECTDRAW lpDD; // DDRAW.H中定义的DirectDraw对象
/*
* DirectDraw初始化函数
* 演示:
* 1) 创建DirectDraw对象
* 2) 设置协作级别
* 3) 设置显示模式
*
*/
bool DirectDrawInit(HWND hwnd)
{
HRESULT ddrval;
/*
* 创建主DirectDraw对象
*
* 此函数初始化COM并构造DirectDraw对象
*/
ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
if( ddrval != DD_OK )
{
return(false);
}
/*
* 协作级别决定我们使用屏幕权力的大小
* 这必须设置为DDSCL_EXCLUSIVE 或 DDSCL_NORMAL
*
* DDSCL_EXCLUSIVE 允许我们改变显示模式, 并且需要
* DDSCL_FULLSCREEN 标记, 从而把窗口设定为
* 全屏. 这是我们首选的DirectDraw模式因为它允许我们
* 控制整个屏幕而不用理会GDI
*
* DDSCL_NORMAL 允许DirectDraw程序在窗口下运行
*/
ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}
/*
* 设置显示模式为 640x480x8
* 设置显示模式可行,因为我们在上面已经选择了exclusive协作级别
*/
ddrval = lpDD->SetDisplayMode( 640, 480, 8);
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}
return(true);
}
现在我们完成了初始化和设置显示模式。
3. 创建主页面并设置翻页链
下面我们创建带有一个后台缓冲的主页面。这在DirectDraw中称为复杂页(complex surface)。缓冲页是附属在主页面上的。当我们在程序结尾释放时,仅仅需要释放主页面指针,缓冲页也就随着释放了。
// 全局变量 (ugh)
LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw主页面
LPDIRECTDRAWSURFACE lpDDSBack; // DirectDraw缓冲区
/*
* 创建一个带有一个后缓冲页的可翻页的主页面
*/
bool CreatePrimarySurface()
{
DDSURFACEDESC ddsd;
DDSCAPS ddscaps;
HRESULT ddrval;
// 创建带有一个缓冲页的主页面
memset( &ddsd, 0, sizeof(ddsd) );
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 )
{
lpDD->Release();
return(false);
}
// 取得指向缓冲页的指针
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);
if( ddrval != DD_OK )
{
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}
return true;
}
现在我们创建好了双页面翻页环境。
4. 载入位图到显存
下一步是为游戏载入位图。理想情况下,有足够的显存,所有位图都可在显存中。如果不能, CreateSurface将在系统内存中创建页面。除了区块拷贝慢一些,其他没有任何影响。
/*
* 此函数创建离屏页面,从磁盘载入位图到页面
* 参数szBitmap是文件名或位图号
*/
IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap)
{
HBITMAP hbm;
BITMAP bm;
IDirectDrawSurface *pdds;
// LoadImage在Win95下有项补充的功能
// 允许你在文件载入位图
hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE|LR_CREATEDIBSECTION);
if (hbm == NULL)
return NULL;
GetObject(hbm, sizeof(bm), &bm); // 取得位图尺寸
/*
* 为位图创建一个页面
* 下面准备函数CreateOffScreenSurface()
*/
pdds = CreateOffScreenSurface(pdd, bm.bmWidth, bm.bmHeight);
if (pdds) {
DDCopyBitmap(pdds, hbm, bm.bmWidth, bm.bmHeight);
}
DeleteObject(hbm);
return pdds;
}
/*
* 创建一个特殊尺寸的页面
* 如果显存足够就在显存中创建
* 否则在系统内存中创建
*/
IDirectDrawSurface * CreateOffScreenSurface(IDirectDraw *pdd, int dx, int dy)
{
DDSURFACEDESC ddsd;
IDirectDrawSurface *pdds;
//
// 为位图创建一个页面
//
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = dx;
ddsd.dwHeight = dy;
if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)
{
return NULL;
} else {
return pdds;
}
}
/*
* 此函数把先前载入过的位图复制到一个DirectDraw页面
* 注意到我们要为DirectDraw页面获得一个GDI的设备上下文(DC)
* 再把位图BitBlt到页面中
*/
HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int dx, int dy)
{
HDC hdcImage;
HDC hdc;
HRESULT hr;
HBITMAP hbmOld;
//
// 把位图选进DC,我们可以使用它了
//
hdcImage = CreateCompatibleDC(NULL);
hbmOld = (HBITMAP)SelectObject(hdcImage, hbm);
if ((hr = pdds->GetDC(&hdc)) == DD_OK)
{
BitBlt(hdc, 0, 0, dx, dy, hdcImage, 0, 0, SRCCOPY);
pdds->ReleaseDC(hdc);
}
SelectObject(hdcImage, hbmOld);
DeleteDC(hdcImage);
return hr;
}
现在我们有了一个已经载入了位图的DirectDraw页面。下面我们用它通过区块拷贝来组成场景。
5. 载入调色板
我们需要为主页面设置调色板。这意味着游戏中的所有位图被这同一个调色板创建。这有一个函数用于为给出的位图创建DirectDraw调色板。
/*
* 为已有的位图创建一个DirectDraw调色板
* 参数szBitmap是文件名或位图号
*/
IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCSTR szBitmap)
{
IDirectDrawPalette* ddpal;
int i;
int n;
int fh;
PALETTEENTRY ape[256];
if (szBitmap && (fh = _lopen(szBitmap, OF_READ)) != -1)
{
BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi;
_lread(fh, &bf, sizeof(bf));
_lread(fh, &bi, sizeof(bi));
_lread(fh, ape, sizeof(ape));
_lclose(fh);
if (bi.biSize != sizeof(BITMAPINFOHEADER))
n = 0;
else if (bi.biBitCount > 8)
n = 0;
else if (bi.biClrUsed == 0)
n = 1 << bi.biBitCount;
else
n = bi.biClrUsed;
//
// 一个设备无关位图(DIB)颜色表存储颜色为BGR而不是RGB
// 所以要轮换一下
//
for(i=0; i
{
BYTE r = ape[i].peRed;
ape[i].peRed = ape[i].peBlue;
ape[i].peBlue = r;
}
}
if (pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL) != DD_OK)
{
return NULL;
} else {
return ddpal;
}
}
下面我们这样为主页面设置调色板:
lpDDPal = DDLoadPalette(lpDD, szBitmap); // 调用上面的函数载入调色板
if (lpDDPal)
lpDDSPrimary->SetPalette(lpDDPal); // 把调色板设置到主页面
6. 关键色
DirectDraw用叫做关键色的概念处理区块拷贝中的透明色。这就是说,你可以让你的位图在区块拷贝中一部分变透明,这在把精灵拷贝到背景上时是非常有用的。关键色指定了调色板中的一个颜色范围,源位图或目标位图的关键色在区块拷贝中不被复制。一般地,把调色板入口0或255设为关键色,同时位图中不想被拷贝的部分颜色应该为设置的关键色。
// 为这副位图设置关键色
//
// 无论什么颜色在调色板入口255处都将成为区块拷贝的关键色
// 颜色会是黑色,除非你创建调色板时用了DDPCAPS_ALLOW256标志
DDCOLORKEY ddck;
ddck.dwColorSpaceLowValue = 0xff;
ddck.dwColorSpaceHighValue = 0xff;
// lpDDSSomeBitmapSurface是一个已载入位图的表面,即所有表面都包含了一个位图
lpDDSSomeBitmapSurface->SetColorKey( DDCKEY_SRCBLT, &ddck );
7. 组成场景
现在我们已经准备好开始编写一个游戏,模拟器或其他基于图象的程序。简单的方法就是将一张背景位图区块拷贝到后缓冲页,然后把一些精灵(同样是位图)区块拷贝到后缓冲页合适的位置,然后翻页。重复上面的步骤,把精灵粘到新的位置。
// 此函数在消息循环的适当位置反复调用
void updateFrame( void )
{
RECT rcRect;
HRESULT ddrval;
int xpos, ypos;
// 把素材区块拷贝到下一帧
SetRect(&rcRect, 0, 0, 640, 480);
// 拷贝背景图。这是一张640x480的位图,
// 所以它将填满屏幕(记得我们把显示模式设为640x480x8
// 参数lpDDSOne是假定背景图装载进的页面。
// LpDDSBack是我们的后缓冲页
ddrval = lpDDSBack->BltFast( 0, 0, lpDDSOne, &rcRect,
DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);
if( ddrval != DD_OK )
{
return;
}
SetRect(&rcRect, 0, 0, 32, 32); // 假设我们的精灵位图尺寸为32x32
// xpos和ypos表示精灵合适的位置
// lpDDSMySprite是有精灵位图的页面
ddrval = lpDDSBack->BltFast( xpos, ypos, lpDDSMySprite,
&rcRect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT );
if( ddrval != DD_OK )
{
return;
}
// 翻页
ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT );
} /* updateFrame */
这里有些东西要解释一下。调用BltFast()和Flip()时,最后一个参数包含DDXX_WAIT标志。 这是有用的,因为这些函数都是异步的。就是说,当操作成功开始(并没有完成)或有错误而无法开始时函数就返回了。通常返回的错误信息是页面正忙,正在进行上一次操作。使用DDXX_WAIT标志就是告诉DirectDraw当页面忙时就不断尝试,或者返回一些其他的错误。
注意到第一次调用BltFast(),拷贝背景时,用了标志DDBLTFAST_NOCOLORKEY。这样告诉BltFast忽略了关键色并且改善了拷贝。这意味着一些透明区域将被拷贝从而显得不透明,但因为这是背景图而不是精灵图象,所以这样使用是正确的。
当Flip()函数被调用,显存中下面的页面被交换。这是指曾是主页面的现在成了后缓冲页,反之亦然。结果是,你可以继续用后缓冲页的指针去组合新场景,然后调用Flip(),不用担心去弄清楚后缓冲页到哪里去了。
8. 清除
DirectX中的清理工作,主要是调用创建的DirectX对象的Release()成员函数。你可以这样做:
/*
* 调用我们创建的所有对象的Release来清除
*/
void finiObjects( void )
{
if( lpDD != NULL )
{
if( lpDDSPrimary != NULL )
{
lpDDSPrimary->Release();
lpDDSPrimary = NULL;
}
if( lpDDSOne != NULL )
{
lpDDSOne->Release();
lpDDSOne = NULL;
}
if( lpDDPal != NULL )
{
lpDDPal->Release();
lpDDPal = NULL;
}
}
lpDD->Release();
lpDD = NULL;
} /* finiObjects */
当你释放活动的DirectDraw对象(LPDIRECTDRAW的实例)时,涉及的东西将归零,对象也被销毁,屏幕恢复为最初的显示模式。
一些细节
1. 调试DirectDraw
当一个DirectDraw程序全屏运行时,GDI是不可用的。这意味着你不能通过在程序中设置断点,用调试器去发现问题。有一些不寻常的方法,包括使用支持双显示器的调试器、远程调试,但是符合硬件限制的最简单的方法是让你的程序既能全屏运行,又能在窗口下运行。
2. 窗口模式运行
如果你想在窗口下运行,你必须以不同方法初始化DirectDraw。既然你不得不与GDI共存,你就不能改变显示模式。你主要要知道的是当你创建主页面时,你得到了一个指向GDI正使用的可见区域的指针。如果你想,你可以在屏幕上乱画。所以你要小心别画到窗口外面去。如果你愿意你可以使用DirectDraw裁切板(clipper)来帮助你。
下面初始化DirectDraw的函数是用于窗口模式运行的,并且不使用翻页。
bool IsWindowed = false;
// 窗口模式初始化DirectDraw的函数
bool DDGameInitWindowed(THIS_ HWND hwnd)
{
DDSURFACEDESC ddsd;
DDSCAPS ddscaps;
HRESULT ddrval;
IsWindowed = true;
/*
* 创建DirectDraw主对象
*/
ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
if( ddrval != DD_OK )
{
return(false);
}
// 用DDSCL_NORMAL表示我们要与GDI共存
ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_NORMAL );
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}
memset( &ddsd, 0, sizeof(ddsd) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
// 这次主页面不是可翻页的页面
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
if( ddrval != DD_OK )
{
lpDD->Release();
return(false);
}
// 创建一个裁切板保证不画到窗口外面
ddrval = lpDD->CreateClipper( 0, &lpClipper, NULL );
if( ddrval != DD_OK )
{
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}
// 把窗口句柄设置给裁切板就给了它一个与窗口相适的裁切框
ddrval = lpClipper->SetHWnd( 0, hwnd );
if( ddrval != DD_OK )
{
lpClipper-> Release();
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}
// 把裁切板附到主页面
ddrval = lpDDSPrimary->SetClipper( lpClipper );
if( ddrval != DD_OK )
{
lpClipper-> Release();
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}
memset( &ddsd, 0, sizeof(ddsd) );
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 640;
ddsd.dwHeight = 480;
// 单独创建后缓冲页
ddrval = lpDD->CreateSurface( &ddsd, &lpDDSBack, NULL );
if( ddrval != DD_OK )
{
lpClipper-> Release();
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}
return(true);
}
这些代码与之前代码的第一个不同就是初始化DirectDraw时调用SetCooperativeLevel()使用了标志DDSCL_NORMAL 。这意味着我们没有完全占用显示模式,从而就没有排除GDI。当我们创建主页面,我们没有创建一个复杂页面,因为我们不准备使用翻页。
然后我们为窗口创建一个DirectDraw裁切板并套用在主页面上。DirectDraw裁切板是一个用一些矩形框来裁切的对象。对窗口句柄设置裁切板,我们就暗示了裁切板把窗口客户区当矩形框进行裁切。然后我们把它设置到主页面,这样区块拷贝到这个页面的都是经过窗口裁切的。
我们单独创建后缓冲页,并且这次我们必须指定它的大小。
现在一切准备就绪。最后一件要做的不同的事就是处理以前正常应该调用Flip()的地方,这次用blit。你可以如下制作你自己的Flip:
bool MyFlip()
{
HRESULT ddrval;
RECT rcRectSrc;
RECT rcRectDest;
POINT p;
// 窗口模式blit,否则Flip即可
if (IsWindowed)
{
// 首先需要指出主页面的位置
p.x = 0; p.y = 0;
ClientToScreen(ddWnd, &p);
GetClientRect(ddWnd, &rcRectDest);
OffsetRect(&rcRectDest, p.x, p.y);
SetRect(&rcRectSrc, 0, 0, 640, 480);
ddrval = lpDDSPrimary->Blt( &rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
} else {
ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT);
}
return (ddrval == DD_OK);
}
3. 页面丢失
上面代码的一个很重要的不足就是忽略了用户从一个全屏运行的DirectDraw程序切换到系统中正在运行的其他程序而产生的后果。首先,你的程序需要处理一个WM_ACTIVATEAPP消息并且当程序不活动时设置一个标志。在你消息循环(PeekMessage loop)中适当的位置,检查这个标志,如果程序不活动就停止刷新。
但实际情况是,一旦你切换到其他程序,你就脱离了DirectDraw环境并允许了GDI成为活动的。GDI忽视DirectDraw,并将覆盖显存中DirectDraw页面使用的空间。这意味着你的位图被擦除了需要重新载入。幸运的是,当你调用Flip()、BltFast()一类的函数操作一个页面时,如果页面已丢失DirectDraw将返回一个DDERR_SURFACELOST 错误警告你。所以,你的代码应该检查这个错误并重新载入位图到错误发生的页面。
ddrval = lpDDSPrimary->Blt( &rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
if( ddrval == DDERR_SURFACELOST )
{
ddrval = restoreAll();
}
ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT);
if( ddrval == DDERR_SURFACELOST )
{
ddrval = restoreAll();
}
:
:
void restoreAll()
{
// 为你程序创建的每一个页面做下面的工作:
ddrval = lpMyDDSurface->Restore(); // 这是把显存重分给页面
if( ddrval == DD_OK )
{
lpTempDDS ->DDReLoadBitmap(); // 这与上面最初创建好页面载入位图的函数是相同的功能
}
}
4. 进一步学习
我能推荐的最好的资料就是DirectX SDK所带的示例代码。这些例子值得一看因为他们包含本文未提到的地方。我已经找到了一本关于DirectX、深度揭示这些API的书。MSJ已经发布了一些关于DirectDraw和DirectSound的不错的书,但都不深入。