二、第一个DirectDraw实例
要使用DirectDraw,首先必须创建DirectDraw对象的一个实例来表征计算机上的显示适配卡,然后使用接口方法来处理对象。另外还需要创建一个或多个DirectDrawSurface对象的实例来显示游戏。DDEX1首先创建一个DirectDraw对象,再创建一个主表面(primary surface)和一个后台缓冲区(back buffer),然后在表面之间转换。DDEXx例子都是用C++写成的,如果你使用的是C编译器,必须将代码做适当改动,至少要加入虚表和指向接口方法的指针。
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、创建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对象不能被创建
}
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有一个后台等待进程,它俘获所有发往该窗口的消息,用这些消息来确定应用程序何时终止。如果创建了新的窗口,必须确定该窗口为活动的,否则,就会有一系列的事件无法继续工作。
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方法检测用户的显示设备的性能。
5、创建可翻转表面(Flippable Surface)
设定了显示模式后,必须创建放置应用程序的表面。在DDEX1例中,我们使用IDirectDraw::SetCooperativeLevel 方法将程序设成独占全屏模式,然后可以创建翻转表面。如果使用IDirectDraw::SetCooperativeLevel 设成DDSCL_NORMAL模式,就只能创建块写方式的表面了。
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错误。
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 参数就指向后台缓冲区。
8、着色表面
创建了主表面和后台缓冲区后,DDEX1使用标准的Windows GDI 函数将一些文本提交到主表面和后台缓冲区,代码如下:
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方法才能获取该表面内存的有效指针。
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 就返回DDERR_WASSTILLDRAWING。本例中,IDirectDrawSurface::Flip继续循环直到返回DD_OK.。
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方法将一个屏外表面的内容位块传输到后台缓冲区,然后翻转表面并将另一个屏外表面的内容位块传输到后台缓
冲区。下面将详细讨论这些功能。
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个实体,每个实体有四个字节(红色、绿色、蓝色和标志字节)。
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);
}
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的错误值。
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。
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,指明不使用特殊的传输标志。一旦屏外表面被传送到后台缓冲区,就可以利用前面的方法将后台缓冲区和主表面相互翻转了。