高级DirectDraw主题
高级DirectDraw主题
返回上级
1、对Mode 13的支持 关于Mode 13 设置Mode 13 Mode 13与页面特性 使用Mode 13模式 2、从DMA中获益 3、在窗口模式下使用调色板 | 4、获得换页和Blit操作的状态 5、使用Blit进行单色填充 6、测定显示硬件的能力 7、在视频RAM中储存位图 8、Triple Buffering(三缓冲) 9、DirectDraw应用程序和窗口风格 10、将真彩色匹配到帧缓冲区的色彩空间 |
对Mode 13的支持
返回目录
关于Mode 13
设置Mode 13
Mode 13与页面特性
使用Mode 13模式
关于Mode 13
返回目录
DirectDraw支持线性非换页的320x200的8位调色板显示模式,人们对它使用更为广泛的名称是Mode 13棗它的16进制BIOS模式编号。DirectDraw将这种模式视为一种Mode X模式,但是它们之间又有着重要的区别,这是由Mode 13模式的物理性质所强加的。
设置Mode 13
返回目录
Mode 13模式与Mode X模式拥有一样的被列举和模式设置的行为特性。如果DDSCL_ALLOWMODEX 标志被传给IDirectDraw2::SetCooperativeLevel 函数,DirectDraw将只会列举出Mode 13模式。
你可以象列举其它显示模式那样列举出Mode 13模式,但是你必须在调用IDirectDraw2::EnumDisplayModes函数之前做一次页面能力的检测。要完成这一步,调用IDirectDraw2::GetCaps函数,并且检查DDSCAPS 结构中的DDSCAPS_STANDARDVGAMODE标志。如果该标志不存在,表示不支持Mode 13模式,那么,试图带DDEDM_STANDARDVGAMODES标志的列举将会失败,返回一个DDERR_INVALIDPARAMS错误。
EnumDisplayModes函数现在支持一个新的列举标志棗DDEDM_STANDARDVGAMODES,它会让DirectDraw除了列举出320x200x8的Mode X模式外,还可以列举出Mode 13模式。同样,IDirectDraw2::SetDisplayMode函数也有一个新的标志棗DDSDM_STANDARDVGAMODE,为了将显示模式设置成Mode 13模式而不是320x200x8的Mode X模式,你必须传递此标志。
注意:有些显示卡提供了线性加速的320x200x8模式。在这种显示卡上,DirectDraw将不会列举出Mode 13模式,而列举出线性模式。在这种情况下,如果你试图通过将DDSDM_STANDARDVGAMODE标志传递给SetDisplayMode函数而设置显示模式为Mode 13,函数会调用成功,但是使用了线性模式。这是低分辨率模式替换Mode X模式的一个相似的方式。
Mode 13与页面特性
返回目录
当DirectDraw调用一个应用程序定义的EnumModesCallback回调函数,相应DDSURFACEDESC 结构的ddsCaps成员包含了反映正被列举模式的标志。在Mode X模式中,它会是DDSCAPS_MODEX,在Mode 13模式中,它会是DDSCAPS_STANDARDVGAMODE。这些标志是相互排斥的。如果没有任何一个标志被设置,那么,该模式是线性加速的。这些特征同样可应用于由调用IDirectDraw2::GetDisplayMode函数获得的标志。
使用Mode 13模式
返回目录
因为Mode 13是线性模式,DirectDraw可以给应用程序提供对帧缓冲区的直接访问。与Mode X模式不一样,你可以调用IDirectDrawSurface3::Lock、IDirectDrawSurface3::Blt、和IDirectDrawSurface3::BltFast函数直接访问主页面。
当使用Mode 13模式时,DirectDraw支持一种模拟的IDirectDrawSurface3::Flip函数,这是通过将后台缓存的内容直接复制到主页面来实现的。你可以自己编写这些代码,使用Blt或BltFast函数复制一个后台缓存中的矩形区域到主页面上。
有一个有关锁定和Mode 13的误解。尽管DirectDraw允许对Mode 13 VGA帧缓冲区的直接线性访问,但绝不要认为该缓冲区的地址总是0xA0000,因为DirectDraw可以返回一个指向的地址并不是0xA0000的帧缓冲区的别名虚拟内存指针。同样,绝不要认为Mode 13页面的宽矩(Pitch)是320,因为支持320x200x8加速的显示卡很可能会使用不同的宽矩值。
从DMA中获益
返回目录
本节的内容是关于如何从设备对DMA(直接内存访问)的支持中获得好处,以提高应用程序的运行效果。探讨了以下几个主题。
- 关于DMA设备支持
- 对DMA支持的检测
- 典型的DMA方案
- 利用DMA
关于DMA设备支持
返回目录
某些显示卡可以完成在系统RAM页面中的Blit或其它操作,这些操作一般被称为“直接内存访问(DMA:Direct Memory Access)”。你可以利用DMA支持来加速某些集合操作。举例来说,你可以利用系统设备对DMA的支持,完成一次从系统RAM到显示RAM的Blit操作,而与此同时,处理器正在准备下一帧。为了使用这个功能,你必须承担一些责任,以下将对此进一步详细探讨。
对DMA支持的检测
返回目录
在使用DMA操作之前,你必须检测系统设备是否支持DMA操作,如果支持的话,支持的程度如何。首先调用IDirectDraw2::GetCaps函数查询设备能力,然后检查相应DDCAPS 结构的dwCaps 成员中是否存在DDCAPS_CANBLTSYSMEM标志。如果该标志存在的话,表示显示设备支持DMA。
如果你已知道显示设备支持DMA,你还需要知道显示设备支持的程度如何。这可以通过检查另一些结构成员来完成,它们提供了有关从系统RAM到视频RAM,视频RAM到系统RAM,以及系统RAM到系统RAM的Blit操作的DMA特性。这些特性由12个DDCAPS结构的成员提供,它们依照Blit操作的DMA方式来命名。下面的表展示了这些成员。
系统RAM到视频RAM | 视频RAM到系统RAM | 系统RAM到系统RAM |
dwSVBCaps | dwVSBCaps | dwSSBCaps |
dwSVBCKeyCaps | dwVSBCKeyCaps | dwSSBCKeyCaps |
dwSVBFXCaps | dwVSBFXCaps | dwSSBFXCaps |
dwSVBRops | dwVSBRops | dwSSBRops |
举例来说,系统RAM到视频RAM的Blit特性标志由dwSVBCaps、dwSVBCKeyCaps、dwSVBFXCaps和dwSVBRops成员提供。类似的,视频RAM到系统RAM的Blit特性是由“dwVSB”打头的成员提供;系统RAM到系统RAM的Blit特性是由“dwSSB”打头的成员提供。检查存在于这些成员中的标志,可以确定硬件对Blit操作的DMA方式的支持程度。
就这些成员的Blit类型来说,这些成员中的标志与在dwCaps、dwCKeyCaps和dwFXCaps成员中Blit相关的标志是一致的。例如,dwSVBCaps成员中包含了常规的Blit操作特性,你可以在dwCaps成员中找到同样的标志。同样,dwSVBRops、dwVSBRops和dwSSBRops成员中也提供了有关特定类型的Blit的光栅操作(Raster operation)的光栅操作码。
从这些成员可以检测到的特性中,有一个关键的特性,用来表明系统对异步DMA Blit操作的支持。如果显示设备支持页面间的异步DMA Blit操作,那么DDCAPS_BLTQUEUE标志会被设置在dwSVBCaps、dwVSBCaps或dwSSBCaps成员中(一般来说,你将看到的是从系统RAM到视频RAM页面的最好的支持)。如果该标志不存在,表明系统并不报告支持异步DMA Blit操作。
典型的DMA方案
返回目录
从系统RAM到视频RAM的SRCCOPY(光栅操作码之一,表示“复制”)传送是被硬件支持的最基本的Blit操作类型。因此,该操作最典型的用法是将系统RAM页面中的数据传送到视频RAM的页面中,以备后用。系统RAM到视频RAM的DMA传送与CPU控制的传送(比如:HEL的Blit)一样快速,但是又具有极大的利用价值,因为它们可以与主CPU进行并行的操作。
利用DMA
返回目录
硬件的Blit操作使用的是物理内存地址,而不是虚拟地址,后者是应用程序的本地地址。某些设备驱动要求你提供页面的物理内存地址。这种机制是由IDirectDrawSurface3::PageLock函数实现的。如果设备驱动不需要进行页锁定(Page locking),当你调用IDirectDraw2::GetCaps函数以测定硬件特性时,返回值中将会被设置DDCAPS2_NOPAGELOCKREQUIRED标志。
锁定一个页面可以阻止系统将该页面的物理内存给其它的进程使用,并且保证该页面的物理内存地址始终保持不变,直到调用了相应的IDirectDrawSurface3::PageUnlock函数。如果设备驱动需要进行页锁定,那么,DirectDraw将只能允许系统RAM中页面的DMA操作,该页面是被应用程序锁定的。如果在这种情况下,你不调用IDirectDrawSurface3::PageLock函数,DirectDraw将会使用软件仿真的方式来完成Blit操作。应该注意的是:锁定大块的系统RAM将会导致Windows系统运行阻塞。因此强烈建议,如果你的应用程序不是运行于全屏独占模式,那么,不要调用IDirectDrawSurface3::PageLock函数以锁定大块的系统RAM,而且,当应用程序最小化时,必须及时将这些页面解锁,当然,当应用程序恢复最大化时,你可以再次锁定页面。
管理页锁定的任务完全落在应用程序开发者手中,DirectDraw绝不会来插手来锁定或解锁一个页面。而且,决定究竟将多少页面内存锁定才不会对系统运行产生严重的负面影响,也是对开发者的一项考验。
在窗口模式下使用DirectDraw调色板
返回目录
当应用程序为独占(全屏)模式时,IDirectDrawPalette接口的函数拥有直接写硬件的能力。然而,当应用程序处于非独占(窗口)模式时,IDirectDrawPalette接口的调用的是GDI的调色板函数句柄来与其它应用程序协同工作。
对以下主题的讨论,我们假使桌面是8位调色板模式,并且你已经创建了一个主页面和一个标准的窗口。
- 窗口模式的调色板入口类型
- 在窗口模式下创建调色板
- 在窗口模式下设置调色板入口
窗口模式的调色板入口类型
返回目录
与全屏模式的应用程序不同,窗口模式的应用程序必须与其它的应用程序共享桌面调色板。这就给开发者带来了一些困难,如怎样修改调色板入口,以及如何才能安全的修改等。用于DirectDrawPalette对象和GDI的PALETTEENTRY结构中,包含了一个peFlags成员,它携带了系统如何解释PALETTEENTRY结构的描述信息。
PALETTEENTRY结构是这样定义的:
typedef struct tagPALETTEENTRY { // pe
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY;
前三个成员我们都很熟悉,它们是该入口所代表的颜色的三原色分量的值,分别是红、绿、蓝。最后一个成员peFlags描述了该调色板入口是以下三种类型的哪一种:
- Windows静态入口
- 动态入口
- 非动态入口
Windows静态入口:
在窗口模式中,Windows保留了调色板的前10个(0到9)和后10个(246到255)共20个入口,将它们作为系统保留色来显示窗口,如:菜单条、菜单文字、窗口边框、按钮底色等。为了使你的应用程序与系统保持一致的外观,以及避免破坏其它的应用程序,你有必要保护主页面调色板的这些入口。通常,开发者调用Win32的GetSystemPaletteEntries函数获得系统调色板的入口,然后,在将用户定制的调色板指派给主页面之前,将系统调色板中这20个保留入口的值明确的匹配给该定制调色板。在定制调色板中复制系统调色板的入口项在应用程序初始化时可以正常工作,但如果用户更改了桌面的配色方案,它会变得无法使用。
要避免你的调色板在用户更改了配色方案之后变得面目全非,你可以通过提供一个指向系统调色板的引用,而不是直接指定一个颜色值来保护这些被保留的入口。用这种方法,不管系统对某一个保留入口使用什么样的颜色,你的定制调色板将总会符合系统颜色,而且不需要做任何更新工作。在peFlags成员中使用PC_EXPLICIT标志,使你将定制调色板的入口直接指向一个系统调色板成为可能。当你使用这个标志,系统将不会再认为其它的成员中包含颜色信息,你应该设置peRed成员的值为系统调色板的一个索引,并且将其它成员的值设为0。
举例来说,如果你想要确保你定制调色板的保留入口与系统调色板相一致,你应该使用如下的代码:
// 设置前10项和后10项入口与系统调色板相匹配
PALETTEENTRY pe[256];
ZeroMemory(pe, sizeof(pe));
for(int i=0;i<10;i++){
pe[i].peFlags = pe[i+246].peFlags = PC_EXPLICIT;
pe[i].peRed = i;
pe[i+246].peRed = i+246;
}
你可以强迫Windows仅保留使用调色板入口的第一项和最后一项(0和255),方法是调用Win32的SetSystemPaletteUse函数。在这种情况下,你应该设置0号与255号PALETTEENTRY结构的peFlags成员值为PC_EXPLICIT。
动态入口:
在相应的PALETTEENTRY结构的peFlags成员中设置PC_RESERVED标志,表示调色板的该项入口即为用户所保留,Windows将不会允许任何其它的应用程序将它们的逻辑调色板映射到这个物理入口中。因此,只有你的应用程序能够修改这个入口的颜色值,实现调色板动画,而其它的应用程序却无法修改它。
非动态入口:
在相应的PALETTEENTRY结构的peFlags成员中设置PC_NOCOLLAPSE标志,表示该调色板入口为普通、非动态的调色板入口。PC_NOCOLLAPSE标志通知Windows不要用另一些已经分配了的物理调色板入口来替换该入口。
在窗口模式下创建调色板
返回目录
下面的例程演示了如何在非独占模式(既窗口模式)下创建一个DirectDraw调色板。为了让你的调色板能够正常工作,一个关键的任务是你要设置256个入口(PALETTEENTRY结构)中的每一个入口,然后将其提交给IDirectDraw2::CreatePalette函数。
LPDIRECTDRAW lpDD; // 假定它已经被初始化
PALETTEENTRY pPaletteEntry[256];
int index;
HRESULT ddrval;
LPDIRECTDRAWPALETTE lpDDPal;
// 首先设置Windows的静态入口
for (index = 0; index < 10 ; index++)
{
// 前10个静态入口
pPaletteEntry[index].peFlags = PC_EXPLICIT;
pPaletteEntry[index].peRed = index;
pPaletteEntry[index].peGreen = 0;
pPaletteEntry[index].peBlue = 0;
// 后10个静态入口
pPaletteEntry[index+246].peFlags = PC_EXPLICIT;
pPaletteEntry[index+246].peRed = index+246;
pPaletteEntry[index+246].peGreen = 0;
pPaletteEntry[index+246].peBlue = 0;
}
// 现在,设置用户私有的入口,在这个例程中为前16个
// 这些入口是可以动态修改的
for (index = 10; index < 26; index ++)
{
pPaletteEntry[index].peFlags = PC_NOCOLLAPSE|PC_RESERVED;
pPaletteEntry[index].peRed = 255;
pPaletteEntry[index].peGreen = 64;
pPaletteEntry[index].peBlue = 32;
}
// 现在,设置其余的入口,非动态入口
for (; index < 246; index ++)
{
pPaletteEntry[index].peFlags = PC_NOCOLLAPSE;
pPaletteEntry[index].peRed = 25;
pPaletteEntry[index].peGreen = 6;
pPaletteEntry[index].peBlue = 63;
}
// 所有256个入口已经被填充,下面将其提交,以创建调色板。
ddrval = lpDD->CreatePalette(DDPCAPS_8BIT, pPaletteEntry,
&lpDDPal,NULL);
在窗口模式下设置调色板入口
返回目录
在IDirectDraw2::CreatePalette函数中应用于PALETTEENTRY结构的规则,同样适用于IDirectDrawPalette::SetEntries函数。典型的做法是,保存住你一开始创建的PALETTEENTRY结构组,以后你就不必每次都重建它。在必要的时候,你可以修改这个组中的个别项,然后调用IDirectDrawPalette::SetEntries函数以更新调色板。
在大多数情况下,如果你的应用程序运行于窗口模式,你就不应该去触动任何一个Windows静态入口,否则,后果是你所无法预知的。
对于调色板动画,通常你需要修改的只是PALETTEENTRY结构组的一个子集,然后将这些子集入口提交给IDirectDrawPalette::SetEntries函数,这些入口必须具有PC_NOCOLLAPSE和PC_RESERVED标志。试图对其它的入口进行调色板动画,后果同样是不可预知的。
下面的例程演示了如何在非独占模式(窗口)下进行调色板动画。
LPDIRECTDRAW lpDD;
PALETTEENTRY pPaletteEntry[256];
LPDIRECTDRAWPALETTE lpDDPal; // 以上三个对象为已初始化的
int index;
HRESULT ddrval;
PALETTEENTRY temp;
// 修改调色板入口,将靠前的16个入口进行循环
temp = pPaletteEntry[10];
for (index = 10; index < 25; index ++)
{
pPaletteEntry[index] = pPaletteEntry[index+1];
}
pPaletteEntry[25] = temp;
// 更新调色板,不需要传递指向整个调色板的指针,
// 只用提供那些修改了的入口。
ddrval = lpDDPal->SetEntries(
0, // 该标志必须为0
10, // 要更新的第一个入口
16, // 要更新的入口个数
& (pPaletteEntry[10])); // 数据来源
获得换页和Blit操作的状态
返回目录
当你调用IDirectDrawSurface3::Flip函数,你所期望的结果是将前台缓冲区与后台缓存进行交换。然而,这个函数调用并不一定都会成功,而且如果成功也并不是说就会立即进行换页的,举例来说,如果上一次的换页操作还没有完毕或没有成功,那么这次调用的换页函数将返回一个名为DDERR_WASSTILLDRAWING的错误。对于这个问题,仅仅依靠该函数本身的一个较为简单的解决办法是:用While循环反复调用IDirectDrawSurface3::Flip函数,直到返回DD_OK为止。还应注意的一点是,即使Flip函数调用成功,也不是说马上就能进行换页,换页被显示系统安排在显示器的下一次垂直回扫发生的时候进行,这就是说,换页一定是与显示器的刷新同步进行的。
然而,上面的这种方法其实是非常低效的,因为在循环的过程中,你将有可能多次调用Flip函数,而该函数不管是否成功,都会占用相对较长的CPU时间,于是就会产生这样一种情况:虽然上一次循环调用Flip函数没有成功,而在调用的过程中正在进行的换页完毕了,所以当再次循环调用Flip函数的时候,此时已经离上次换页结束有一段时间了,那么在这两次的换页中将会存在一个较长的时间差(这里所指的时间其实都相当短,可以说是在瞬间完成,其数量级为毫秒,远远超出人的感觉器官的灵敏度)。
最好的方法就是使用另一个用以检测换页操作行进状态的GetFlipStatus函数。该函数相对于Flip函数来说只占用相当短的时间,所以可以使两次换页的时间间隔达到最小。如果上一次的换页尚未结束,并且返回DDERR_WASSTILLDRAWING,你的应用程序可以利用这段时间来进行另外的任务,然后再次调用IDirectDrawSurface3::GetFlipStatus函数检测换页是否完毕。一直到函数返回DD_OK,就表示你被获准可以进行下一次的换页操作了。下面的例程片段演示了这种方法:
while(lpDDSBack->GetFlipStatus(DDGFS_ISFLIPDONE) ==
DDERR_WASSTILLDRAWING)
// 等待上一次的换页操作完毕,
// 应用程序可以在这里进行另外的任务。
;
ddrval = lpDDSPrimary->Flip(NULL, 0);
这种方法打个比方说就是在你攻城之前先派个小兵去打探敌情,直到探得条件已经成熟再大军压境,而不必每次都兴师动众却无功而返。
对于基于窗口模式的应用程序,你会取而代之使用Blit函数,这时,你可以使用另一个与GetFlipStatus函数相类似的IDirectDrawSurface3::GetBltStatus函数以检测正在进行的Blit操作的状态,这个函数与GetFlipStatus函数一样,都会占用相当短的CPU时间,并且立即返回,利用它们可以使你的应用程序达到最快的换页或Blit速度,而其间只有很少的时间损失。
使用Blit进行单色填充
返回目录
Blt函数有很多用法,对你来说最熟悉不过的莫过于将图象从一个页面复制到另一个页面,然而,Blt函数也可用于单色填充却是鲜为人知的。不熟悉DirectDraw的程序员经常使用Win32的FillRect函数来实现这一功能,要知道FillRect是GDI的函数,其速度受到GDI模式的限制,其实是非常低效的。
在页面上,你可以将最常用的颜色作为该页面的底色,使用IDirectDrawSurface3::Blt函数可以为该页面“打底”,或者用该函数来实现清屏(使整个页面为黑色)。例如,如果你的应用程序画面的底色是兰色,首先填充一个DDBLTFX结构,并且设置其dwFillColor成员,该值必须与目标页面的像素格式一致。对于基于调色板的页面来说,该值应该是一调色板索引;对于一个16位RGB像素格式的页面来说,该值应该是一个16位的像素颜色值。然后,调用Blt函数,指定其dwFlags成员为DDBLT_COLORFILL,表示使用该函数的单色矩形填充功能。“打底”之后,你就可以在该页面上绘制图象了。使用DirectDrawSurface的Blt函数是进行单色填充最快捷的一种方法,而调用常规的FillRect函数绘制实心矩形将耗时得多。
应该注意的是,如果页面是8位像素格式的页面,将一个颜色如红色,用RGB(255,0,0)的三原色方式赋给DDBLTFX结构的dwFillColor成员是错误的,你必须给它红色所对应的调色板索引值,可以用DDColorMatch函数获得。DDColorMatch函数是在随微软DirectX SDK附带的Ddutil.cpp文件中提供的,你必须将该文件连结到你的工程中去。如何连结在前面的章节中已有详细的介绍。
下面的例程演示了如何对页面进行清屏,即进行黑色填充:
DDBLTFX ddbltfx;
ddbltfx.dwSize = sizeof(ddbltfx);
ddbltfx.dwFillColor =0 ;
ddrval = lpDDSPrimary->Blt(
NULL, // 目标矩形
NULL, NULL, // 源页面和源矩形
DDBLT_COLORFILL, &ddbltfx);
switch(ddrval)
{
case DDERR_WASSTILLDRAWING:
.
.
case DDERR_SURFACELOST:
.
.
case DD_OK:
.
.
.
default:
}
测定显示硬件的能力
返回目录
DirectDraw使用软件仿真的方式来实现那些用户的显示硬件不支持的DirectDraw函数。为了加速你的DirectDraw应用程序的图象显示,你应该在创建了DirectDraw对象之后,测定用户显示硬件的能力,然后指挥你的应用程序尽可能的利用这些能力。
通过调用IDirectDraw2::GetCaps函数可以测定这些能力。不是所有的需要硬件支持的特性都可以由软件仿真出来。如果你想要利用那些只有某些高级显示设备(如高级的3D图形加速卡,如VooDoo等)才能支持的特性,在你的用户没有装备这些硬件设备时,你必须给他们提供一个可以选择的方案,是退出还是用软件仿真模式来玩,当然,后者是一个不得以而为之的办法,有时候你的程序会变得跟蜗牛爬一样。
在视频RAM中储存位图
返回目录
从视频RAM到视频RAM的Blit操作通常比从系统RAM到视频RAM更有效。因此,你应该在视频RAM中储存尽可能多的用于显示的图象。
目前,绝大多数显示适配器(显卡)都可以在容纳主页面及其后台缓存之后还有一些富余。调用IDirectDraw2::GetCaps函数以获得用户的显示硬件的能力,其中,DDCAPS结构的dwVidMemTotal和dwVidMemFree成员可以用来测定视频RAM中可用内存的数量。如果你想亲眼看一下它是怎样工作的,使用包含在DirectX SDK中的DirectX Viewer(DirectX 查看器)例子。运行后,打开DirectDraw Devices文件夹,再打开主显示驱动程序文件夹,于是就会显示出视频RAM的总量以及空余视频RAM的数量。每当向DirectDraw对象中增加一个页面,空余视频RAM数量将减少,其减少量为该新增的页面所占用的视频RAM。
Triple Buffering(三缓冲)
返回目录
在某些情况下,那就是,当显示适配器(显卡)拥有足够的视频RAM,那么,你就可以能够使用Triple Buffering(三缓冲区)来加速你的应用程序的显示速度。三缓冲区使用一个主页面和两个后台缓存。下面的例子演示了如何初始化一个三缓冲区方案:
// lpDDSPrimary,lpDDSMiddle和lpDDSBack是全局变量,
// 定义成LPDIRECTDRAWSURFACE型。
DDSURFACEDESC ddsd;
ZeroMemory (&ddsd, sizeof(ddsd));
// 创建一个主页面,带有两个后台缓存。
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 2;
ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL);
// 如果我们成功的创建了换页链,
// 取出指向这些页面的指针,用来进行换页和Blit。
if(ddrval == DD_OK)
{
// 得到直接连结到主页面的页面(后台缓存)。
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->GetAttachedSurface(&ddsd.ddsCaps,
&lpDDSMiddle);
if(ddrval != DD_OK) ;
// 在此处显示出错信息。
}
因为在调用Flip函数进行换页的时候,系统会自动将主页面在三缓冲区换页链中的三个页面中循环,所以你并不需要关心三缓冲区换页链中的每一个页面。你只需要保持住指向主页面和紧接主页面的一个后台缓存的指针就可以了。指向主页面的指针用来调用Flip函数进行换页,指向后台缓存页面的指针用来准备将要显示的下一个画面。要得到更多的信息,请参阅“换页”。
建立三缓冲区换页链的好处在于:你可以不用象双缓冲区换页链那样,必须等到换页操作结束后,才能向后台缓存绘制图象(因为换页时页面被锁定,不能被其它读写工作);相反,你可以在调用完Flip函数之后立即对后台缓存进行新的绘制工作,从而使换页成为一种非同步的事件。如果你只使用一个后台缓存,那么在等待Flip函数返回DD_OK前,你的应用程序将让这些空闲的时间白白浪费掉。
DirectDraw应用程序和窗口风格
返回目录
如果你的DirectDraw应用程序是运行于窗口模式的,你可以用任何窗口风格来创建你的窗口。然而,全屏独占模式的DirectDraw应用程序的窗口不能设置有WS_EX_TOOLWINDOW风格,否则可能会遭遇到不可预知的行为。WS_EX_TOOLWINDOW风格原是阻止一个窗口成为顶层窗口,而在DirectDraw的全屏独占模式应用程序中,这却是必须的。
为了正确的显示,全屏独占模式的应用程序最好带有WS_EX_TOPMOST扩展窗口风格和WS_VISIBLE基本窗口风格。这些风格使得DirectDraw应用程序成为所有窗口的上层窗口,并且阻止GDI写向主页面。
下面的例子演示了在全屏独占模式的应用程序中如何正确的初始化一个窗口:
////////////////////////////////////////////////////////
// 注册窗口类,显示窗口,
// 并且初始化所有DirectX和图形对象。
////////////////////////////////////////////////////////
BOOL WINAPI InitApp(INT nWinMode)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.hInstance = g_hinst;
wcex.lpszClassName = g_szWinName;
wcex.lpfnWndProc = WndProc;
wcex.style = CS_VREDRAW|CS_HREDRAW|CS_DBLCLKS;
wcex.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wcex.hIconSm = LoadIcon (NULL, IDI_WINLOGO);
wcex.hCursor = LoadCursor (NULL, IDC_ARROW);
wcex.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU);
wcex.cbClsExtra = 0 ;
wcex.cbWndExtra = 0 ;
wcex.hbrBackground = GetStockObject (NULL_BRUSH);
RegisterClassEx(&wcex);
g_hwndMain = CreateWindowEx(
WS_EX_TOPMOST,
g_szWinName,
g_szWinCaption,
WS_VISIBLE|WS_POPUP,
0,0,CX_SCREEN,CY_SCREEN,
NULL,
NULL,
g_hinst,
NULL);
if(!g_hwndMain)
return(FALSE);
SetFocus(g_hwndMain);
ShowWindow(g_hwndMain, nWinMode);
UpdateWindow(g_hwndMain);
return TRUE;
}
将真彩色匹配到帧缓冲区的色彩空间
返回目录
当显示设备不是处于24位真彩模式时,应用程序需要知道一个真彩RGB颜色是怎样映射到一个帧缓冲区的色彩空间中去的。举例来说,如果你正在编写一个可运行于8、16或24位显示模式的应用程序,在设置关键色或用Blit进行单色填充的时候,你所提供的颜色值必须与该页面当前的色彩空间相一致,也就是与帧缓冲区的色彩空间一致。这就是说,如果你的应用程序运行于8位色彩模式,那么你必须提供一个调色板索引;如果运行于16位色彩模式,你必须提供一个相应的16位值;如果运行于24位真彩模式,你必须提供一个24位值。用户可以选择他所期望的显示模式,那么你的程序就必须可以根据当前的显示模式,将一个特定的颜色值(通常是真彩24位值)映射到正确的色彩空间中去。
尽管DirectDraw不会为你自动完成颜色匹配的任务,但是仍有许多方法来解决如何把你的关键色映射到帧缓冲区中去。这些方法是比较复杂的。在绝大多数情况下,你可以使用GDI内建的色彩匹配服务,加上DirectDraw的直接帧缓冲区访问,来解决将一个24位颜色值匹配到不同的色彩空间中去。实际上,DirectX SDK中的Ddutil.cpp源文件包含了一个DDColorMatch函数,用它就可以完成任何色彩空间的匹配工作。它的工作原理十分巧妙,你并不需要了解每一种色彩空间的格式,它用的是“埋种子”的方法,其步骤如下:
- 调用Win32的GetPixel函数,取页面左上角(坐标为0,0)的像素值。
- 调用Win32的SetPixel函数,用COLORREF结构来描述你的24位RGB颜色,将该色设置给页面左上角。
- 使用DirectDraw的Lock函数锁定页面,取得指向帧缓冲区内存的指针。
- 从帧缓冲区中获得真实的颜色值,调用Unlock函数将页面解锁。
- 还原页面左上角像素颜色为原始值,使用SetPixel函数。
DDColorMatch函数的速度并不是很快,这是因为它调用了Win32的GetPixel和SetPixel函数。然而,它提供了一个可靠和值得信赖的方法来求解颜色在不同的RGB色彩空间中是如何映射的。