四、使用覆盖表面
本例将使用DirectX SDK包含的Mosquito范例程序一步一步地说明怎样在程序中使用DirectDraw和硬件支持的覆盖表面。Mosquito使用覆盖表面的翻转链而没有位块传输到主表面将运动位图显示在桌面上。Mosquito程序调整覆盖表面的特征以适应硬件的限制。
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集合。
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表明显示设备支持覆盖表面。
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、显示覆盖表面
创建了覆盖表面之后就可以显示它了。通常,硬件在用于显示覆盖的矩形的位置和像素格式上加上对齐约束。另外,还需要经常通过调整目的矩形的宽度来说明最小要求的拉伸因子以成功地显示覆盖表面。Mosquito程序按照以下的步骤准备和显示覆盖表面。
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处显示覆盖,于是在第一次显示覆盖之前就不需要获取约束信息。但是因为不同应用程序的实现过程可能不同 ,所以你可能需要在显示覆盖之前检查这些信息以调整目的矩形。
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;
程序检测硬件能力的标志,查看驱动程序是否加了矩形大小对齐约束。如果是,就增加目的矩形的宽度使之满足大小对齐约束。这里对矩形的调整是扩展其宽度而不能减少其宽度,因为减少宽度可能会导致目的矩形比最小拉伸因子要求的还要小,从而引起显示覆盖表面失败。
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中也只是简单地返回一个错误信息。
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。
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程序来说,覆盖就不再需要了。如果在应用程序中还需要使用该覆盖,就只需简单地隐藏覆盖,而不要释放它,然后在需要的时候再重新显示。