页面
页面
返回上级
该节包含了关于DirectDrawSurface对象的信息,探讨了以下主题:
|
|
页面的基本概念
返回目录
1、什么是页面?
页面,或被我们称作DirectDrawSurface对象,代表了内存里的一个连续的线性的数据区。这个数据区可以被代表显示硬件的DirectDraw对象所识别和确认。通常,DirectDrawSurface对象被置于显卡上的视频RAM中,而这并不是绝对的。除非明确的指定是在视频RAM还是系统RAM中创建DirectDrawSurface对象,DirectDraw可以将其放置在其中任一位置,条件是这样可以获得最佳性能。
DirectDrawSurface对象可以从显卡上的特效处理器上获得好处,不仅仅是通常意义上的加快处理速度,而是可以与系统CPU并行工作,以达到最优的效率和速度。
调用IDirectDraw2::CreateSurface函数可以创建若干类型的DirectDrawSurface对象,包括最简单的单页面对象,复杂的由若干个页面组成的换页链,以及三维页面等等。CreateSurface函数创建我们所请求的页面或换页链,并且返回指向主页面的IDirectDrawSurface接口的指针,通过该接口可以暴露DirectDrawSurface对象的函数性。如果你想使用该接口的较高级的版本,如IDirectDrawSurface3,你也可以询问系统并且得到它。
IDirectDrawSurface3接口通过Blit函数可以使你间接的访问页面内存,例如:IDirectDrawSurface3::BltFast函数。DirectDrawSurface对象可以创建Windows的GDI设备环境句柄(HDC),这样,就可以允许使用Win32的API函数来访问代表DirectDrawSurface对象的页面。GDI识别这些HDC(设备环境句柄),如果它们存在于视频RAM中,那么就可以获得硬件的加速特性。除此之外,你还可以使用IDirectDrawSurface3接口的函数直接访问页面内存。例如:可以使用IDirectDrawSurface3::Lock函数锁定页面内存,并且获得指向该页面上相应区域(用户指定的矩形区域)的内存区的地址。视频RAM上的地址可以指向可见的祯缓存(存储了当前显示画面的缓冲区,也称作主页面),也可以是不可见的缓存(离屏页面或覆盖页面)。不可见的缓存通常被置于视频RAM中,但是如果是受硬件限制或DirectDraw正以仿真模式运行,它也可以被置于系统RAM中。IDirectDrawSurface3接口还扩展了另外一些函数,比如可以用来设置或获得调色板的函数,专门用于某特定类型页面的函数(如换页链或覆盖页面)。
从下面这个例图中,你可以看到所有的DirectDrawSurface页面对象都是由DirectDraw对象创建的,并且与调色板协同工作。尽管每一个页面对象都可以被分配一个调色板,除了像素格式的位深度小于等于8的主页面以外,调色板并不总是必须的。
2、页面接口
返回目录
前面已经提到过,DirectDrawSurface对象是通过IDirectDrawSurface、IdirectDrawSurface2和IDirectDrawSurface3接口来暴露其函数性的。接口的每一个新的版本与旧的版本相比,除了提供所有原有的函数并且扩充其功能之外,还提供了一些新的函数。
三种接口中,IDirectDrawSurface接口是最早的一个版本,当你调用IDirectDraw2::CreateSurface函数时,系统会为你缺省的创建一个该接口的页面对象。要利用新版接口的函数性,你必须通过调用QueryInterface函数来询问是否存在新版本,并获得它。下面的例程为你展示了这是怎样完成的。
LPDIRECTDRAWSURFACE lpSurf;
LPDIRECTDRAWSURFACE2 lpSurf2;
//填充页面结构
memset(&ddsd, 0, sizeof(ddsd)); //调用Win32 API函数清空ddsd结构
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
ddsd.dwWidth = 10;
ddsd.dwHeight = 10;
//创建页面,该页面使用IDirectDrawSurface接口
ddrval = lpDD2->CreateSurface(&ddsd, &lpSurf, NULL);
if(ddrval != DD_OK)
return;
//询问IDirectDrawSurface3接口
ddrval = lpSurf->QueryInterface( IID_IDirectDrawSurface3, (LPVOID *)&lpSurf2);
if(ddrval != DD_OK)
return;
//调用IDirectDrawSurface3接口特有的函数
ddrval = lpSurf2->PageLock(0);
if(ddrval != DD_OK)
return;
ddrval = lpSurf2->PageUnlock(0);
if(ddrval != DD_OK)
return;
上面的例子通过调用QueryInterface函数(指定IID_IDirectDraw2引用标志符)获得一个DirectDrawSurface对象的IDirectDrawSurface3接口。要得到IDirectDrawSurface3接口,使用IID_IDirectDrawSurface3引用标志符即可。
3、宽度(Width)和宽距(Pitch)
返回目录
如果你的应用程序要写视频RAM,内存中的位图并不需要占据连续的内存块。在这种情况下,一条线的width和pitch含义是不同的。width是指内存中位图的一条线的开始和结束位置的内存地址之差。这个距离只代表了内存中位图的宽度,它不包括位图中到达下一条线开始位置所需要的任何额外的内存。pitch是指内存中位图的一条线到下一条线开始位置的内存地址之差。
对矩形内存来说,比如,视频RAM的pitch将包括位图的宽度加上一部分缓存。下面的例图表示了矩形内存中width和pitch的区别。
在这个例图中,前台缓存和后台缓存大小都是640x480x8,高速缓存是384x480x8。要到达下一条线的地址,你必须在640后加上384,得到1024,这就是下一条线的地址。
因此,当直接向页面内存中着色时,一般用IDirectDrawSurface3::Lock(或IDirectDrawSurface3::GetDC)方法返回的pitch值。不要认为pitch只是基于显示模式的。如果你的应用程序在某些显示器上发生显示混乱,这多半是因为pitch使用错误造成的。
4、关键色
返回目录
DirectDraw支持带源或目标关键色的Blit操作和覆盖页面。这个关键色可以是单个的颜色值,也可以是一个颜色范围。要得到关于关键色的详细介绍,请参阅前一章的“关键色”一节。通过调用IDirectDrawSurface3::SetColorKey函数,可以为一个页面设置一个关键色。
源关键色(Source color key)指定了一个颜色或一个颜色范围,在Blit过程中,不被复制,或在覆盖页面中,对目标层来说是不可见的。目标关键色(Destination color key)指定了一个颜色或一个颜色范围,在Blit过程中,将被替换,或在覆盖页面中,将被目标层所覆盖。源与目标关键色的一个显著的区别就是:源关键色指定了在源页面上什么是可以读和什么是不可以读的;目标关键色指定了在目标页面上,什么是可以写和什么是不可以写的。如果目标页面有关键色,则只有那些符合关键色的像素可以被改变(在Blit操作中),或被覆盖(在覆盖页面中)。
除了与Blit相关的关键色之外,覆盖页面还可以使用覆盖关键色。要得到更多信息,请参阅“覆盖关键色”。
有些硬件只支持YUV像素数据的颜色范围。YUV数据通常用于视频显示的像素格式,并且其透明背景不是一个特定颜色而导致在数值转换过程中发生错误。所以,只要可能,就应该将数据写到一个特定的透明颜色上,而不管它是什么像素格式。
关键色是按页面的像素格式指定的。如果一个页面是调色板格式,关键色是以一个调色板索引或一组调色板索引指定的。如果页面的像素格式是按FOURCC代码指定的,描述了一个YUV格式,YUV关键色是由DDCOLORKEY结构的dwColorSpaceLowValue 和dwColorSpaceHighValue成员的低三位字节指定的。最低的字节包含V数据,下一个包含U数据,第三个包含Y数据。IDirectDrawSurface3::SetColorKey的dwFlags参数指定了关键色是用在Blit操作中还是覆盖页面中,以及它是源还是目标关键色。以下是一些合法的关键色的例子。
8位调色板模式:
//调色板登录项26是关键色。
dwColorSpaceLowValue = 26;
dwColorSpaceHighValue = 26;
24位真彩模式:
//(255,128,128)颜色是关键色
dwColorSpaceLowValue = RGBQUAD(255,128,128);
dwColorSpaceHighValue = RGBQUAD(255,128,128);
FourCC YUV模式:
//只要Y在100和110之间,并且U或V在50和55之间的的任何一个YUV颜色为透明。
dwColorSpaceLowValue = YUVQUAD(100,50,50);
dwColorSpaceHighValue = YUVQUAD(110,55,55);
5、像素格式
返回目录
像素格式规定了页面内存中的每个像素的数据是怎样进行编码的。DirectDraw使用DDPIXELFORMAT结构来描述各式各样的像素格式(请参阅“DirectDraw参考手册中关于该结构的帮助”)。DDPIXELFORMAT结构中的成员包含了各种像素格式相互区别的以下几个显著的特点:
- 像素格式是基于调色板的还是非调色板式的
- 如果是非调色板式,像素是RGB,还是YUV格式
- 位深度
- 位掩码
通过调用IDirectDrawSurface3::GetPixelFormat函数,你可以获得关于当前页面的像素格式的信息。
创建页面
返回目录
DirectDrawSurface对象代表了一个页面,调用IDirectDraw2::CreateSurface函数可以创建一个DirectDrawSurface对象,也可以同时创建由若干个页面组成的复杂页面结构,最典型的就是换页链。调用该函数时,需要提供要创建的页面的描述,如页面的尺寸、是单个页面还是复杂页面、所采用的像素格式(如果页面将不使用索引调色板)等等。所有这些描述信息存储在一个DDSURFACEDESC结构中,在调用CreateSurface函数时必须提供。如果硬件不支持所请求的页面特性,或是要创建的页面已经存在,则该函数返回一个错误。
要创建单个的页面或若干个页面其实是一项并不复杂的工作,只需要少许的几行代码即可。主要有以下四种类型的页面可以被创建:
- 创建主页面
- 创建离屏页面
- 创建复杂页面和换页链
- 创建超宽页面
在缺省的情况下,DirectDraw试图在本地的视频RAM中创建页面。如果恰好没有足够的本地(local)视频RAM来容纳要创建的页面的话,DirectDraw将试图使用非本地(non-local)的视频RAM(在某些装备了AGP设备的系统上),如果仍旧无法实现,那么DirectDraw将只能将其创建于系统RAM中。当然,在调用CreateSurface函数时,你也可以在相关的DDSCAPS结构中指定适当的标志符向DirectDraw明确的表明你想将页面置于哪种类型的内存中。
创建主页面
返回目录
主页面(primary surface)代表的是在显示器的当前可见屏幕,它在页面描述中具有PRIMARYSURFACE标志符。对于每一个DirectDraw对象来说,你只可能拥有一个主页面。
主页面对用户来说是可见的。当你创建一个主页面时,实际上,你创建的这个DirectDrawSurface对象,访问的是由GDI正在使用的已经可见的页面(即显示屏幕),主页面的大小以及像素格式暗中符合当前显示器的显示模式。因此,尽管创建所有其它类型的页面要求填充DDSURFACEDESC结构的dwHeight 和 dwWidth值以及像素格式,而创建主页面时一定不能自己指定它们,甚至你知道它们与当前屏幕是同样大小,否则,CreateSurface函数将调用失败,并且返回DDERR_INVALIDPARAMS。
要创建一个主页面,DDSURFACEDESC 结构(以下为ddsd )的成员是如下填充的。
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
//告诉DirectDraw哪些成员是可用的。
ddsd.dwFlags = DDSD_CAPS;
//请求一个主页面
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
然后,就可以调用CreateSurface函数并且提供了这个页面描述,如果函数调用成功,可以返回一个指向主页面的指针。如果你想要得到关于这个主页面的尺寸和像素格式信息,应该调用IDirectDrawSurface3::GetSurfaceDesc函数。相关信息,请参阅“显示模式”。
创建离屏页面
返回目录
离屏页面(off-screen surface),通常被用来存储位图,用于后来的将位图图象Blit到主页面或后台缓存上。因为离屏页面是一个相互独立的页面,不与任何对象产生隶属关系,所以你必须指定你所要创建的离屏页面的大小,这是通过在DDSURFACEDESC结构中包含进DDSC_WIDTH和DDSD_HEIGHT标志符,并且给dwWidth和dwHeight成员填充正确的参数完成的。除此之外,你还必须包含进DDSCAPS_OFFSCREENPLAIN标志符以表明创建的是一离屏页面。
在缺省的情况下,DirectDraw将会在视频RAM中创建离屏页面,除非视频RAM容量不够,那么,DirectDraw将会把离屏页面置于系统RAM中。你可以在DDSURFACEDESC结构的dwCaps成员中包含进DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY标志符,以明确的表明你希望将页面置于何处。如果DirectDraw不能满足你所提供的要求,CreateSurface函数将调用失败,并且返回错误。
下面面的例程展示了要创建一个离屏页面,DDSURFACEDESC 结构(以下为ddsd )的成员是如何填充的。
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
//告诉DirectDraw哪些成员是可用的
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
//请求一个离屏页面,大小为100x100
//(这假定了要创建的离屏页面的像素格式将符合主页面的像素格式)
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
dwHeight = 100;
dwWidth = 100;
除此之外,你也可以创建一个页面的像素格式与主页面不同的离屏页面。然而,在这种情况下,有一个缺点棗该离屏页面将只能被限制于系统RAM中。下面的例程片段展示了如何准备DDSURFACEDESC结构的各成员,以用于创建一个8位的调色板式页面(假定当前的显示模式不是8位格式)。
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
ddsd.dwHeight = 100;
ddsd.dwWidth = 100;
ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
ddsd.ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED8;
// 设置页面的位深度为8,但是绝对不要设置任何RGB mask值,
// 因为对调色板式页面来说,该值一定是0。
ddsd.ddpfPixelFormat.dwRGBBitCount = 8;
在DirectX以前的版本中,离屏页面的宽度的最大值被限制于主页面的宽度值之内。而在DirectX 5.0版中,你可以随心所欲的创建任何宽度的离屏页面,如果显示硬件能够承受的话。在请求一个超宽离屏页面时,要小心的是,如果显卡上的内存不能够容纳该页面,页面将被置于系统RAM中。如果你明确的指定在视频RAM中创建超宽页面,而硬件又无法承受的话,调用失败。要得到更多关于超宽页面的信息,请参阅“创建超宽页面”。
创建复杂页面和换页链
返回目录
除了上面介绍的由单独的一个页面组成的主页面和离屏页面之外,你还可以创建由若干个页面组成的复杂页面(complex surfaces),它同样也是由一步调用IDirectDraw2::CreateSurface函数所创建的。如果你在页面描述中设置了DDSCAPS_COMPLEX标志符,那么在调用CreateSurface函数后,DirectDraw除了创建你所明确要创建的页面之外,还将暗中的为你创建一个或多个附加页面。对复杂页面的管理与对单页面的管理基本上是没有区别的:一步调用IDirectDraw::Release函数将释放复杂页面中所有的页面,并且一步调用IDirectDrawSurface3::Restore函数将恢复所有页面。然而,这些暗中创建的页面不能被脱离,即解除隶属关系,要得到更多的信息,请参阅“DirectDraw参考手册”中关于IDirectDrawSurface3::DeleteAttachedSurface函数的帮助。
你所能创建的最常用的复杂页面之一就是换页链(flipping chain)。通常,一个换页链是由一个主页面以及隶属于它的若干个后台缓存组成。DDSCAPS_FLIP标志符表明页面是一个换页链的一部分。创建一个换页链的页面描述中,同样必须包含进DDSCAPS_COMPLEX标志符。
下面的例程片段展示了要创建一个换页链结构,如何填充页面描述的各成员。
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
// 告诉DirectDraw哪些成员是可用的
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
// 请求一个主页面,以及后台缓存数量为1
ddsd.ddsCaps.dwCaps = DDSCAPS_COMPLEX | DDSCAPS_FLIP | DDSCAPS_PRIMARYSURFACE;
ddsd.dwBackBufferCount = 1;
上面的例程构造了一个双缓冲区换页(double-buffered flip)环境:单步调用IDirectDrawSurface3::Flip函数可以交换主页面和后台缓存。如果给DDSURFACEDESC 结构的dwBackBufferCount成员设置了2,将会创建两个后台缓存,在每次调用Flip函数时,主页面将在三个页面间循环,这就构成了一个三缓冲区换页(triple-buffered flip)环境。
创建超宽页面
返回目录
DirectDraw允许你在显卡内存中创建超宽离屏页面(页面的宽度大于主页面)。这只在显示设备支持超宽页面的情况下才能实现。
要判断DirectDraw是否支持超宽页面,调用IDirectDraw2::GetCaps函数,检查你所提供的第一个DDCAPS结构的dwCaps2成员中是否存在DDCAPS2_WIDESURFACES标志。如果存在,表明你的DirectDraw支持超宽页面。
如果你试图在视频RAM中创建一个宽度大于主页面的页面,而DDCAPS2_WIDESURFACES标志并不存在,函数调用将失败,并且返回一个DDERR_INVALIDPARAMS错误。
超宽页面通常被系统RAM页面、视频端口页面、和可执行缓存所支持。
换页
返回目录
DirectDraw里的任何页面都可以构造为换页页面(Flipping surface)。一个换页页面是位于内存里的任何一个可以在前台缓存(front buffer)和后台缓存(back buffer)之间交换的页面,这个换页环境就是我们所称的换页链(flipping chain)。通常,前台缓存指的就是主页面,当然,这并不是绝对的。
典型的,当你调用IDirectDrawSurface3::Flip函数以请求一次换页操作,指向主页面和后台缓存的指针相互交换。这就是说,换页的操作,是通过交换显示设备用来代表页面内存的指针,而不是通过相互复制页面的实际内存来完成的。但是,也有例外的时候,那就是当DirectDraw以仿真的方式进行换页操作时,在这种情况下,它所做的就是简单的相互复制页面内存。只有在后台缓存不能容纳进视频RAM,或硬件不支持DirectDraw的时候,DirectDraw才会以仿真的方式进行换页操作,当然,这只是极其少见的情况。
当换页链中包含了一个主页面和一个以上的后台缓存时,在换页操作中,指向它们的指针将按前后顺序依次转换。如下图所示:
隶属到DirectDraw对象上的其它类型的页面,只要不是换页链中的一部分,在换页过程中都不会受到任何影响。
请牢记,DirectDraw进行换页,是通过交换指向DirectDrawSurface对象的指针。而不是交换DirectDrawSurface对象本身。这意味着,在任何类型的换页方案中,如果你想将图象Blit到后台缓存,你所使用的始终是同一个DirectDrawSurface对象,而不用去考虑原先的后台缓存已经换页到哪儿了。同样的,你应该始终使用主页面作为Flip函数的调用者,以完成一次换页操作,而不用去管最开始的主页面换页到哪儿了。
当换页对象是可见的页面,比如主页面换页链或一个可见的覆盖页面换页链,进行换页的Flip函数与系统CPU是异步执行的。这就是说,在这些可见的页面上,调用Flip函数,它只是简单的告诉显示硬件该进行换页了,并不需要等待换页操作在硬件设备中实际完成后才返回。这是因为显示硬件(显示器)只有在完成一次垂直刷新后才能进行一次换页。所以,Flip函数调用成功,并不意味着换页已经完成,在实际的换页操作进行之前,对即将成为主页面的后台缓存是不能锁定和进行Blit操作的,如果在这时调用以下这些函数,调用将失败,并且返回DDERR_WASSTILLDRAWING的错误,如IDirectDrawSurface3::Lock、IDirectDrawSurface3::Blt、IDirectDrawSurface3::BltFast和IDirectDrawSurface3::GetDC。但是,对于三缓冲区换页环境,最后一个后台缓存仍是可用的。
要让Flip函数成为与系统CPU同步的操作,在调用时指定DDFLIP_WAIT标志即可。
页面丢失
返回目录
当代表页面内存的DirectDrawSurface对象被不得已的释放时,与该对象相关联的页面内存也会被释放。当一个DirectDrawSurface对象丢失其页面内存的时候,它的许多函数将返回DDERR_SURFACELOST,并且不进行任何其它操作。
页面可能被丢失是因为:.显示设备(显示器)显示模式的改变,或另一个应用程序获得了对显卡的独占访问模式,并且释放了显卡上当前被分派其它应用程序的所有页面内存。对页面调用IDirectDrawSurface3::Restore方法可以为这些丢失了内存的页面重新分配内存,并且将这些内存与DirectDrawSurface对象联系上。重建内存并不会使以前存在于该页面上的图象重新显现出来,因此,如果你的页面丢失了其内存,在调用Restore函数重建之后,必须亲手重新绘制所有的图象。
要得到更多资料,请参阅“设置显示模式”。
释放页面
返回目录
与所有的COM接口一样,在你不再需要某页面的时候,你可以通过调用Release方法释放它。
每一个单独创建的页面必须逐个的明确的释放掉。然而,如果页面是通过单步调用IDirectDraw2::CreateSurface或IDirectDraw::CreateSurface函数创建一个多页面结构(例如一个换页链)时暗中形成的,那么,你只需要明确的释放前台缓存就可以了。在这种情况下,所有的后台缓存都被暗中的释放了,指向它们的指针将不再合法。
更新页面属性
返回目录
你可以通过调用IDirectDrawSurface3::SetSurfaceDesc函数来更新一个现存页面的属性。有了这个函数,你可以更改页面的像素格式,还可以使该DirectDrawSurface对象指针重定位,使其指向一块应用程序已经明确分配了的系统RAM。这是很有用的,因为它使得你的页面可以直接使用一个已经存在的缓冲区的数据,而不用进行复制操作。新的页面内存是由客户程序所分配,同样的,这些内存也必须由客户程序释放掉。要得到更多关于如何使用SetSurfaceDesc函数的资料,请参阅“DirectDraw参考手册”中关于此函数的帮助。
在调用IDirectDrawSurface3::SetSurfaceDesc函数时,lpddsd参数必须是一个DDSURFACEDESC结构的地址,描述了新的页面内存并且提供了指向该内存的指针。在这个结构中,你只能设置dwFlags成员为反映了页面内存的地址、大小、宽距、和像素格式的标志符。因此,dwFlags只能是以下标志符的集合:DDSD_WIDTH、DDSD_HEIGHT、DDSD_PITCH、DDSD_LPSURFACE、和DDSD_PIXELFORMAT。
在向DDSURFACEDESC结构中填充数据之前,你必须为新的页面分配内存。你所分配的内存的大小是非常重要的,它不仅要能容纳满足页面的长和宽所需要的内存,还必须能够容纳页面的宽距,宽距必须是WORD(8位)的倍数。应该注意的是,宽距是以字节为单位,而非像素。
在向DDSURFACEDESC结构中填充数据的时候,lpSurface成员是一个指向你刚分配的内存的指针,并且dwHeight和dwWidth成员描述了页面的大小(以像素为单位)。如果你指定了页面的大小,你还必须填充lPitch成员以反映页面宽距的大小。Pitch必须是DWORD的倍数。同样的,如果你指定了宽距,你还必须为其指定一个宽度值。最后,ddpfPixelFormat成员描述了页面的像素格式。如果你没有给这些成员指定新的值,那么,SetSurfaceDesc函数将使用当前页面的原始值,只有lpSurface成员是例外。
在使用IDirectDrawSurface3::SetSurfaceDesc方法的过程中,你还应当注意到这样一些细节,当然,它们只是常识。举例来说,DDSURFACEDESC结构的lpSurface成员必须是一个指向系统RAM的合法的指针(SetSurfaceDesc函数目前还不支持指向视频RAM的指针)。同样,dwWidth和dwHeight成员的值不能为0。最后一点,你不能为主页面或换页链中的任何页面调用此函数。
你可以将同一块内存设置给若干个DirectDrawSurface对象,但是,你必须注意到,这块内存被所有的页面对象所使用,它不会因为某一个页面的释放而被释放掉。
不正确的使用SetSurfaceDesc函数将导致不可预知的行为。因为DirectDrawSurface对象不会释放并不是它分配的页面内存,因此,当页面内存不再需要的时候,将其及时的释放掉是你的责任。但是,不管怎样,当SetSurfaceDesc函数被调用的时候,DirectDraw将释放掉该页面在创建的时候被暗中分配的原始的页面内存。
直接访问帧缓存
返回目录
一个DirectDrawSurface对象允许应用程序通过调用IDirectDrawSurface3::Lock锁定页面以获得对页面内存的直接的访问。当应用程序调用这个函数的时候,需要给lpDestRect参数提供一个指向RECT结构的指针,描述了页面中你所想要直接访问的矩形区域。如果应用程序需要访问整个页面,设置这个参数为NULL即可。两个线程或进程可以同时锁定同一个页面上的若干个矩形区域,条件是这些矩形区域没有相互重叠。
Lock函数调用成功的话,将填充一个DDSURFACEDESC结构,描述了你要正确的访问页面内存所需要的所有信息。如果页面的像素格式与主页面的不一样,该结构中还包含了关于页面的宽距(pitch)和像素格式的信息。当应用程序结束了对页面内存的访问,可以调用IDirectDrawSurface3::Unlock以解锁页面。
当你锁定了一个页面,你就可以对页面内存中的数据进行直接的操作。以下介绍了一些小技巧,可以避免在页面被锁定的过程中,直接向页面内存进行绘图的时候发生的绝大多数一般的错误。
- 决不要假想页面的宽距(pitch)为一恒定值,每次调用IDirectDrawSurface3::Lock函数的时候都要检查返回信息中的宽距值。这个值的改变可以有各种各样的原因,包括页面内存在内存中的位置,显卡的类型,甚至是DirectDraw引擎的版本。
- 确保你要进行Blit操作的目标页面是没有被锁定的。如果对一个锁定的页面调用DirectDraw的Blit函数,调用将失败,并且返回DDERR_SURFACEBUSY或DDERR_LOCKEDSURFACES错误。相似的,如果对视频RAM中一个锁定的页面调用了GDI的Blit函数,调用也将失败,但不会返回错误值。
- 尽量减少你的程序在IDirectDrawSurface3::Lock 和IDirectDrawSurface3::Unlock之间的执行活动。在一个页面被锁定的过程中,DirectDraw通常控制住Win 16锁,于是使得对页面内存的访问得以安全的进行。Win 16锁使对GDI和USER的访问串行化,在Lock和UnLock函数的调用过程中,暂停执行Windows。IDirectDrawSurface3::GetDC函数暗中的调用了IDirectDrawSurface3::Lock以锁定页面,并且IDirectDrawSurface3::ReleaseDC函数暗中的调用了IDirectDrawSurface3::Unlock以使页面解锁。
- 对齐复制到显示存储器。Windows 95使用了一个页错误处理器棗Vflatd.386文件,来为具有堆交换存储器的显示卡实现虚拟的平面桢缓冲区。该处理器允许这些显示设备为DirectDraw提供线性桢缓冲区。如果复制是跨存储堆的,非对齐复制到显示存储器会导致系统挂起。
锁定页面通常导致DirectDraw控制住Win 16锁。在Win 16锁被控制期间,所有其它的应用程序,包括Windows系统,都会暂停执行。因为这个原因,标准的调试器在此期间都不可能工作,但是只有内核(kenerl)调试器仍可以正常工作。
如果在你调用IDirectDrawSurface3::Lock函数的时候,对于该页面的一次Blit操作还在进行之中,函数将立即返回一个错误值。要防止这种情况的发生,可以在调用Lock函数的过程中指定DDLOCK_WAIT标志,以表明该函数将等待,直到成功的获得锁定之后才返回。
使用非本地视频RAM页面
返回目录
DirectDraw支持高级图形端口(AGP)结构,所以它可以在非本地的视频RAM中创建页面。在装备了AGP的系统中,如果本地的视频RAM被耗尽,或用户明确的请求非本地的视频RAM,DirectDraw将使用非本地的视频RAM,这依赖于当时的AGP执行模式。
目前,有两种AGP结构的执行模式,这就是通常所说的“执行模式”(execute model)和“DMA模式(DMA model)”。在执行模式中,对于非本地的视频RAM和本地的视频RAM,显示设备支持同样的特性。因此,当你调用IDirectDraw2::GetCaps方法以获取硬件特性的时候,在DDCAPS 结构的dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps、和dwNLVBRops成员中所包含的与Blit操作相关的标志符,与本地视频RAM的那些标志符是一样的。在执行模式中,如果本地的视频RAM被耗尽,DirectDraw将自动的退回到使用非本地的视频RAM,除非调用者明确的指定使用本地的视频RAM。
在DMA模式执行过程中,对非本地视频RAM的Blit操作和材质贴图(texturing)的支持是有限度的。如果显示设备使用DMA模式,当你询问设备特性的时候,dwCaps2成员将会被设置了DDCAPS2_NNLOCALVIDMEMCAPS标志。DDCAPS结构中的dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps、和dwNLVBRops成员中所包含的Blit相关的标志描述了DirectDraw所支持的特性;这些特性通常是本地的视频RAM页面所支持的特性的一个子集。DMA模式下,当本地的视频RAM被耗尽的时候,对于材质页面,DirectDraw将自动的退回到使用非本地的视频RAM,除非调用者明确的请求使用本地的视频RAM。材质页面是唯一的一种会被这样处理的页面,所有其它类型的页面不能被创建在非本地的视频RAM中,除非调用者明确的指定。
DMA执行模式对材质贴图的支持,因非本地视频RAM页面的不同而不同。如果驱动程序支持从非本地视频RAM页面的材质贴图,当你调用IDirect3DDevice2::GetCaps方法询问3-D驱动程序的特性的时候,D3DDEVCAPS_TEXTURENONLOCALVIDMEM标志符将会被设置其中。
色彩和格式转换
返回目录
非RGB色彩空间的页面格式是由四字符代码(FOURCC codes)定义的。如果应用程序调用IDirectDrawSurface3::GetPixelFormat方法询问页面的像素格式,并且该页面是一个非RGB页面,那么DDPF_FOURCC标志符将会被设置,并且DDPIXELFORMAT结构中的dwFourCC成员的值是有效的。如果页面的四字符代码(FOURCC code)代表了一个YUV格式,那么DDPF_YUV标志符也将被设置,并且dwYUVBitCount、dwYBits、dwUBits、dwVBits、和dwYUVAlphaBits成员的值是有效的,它们可以被用来提取出页面像素格式的信息。
如果当前的页面是RGB格式,那么DDPF_RGB标志符将被设置,并且dwRGBBitCount、dwRBits、dwGBits、dwBBits、和dwRGBAlphaBits成员的值是有效的,它们可以被用来提取出页面像素格式的信息。DDPF_RGB标志符可以与DDPF_FOURCC标志符一起使用,如果被描述的页面是非标准的RGB格式。
在色彩和格式转换过程中,两套四字符代码可以为应用程序所使用。一套代表了Blit硬件的特性,另一套代表了覆盖硬件的特性。
要得到更多的信息,请参阅“DirectDraw参考手册”中的“四字符代码”部分的帮助。
覆盖页面
返回目录
该节包含了关于DirectDraw支持覆盖页面的资料。讨论了以下主题:
- 覆盖页面概览
- DDCAPS的重要成员和标志
- 源和目标矩形
- 边界和大小限制
- 最小和最大缩放系数
- 覆盖页面关键色
- 覆盖页面的定位
- 创建覆盖页面
- 覆盖页面的Z轴次序(Z-Order)
- 覆盖页面的换页
要得到更多的关于执行覆盖页面的资料,请参阅“使用覆盖页面”。
覆盖页面概览
返回目录
覆盖页面(Overlay),通常也被称作重叠页面或覆盖层,是一种需要特定的硬件支持的页面。覆盖页面通常被用于显示实时视频、视频录制、或静止的位图于主页面之上,而不需要进行Blit操作到主页面上或用任何方法改变主页面的内容。覆盖页面完全是由硬件支持的;DirectDraw支持由显示设备驱动程序报告的任何一种特性。DirectDraw不支持以软件仿真的方式实现覆盖页面。
可以用一张透明的塑料片来比喻一个覆盖页面,你可以将它放在显示器屏幕前,并且在上面进行绘图。同样的道理,当覆盖页面位于显示器屏幕的上层时,你可以同时看到覆盖页面和主页面的内容,当你将覆盖页面挪开时,主页面的内容不会发生任何改动。实际上,正如前面提到的那样,一个覆盖页面的执行机制与一张透明的塑料片基本上是一样的。当你要显示一个覆盖页面的时候,你要告诉设备驱动程序在哪儿和怎样去显示这个覆盖页面。当显示设备在显示器上绘制扫描线的时候,它会检查主页面上的每一个像素的位置,来判断该处是否应该让覆盖页面可见。如果是的话,显示设备从覆盖页面上取得该处的像素颜色,并且显示出来。整个过程如下图所示:
通过这个方法,显示适配器在显示屏幕上产生了一个由主页面和覆盖页面合成的图象,并且在提供透明和缩放特效的同时,不用修改任何一个源页面的内容。这个合成的页面被注入视频数据流中,直接显示在屏幕上。因为这是一个悬空(on the fly-我们暂且这么称呼它)的操作过程,并且像素的转换是在硬件层中完成的,所以对用户来说,在显示覆盖页面的过程中,并没有可以察觉的时间损失。除此之外,这个方法还允许将具有不同的像素格式的主页面和覆盖页面合成起来。
你可以通过调用IDirectDraw2::CreateSurface函数创建一个覆盖页面,在相应的DDSCAPS结构中指定DDSCAPS_OVERLAY标志符。覆盖页面只能存在于视频RAM中,所以,你还必须包含DDSCAPS_VIDEOMEMORY标志符。与创建其它类型的页面一样,通过包含进适当的标志符,你可以创建一个单独的覆盖页面,或一个由若干个覆盖页面组成的换页链。
DDCAPS结构中的重要成员和标志
返回目录
通过调用IDirectDraw2::GetCaps方法,你可以获得关于DirectDraw支持的覆盖页面特性的信息。该函数将描述了所有这些特性的信息填充到两个DDCAPS结构中。
当报告硬件特性时,如果硬件对某特定类型的限制加以实施,设备驱动程序会在DDCAPS 结构的dwCaps成员中设置相应的标志符来表明。在获得驱动程序的能力之后,检查dwCaps成员中的标志符,以判断硬件实施了哪些限制。DDCAPS结构中包含了九个描述了硬件对覆盖页面的限制信息的成员。下面的这张表列出了与覆盖页面相关的成员及其相应的标志符。
成员 | 标志符 |
dwMaxVisibleOverlays | 该成员总是可用的 |
dwCurrVisibleOverlays | 该成员总是可用的 |
dwAlignBoundarySrc | DDCAPS_ALIGNBOUNDARYSRC |
dwAlignSizeSrc | DDCAPS_ALIGNSIZESRC |
dwAlignBoundaryDest | DDCAPS_ALIGNBOUNDARYDEST |
dwAlignSizeDest | DDCAPS_ALIGNSIZEDEST |
dwMinOverlayStretch | DDCAPS_OVERLAYSTRETCH |
dwMaxOverlayStretch | DDCAPS_OVERLAYSTRETCH |
dwMaxVisibleOverlays和dwCurrVisibleOverlays成员携带了硬件设备可以显示的覆盖页面的最大个数,以及其中的多少个可以同时被显示出来的数据。
除此之外,硬件设备将覆盖页面矩形的位置和大小限制反映在dwAlignBoundarySrc、dwAlignSizeSrc、dwAlignBoundaryDest、dwAlignSizeDest、和dwAlignStrideAlign成员中。这些成员中的值表明了在显示覆盖页面的时候,你必须怎样控制源和目标矩形的大小和位置。要得到更多的资料,请参阅“源和目标矩形,以及边界和大小限制”。
同样,硬件将缩放系数反映在dwMinOverlayStretch和dwMaxOverlayStretch成员中。要得到更多资料,请参阅“最小和最大缩放系数”。
源和目标矩形
返回目录
要显示一个覆盖页面,调用覆盖页面的IDirectDrawSurface3::UpdateOverlay函数,在dwFlags参数中指定DDOVER_SHOW标志。该方法需要你在lpSrcRect和lpDestRect参数中指定源和目标矩形。源矩形描述了将显示在主页面上的覆盖页面的区域。要请求该方法使用整个页面,设置lpSrcRect参数为NULL即可。目标矩形描述了在主页面上将被用来显示覆盖页面的区域。
源和目标矩形不需要是同样的大小。通常,你所设置的目标矩形是小于或者大于源矩形的,那么在显示出覆盖页面的时候,硬件将会自动的将其进行缩小或放大。
要成功的显示覆盖页面,你可能需要调整源矩形和目标矩形的大小和位置。这是否必要,取决于你的设备驱动所强加的限制。要得到更多的资料,请参阅“边界和大小限制,以及最小和最大缩放系数”。
边界(Boundary)和大小(Size)限制
返回目录
由于不同的硬件的能力所限,在显示覆盖页面的时候,某些设备驱动对源和目标矩形的位置和大小强加了一些限制。要找出某个设备受到了哪些限制,调用IDirectDraw2::GetCaps函数,并且在DDCAPS的dwCaps成员中检查那些与覆盖页面相关的标志。下面的表展示了表明边界和大小限制的的成员极其标志。
类型 | 标志 | 成员变量 |
边界(位置)限制 | DDCAPS_ALIGNBOUNDARYSRC | dwAlignBoundarySrc |
DDCAPS_ALIGNBOUNDARYDEST | DwAlignBoundaryDest | |
大小限制 | DDCAPS_ALIGNSIZESRC | dwAlignSizeSrc |
DDCAPS_ALIGNSIZEDEST | DwAlignSizeDest |
有两种类型的限制:边界限制和大小限制。每一种限制都是以像素为单位(而不是以字节为单位),并且可以提供给源和目标矩形。同样,这些限制会因为覆盖页面和主页面的像素格式的不同而不同。
边界限制影响的是你可以放置一个源或目标矩形的位置。dwAlignBoundarySrc和dwAlignBoundaryDest成员所报告的值分别告诉你应如何对齐相应的矩形的左上角。矩形左上角的x坐标(RECT结构的left成员)必须是所报告的该限制值的整数倍。
大小限制影响的是你可以设置一个源或目标矩形的合法的大小。dwAlignSizeSrc和dwAlignSizeDest成员所报告的值分别告诉你应如何对齐相应矩形的宽度(以像素为单位)。源或目标矩形的宽度必须是这个所报告的值的整数倍。如果你按照最小的缩放系数缩放一个矩形,还必须保证缩放后的矩形仍是满足大小限制的。在缩放完成后,用上舍入的方法(只取不舍)调整矩形的的宽度,而不是下舍入,这样才能保证满足最小缩放系数。要得到更多的资料,请参阅“最小和最大缩放系数”。
最小和最大缩放系数
返回目录
由于硬件的能力所限,某些设备对于覆盖页面从源矩形到目标矩形Blit操作的缩放比例存在一定的限制。DirectDraw将这种限制称之为缩放系数。要获得设备缩放系数的数据,调用IDirectDraw2::GetCaps函数,如果DDCAPS成员中设置了DDCAPS_OVERLAYSTRETCH标志,那么,有关缩放系数的数据就存在dwMinOverlayStretch和dwMaxOverlayStretch成员中。应该注意的是,缩放系数是按照实际数值的1000倍给出的,所以,1300实际上表示缩放系数为1.3,750表示0.75。
对于那些对覆盖页面的缩放不存在限制的设备来说,最小和最大缩放系数的值都被设为0。
最小缩放系数报告的是目标矩形必须至少是源矩形的宽度乘以该最小缩放系数值。如果最小缩放系数比1000大,那么,你所指定的目标矩形必须比源矩形大。举例来说,如果设备报告的最小缩放系数是1300,你必须保证目标矩形的宽度至少是源目标矩形宽度的1.3倍。同样,最小缩放系数小于1000表明目标矩形可以比源矩形小。
最大缩放系数报告了目标矩形必须至多是源矩形的宽度乘以该最大缩放系数。举例来说,如果最大缩放系数为2000,你可以指定目标矩形的大小至多是源矩形大小的两倍。如果最大缩放系数小于1000,那么,你总是必须使目标矩形比源矩形要小,才能正常的实现覆盖页面。
覆盖页面在进行完缩放操作之后,目标矩形还必须满足设备所需要的大小和位置限制。因此,先将目标矩形进行缩放,然后再进行大小和位置的调整是一个好主意。要得到更多的资料,请参阅“边界和大小限制”。
硬件不需要你对目标矩形的高度进行调整。你可以增加一个目标矩形的高度来保证显示正常的外观比例,而不会产生负面影响。
覆盖页面的关键色
返回目录
与其它类型的页面一样,覆盖页面使用了源和目标关键色,以控制完成在不同页面间的透明Blit操作。因为覆盖页面本身在Blit操作时是不可见的,因此,当你调用IDirectDrawSurface3::UpdateOverlay函数的时候,需要一个另外的方法来控制该覆盖页面是如何被显示到主页面之上的。这种要求是通过覆盖页面的关键色来满足的。覆盖页面的关键色,与其它Blit相关的关键色一样,被分为源关键色和目标关键色,可以通过IDirectDrawSurface3::SetColorKey函数来设置。使用DDCKEY_SRCOVERLAY或DDCKEY_DESTOVERLAY标志来指定是源关键色还是目标关键色。覆盖页面可以使Blit操作、覆盖页面关键色、以及覆盖页面显示操作同时正常的进行,两种类型的关键色不会发生相互冲突。
IDirectDrawSurface3::UpdateOverlay函数使用覆盖页面的源关键色来指定在覆盖页面上哪些像素被视为透明,以使在这些像素覆盖下的主页面的内容可以被显示出来。同样的,该函数还使用覆盖页面的目标关键色来指定在主页面上哪些部分可以被覆盖页面所占据。产生的这些视觉效果与由Blit相关的关键色的是一样的。要得到更多的资料,请参阅“透明Blit,以及源和目标关键色”。
覆盖页面的定位
返回目录
在调用IDirectDrawSurface3::UpdateOverlay函数初始化显示一个覆盖页面之后,你可以通过调用IDirectDrawSurface3::SetOverlayPositio函数来重新定位覆盖页面的目标矩形。
必须保证你所指定的位置要满足硬件设备所强加的边界限制。要得到更多的资料,请参阅“边界和大小限制”;同时,也要记住IDirectDraw2::SetOverlayPosition函数不会帮你进行裁减操作,函数中使用的坐标值如果有可能使你的覆盖页面超出目标页面的边缘,将会导致该函数调用失败,返回一个DDERR_INVALIDPOSITION的错误。
创建覆盖页面
返回目录
与其它的页面一样,通过调用IDirectDraw2::CreateSurface函数可以创建一个覆盖页面,条件是必须在DDSCAPS结构中指定DDSCAPS_OVERLAY标志。
对覆盖页面的支持随显示设备的不同而存在着巨大的差异。因此,绝对不要认为存在一个被绝大多数硬件设备所支持的像素格式,必须作好准备与各种各样不同的像素格式打交道。通过调用IDirectDraw2::GetFourCCCodes函数,你可以得到设备驱动所支持的有关非RGB格式的信息。
当你创建一个覆盖页面的时候,先试着创建一个被大多数设备所认可的像素格式,如果失败,再试着使用其它的格式,这种方法是比较好的。
你可以创建一个覆盖页面换页链。要得到更多的资料,请参阅“创建复杂页面和换页链”。
覆盖页面的Z轴排序(Z-order)
返回目录
覆盖页面在DirectDraw中被认为是位于屏幕上所有内容的最上一层,但是,当你需要显示多个覆盖页面的时候,你需要有一个办法来组织这些页面。为此,DirectDraw支持覆盖页面的Z轴排序,来管理这些覆盖页面层次关系和相互裁减。Z轴排序值代表了从主页面到观察者之间的一个抽象的距离。这些值的可能范围是从0,紧靠着主页面的一层,直到40亿,最靠近观察者的一层,并且没有任何两个覆盖页面可以共享同一个Z轴次序值。通过调用IDirectDrawSurface3::UpdateOverlayZOrder可以设置一个覆盖页面的Z轴次序。
目标关键色只受主页面的影响,而不会受到被另一个覆盖页面所覆盖的覆盖页面的影响。源关键色作用于覆盖页面,而不考虑该覆盖页面是否被设置了Z轴次序。
没有指定Z轴次序的覆盖页面,其Z轴次序被默认为0。没有指定Z轴次序的多个覆盖页面间的层次关系是不可预知的,当它们被映射到主页面上时,会产生混乱。
一个DriectDraw对象不能跟踪由另一个应用程序显示的覆盖页面的Z轴次序。
覆盖页面的换页
返回目录
和其它各类型的页面一样,你可以创建覆盖页面的换页链。在创建好一个覆盖页面的换页链之后,你可以通过调用IDirectDrawSurface3::Flip函数实现换页。要得到更多的资料,请参阅“换页”。
利用覆盖页面的软件视频解码在调用Flip函数实现换页的时候,可以使用DDFLIP_ODD和DDFLIP_EVEN标志,以利用减少运动噪声的特性。如果设备驱动支持奇偶(odd-even)隔行换页,在调用GetCaps函数以获取设备驱动能力的DDCAPS结构中将会被设置DDCAPS2_CANFLIPODDEVEN标志。如果是这样的话,那么,在调用IDirectDrawSurface3::UpdateOverlay函数时,你可以包含进DDOVER_BOB标志,以告知设备驱动你希望它使用BOB(摆动)算法来使运动噪声达到最小。随后,在你调用Flip函数实现换页时,你可以带上DDFLIP_ODD或DDFLIP_EVEN标志,设备驱动将会自动的调整覆盖页面的源矩形来补偿不稳定的噪声。
获取设备驱动能力时,如果DDCAPS结构中没有设置DDCAPS2_CANFLIPODDEVEN标志,那么,指定了DDOVER_BOB 标志的UpdateOverlay函数调用将会失败。
要得到更多的关于Bob算法的资料,请参阅“普通视频噪声的解决方案”。
Blit到多窗口
返回目录
你可以使用一个DirectDraw对象和一个DirectDrawClipper对象Blit到由运行于普通控制级的应用程序创建的若干个窗口中。要得到更多的信息,请参阅“对多窗口使用裁剪器”。
创建多个DirectDraw对象,并且让它们Blit到相互的主页面的作法是不推荐的。