DirectX5.0最新游戏编程指南 DirectDraw篇(转)

    DirectDraw是DirectX SDK的主要部分之一,它允许你直接对显示内存操作,支持硬件位块传输、硬件覆盖、表面翻转,并且保持同目前的基于Windows的应用程序和驱动程序兼容。
    DirectDraw是一种软件接口,它除了能直接对显示设备存取外,还保持同Windows图形设备接口GDI(Graphics Device Interface)兼容。对于图形来说,它并不是一种高级应用程序接口。DirectDraw提供了一种设备无关性的方法,使得基于Windows的应用软件和游戏(例如三维图形软件包和数字视频游戏)能直接获取显示设备的特性。
    DirectDraw能工作于各种各样的显示设备,从简单的SVGA显示器到能够提供剪切、拉伸和非RGB格式支持的高级显示设备。DirectDraw接口使得你的应用程序能仿真基本硬件的性能并使用硬件加速特性;硬件不提供的特性将由DirectX来仿真。DirectDraw提供的对显示内存的设备无关性的访问能使你很容易地管理显示内存。你的应用程序只需要识别一些基本的设备属性,它们都是标准的硬件应用,例如RGB和YUV格式的颜色。你不需要调用特殊的过程来使用位块传输或操纵调色板寄存器。使用DirectDraw,你可以很容易地操作显示内存,充
分利用不同类型显示设备的位块传输和颜色解压功能,而不需要依赖于特定的硬件。DirectDraw可以运行在Windows95/NT4.0和以后的版本中。
    DirectDraw的硬件抽象层HAL(Hardware Abstraction Layer)提供了统一的接口,通过该接口,程序可以直接在显示内存或视频内存中工作,获取硬件的最佳性能。
    DirectDraw对视频硬件的性能进行估计,只要可能就会使用硬件提供的特定性能。例如,如果视频卡支持位块传输,DirectDraw就会把位块传输委托给视频卡,CPU不参与位块传输的处理,这就大大提高了程序运行的性能。另外,DirectDraw提供了硬件仿真层HEL(Hardware Emulation Layer ),使得在某些硬件不存在时可以用软件仿真来支持本应该由这些硬件提供的特性。
    DirectDraw运行在Windows 95上,能够利用32位内存的优越性和操作系统提供的“平坦”内存模型。DirectDraw将系统内存和视频内存作为大块存储而不是一小段一小段地使用。另外,DirectDraw还为 Windows图形程序员带来了许多强大的功能:
  .DirectDraw使得在全屏模式下的应用多个后台缓冲区的页翻转变得容易
  .支持窗口模式和全屏模式下的剪切功能
  .支持三维 Z缓冲区
  .支持Z方向的硬件辅助覆盖
  .提供对图象拉伸的硬件的存取
  .同时访问标准的和增强的显示设备内存区域
  .动态调色板、独占式的硬件访问、分辨率切换等
将这些特性结合在一起,你就可以较容易地编制出性能超过基于GDI的标准Windows游戏甚至是MS-DOS下的游戏。

一、DirectDraw的基本图象概念
    DirectDraw提供了一组不同于GDI的图形术语,因此,要用好DirectDraw,首先应该了解其中的有关概念。下面就对DirectDraw中重要的概念作一介绍。
1.1设备无关性位图DIB(Device-Independent Bitmap)
     DirectX使用设备无关性位图DIB作为主要的图形文件格式。一个DIB文件主要保护了如下信息:图象的维数、使用的颜色数、描述颜色的值及描述每一个像素的数据。DIB文件还保护了较少用到的参数,象有关文件压缩的信息和图象的物理维数。DIB文件的扩展名一般是“.BMP”,有时也可能是“.DIB”。
    因为DIB在Windows编程中的应用极其广泛,DirectX SDK已经包含了许多相关的函数。例如,DirectX SDK提供的ddutil.cpp文件中有一个函数,它将Win32和DirectX函数结合在一起,概念是把一个DIB文件装入DirectX表面,代码如下:
extern "C" IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd,
    LPCSTR szBitmap, int dx, int dy)
{
    HBITMAP             hbm;
    BITMAP              bm;
    DDSURFACEDESC       ddsd;
    IDirectDrawSurface *pdds;
    //  This is the Win32 part.
    //   Try to load the bitmap as a resource, if that fails, try it as a file.
    hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, dx, dy, LR_CREATEDIBSECTION);
    if (hbm == NULL)
        hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, dx, dy, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
    if (hbm == NULL)
        return NULL;
    // Get the size of the bitmap.
    GetObject(hbm, sizeof(bm), &bm);
    // Now, return to DirectX function calls.
    // Create a DirectDrawSurface for this bitmap.
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = bm.bmWidth;
    ddsd.dwHeight = bm.bmHeight;
    if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)
        return NULL;
    DDCopyBitmap(pdds, hbm, 0, 0, 0, 0);
    DeleteObject(hbm);
    return pdds;
}

 1.2、绘图表面(Drawing Surface)
    绘图表面接受视频数据并将其作为图象显示出来。在大多数的Windows应用程序中,你可以使用Win32的函数如GetDC来访问绘图表
面。GetDC获取设备上下文DC(Device Context),获得了设备上下文后,就可以重画表面了。然而,Win32的图象函数是由GDI提供的。
GDI是系统的一部分,它提供了抽象层,使得标准的Windows程序向表面绘图。
    GDI的缺点是它并不是为高性能的多媒体软件设计的,它主要用于商业软件如字处理和电子表格软件。GDI提供了对系统内存中的视频缓冲区的访问,但不提供对视频内存的访问。尽管GDI对于大多数的商业软件非常合适,但对于多媒体应用程序和游戏软件则显得太慢了。
    另一方面,DirectDraw 能提供表征真实视频内存的绘图表面,这就意味着当你使用DirectDraw 时,你能够直接向视频内存写数据,使得图象的显示速度足够快。这些表面表征为连续的内存块,使得寻址时更加容易。
1.3、位块传输Blit
   Blit是“Bit Block Transfer”的简写,表示位块传输。它是将内存中一个地址的一块数据传送到另一个地址的一种方法。位块传输经常用于精灵动画中。你可以使用IDirectDrawSurface3::Blt 方法和IDirectDrawSurface3::BltFast方法来执行位块传输。

1.4、页翻转(Page Flipping)和后台缓冲(Back Buffering)
    页翻转是多媒体、动画、游戏软件中的关键。软件页翻转是对卡通画家使图象运动的过程的模拟。例如,画家在一张纸上画了一个人物,然后将其置于下一帧的工作状态,对于每一帧,只很少地改变人物图象。当你快速翻转纸片时,连续的人物图象看起来就成了动画。
    软件中的页翻转类似于上述的过程。首先,你建立了一系列DirectDraw表面,这些设计好的表面准备“翻转”到屏幕。第一个表面被看作是主表面(Primary Surface),在主表面后的所有表面都称为后台缓冲区。应用程序将数据写向后台缓冲区,然后翻转主表面,于是后台缓冲区就显示在片面上了。当系统正在显示图象时,程序就可以向后台缓冲区写数据,这个过程一直持续到动画的结束,它使你快速而高效地将离散的图象变成动画。 DirectDraw可以利用相对简单一些的双缓冲区(一个主表面和一个后台缓冲区),也可以使用较复杂的技术,加入其它的后台缓冲区。使得你能够容易地建立页翻转的程序。

1.5、矩形(Rectangle)
    贯穿DirectDraw和Windows编程的一个最重要的概念是表面上的对象──有界矩形。一个有界矩形由两个点来确定,即左上角和右下角。当以位块传输的方式向屏幕写数据时,大多数的应用程序都使用RECT结构来传送有关有界矩形的信息。RECT结构的定义如下:
typedef struct tagRECT {
    LONG    left;    // This is the top-left corner's X-coordinate.
    LONG    top;     // The top-left corner's Y-coordinate.
    LONG    right;   // The bottom-right corner's X-coordinate.
    LONG    bottom;  // The bottom-right corner's Y-coordinate.
} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

    其中left和top成员变量是矩形左上角的X、Y坐标的值,right和bottom是右下角的X、Y的坐标值。

1.6、精灵(Sprite )
    许多视频游戏都使用了精灵。从最基本的意义上来讲,一个精灵就是在屏幕上移动的图象。精灵画在一个表面上,覆盖在已有的背景上,合成后的图象被送到屏幕上显示出来。
1.6.1、透明位块传输(Transparent Blitting)和Color Key
    精灵动画中的难点在于对非标准矩形的精灵的处理。因为位块传输函数是工作在矩形方式下,你的精灵也必须放在一个矩形之中,而不管它在屏幕上是否矩形。
    精灵图象本身不是矩形的,但却包含在矩形区域中。当用位块传输将图象移动到目的地时,矩形区域中不属于精灵的像素就被“透明”处理。程序员选择任意一种颜色来创建精灵,这种颜色就被用来作为透明度“Color Key”。它是一种不常用的颜色,程序员仅仅用于表示透明度或特定的颜色范围。
    使用IDirectDrawSurface3::SetColorKey方法,你可以为一个表面设定Color Key。设置了Color Key后,调用 IDirectDrawSurface3::BltFast方法来使用已设好的Color Key,而不管像素是否同该Color Key匹配。这种类型的Color Key是源 Color Key。源Color Key禁止“透明”的像素写到目的地,这样,原来的背景像素就保留下来了,使得精灵看起来就是非矩形的了并且精灵还可以在背景上移动。
    另外,你还可以使用一个Color Key来影响目的表面(目的Color Key)。目的Color Key也是表面上的一种颜色,它用于指明像素是否可以被精灵所覆盖。
1.6.2、精灵和修补矩形(Sprite and Patch Rectangle)
    为了产生精灵运动的效果,你在将精灵画到新的位置之前还必须从背景上擦除旧的位置上的精灵图象。当然,也可以重新调入整个背景再重画精灵,但这会大大降低动画的质量。事实上,你可以保留精灵矩形的上一次的轨迹,仅对该位置重画。这种方法称为“修补”(Patching)。
为了修补精灵旧的位置,可以利用原始背景图象(已经调入到一个屏外表面)的一个拷贝来重画这一位置。在这个过程中使用的是位块传输方法,在整个表面的循环中进行,需要耗用的处理时间很少。下面是这一过程的简单步骤:
(1). 设置精灵上一次位置的修补矩形
(2). 使用位块传输将屏外表面中的背景图象的主拷贝补到该位置
(3). 更新精灵是目的矩形,它反映了精灵新的位置
(4). 将精灵位块传输到背景上最新更新的矩形位置
(5).重复
    将DirectDraw提供的强大的图象功能同直线式的C/C++程序结合起来,利用上面的步骤,马上就可以创建一个简单的精灵动画。
1.6.3、边界检查和碰撞检测(Bounds Checking and Hit Detection)
    在精灵动画中,边界检查和碰撞检测是两个非常车间而又相当重要的任务。边界是限制精灵活动的范围,边界检查就是通过RECT结构来检查精灵的位置,判断精灵是否超出了限定的范围。
碰撞检测就是检查多个精灵是否占用了同一个位置。大部分的碰撞检测是检查各个精灵的矩形之间是否有所覆盖。
 

二、DirectDraw的体系
    多媒体软件要求高性能的图象。通过DirectDraw,Microsoft使得对图象敏感性的程序在速度和效率上都比在GDI上有了大大是提高,同时又保持了设备无关性,DirectDraw提供的工具能完成如下的关键任务:
 .操作多个显示表面
 .直接存取视频内存
 .页翻转
 .后台缓冲
 .调色板的管理
 .剪切
另外,DirectDraw在运行时允许你查询显示硬件的性能然后提供显示设备所能够支持的最佳效能。
    DirectDraw提供了基于COM的服务接口,使用最多的是IDirectDraw2、IDirectDrawSurface3、IDirectDrawPalette、IDirectDrawClipper和IDirectDrawVideoPort。除了这些接口外,DirectDraw继续支持以前版本的接口。
    DirectDraw对象表征了显示适配器,通过IDirectDraw和IDirectDraw2接口揭示其方法。在大多数情况下,你可以使用DirectDrawCreate函数来创建一个DirectDraw对象,也可以使用COM函数CoCreateInstance。
创建了DirectDraw对象后,你可以调用IDirectDraw2::CreateSurface方法为该对象创建表面。表面表征了显示硬件上的内存,既可以是视频内存,也可以是系统内存。DirectDraw通过其它的接口扩展了对调色板、剪切及视频端口的支持。

1、DirectDraw的对象类型
    DirectDraw对象既可以是一个单独的对象,也可以是几个对象的组合。最新版本的DirectDraw有以下类型的对象:
1.1、DirectDraw对象
    DirectDraw对象是DirectDraw应用程序的核心,它是你创建的第一个对象。创建了DirectDraw对象后,可以在它的基础上创建其它所有相关的对象。创建DirectDraw对象的函数是DirectDrawCreate。
1.2、DirectDrawSurface对象
    DirectDrawSurface对象表征了一块内存区域,在该区域的数据将作为图象显示在屏幕上或移动到其它表面上。创建DirectDrawSurface对象的方法是 IDirectDraw2::CreateSurface,其它相关的方法可通过接口 IDirectDrawSurface, IDirectDrawSurface2和IDirectDrawSurface3得到。
1.3、DirectDrawPalette对象
    DirectDrawPalette对象(有时也指的是“palette”)表征了一个用于表面的16色或256色的索引调色板,它包含了一系列描述同表面相关的RGB颜色索引值。创建DirectDrawPalette对象的方法是IDirectDraw2::CreatePalette。可以从接口IDirectDrawPalette获取其它的方法。
1.4、DirectDrawClipper对象
    DirectDrawClipper对象(有时指的是“clipper”)帮助你禁止向表面的某一位置或超出表面的位置块写数据,创建DirectDrawClipper的方法是 IDirectDraw2::CreateClipper,其它相关的方法可从接口 IDirectDrawClipper中获取。
1.5、DirectDrawVideoPort对象
    DirectDrawVideoPort对象表征了一些系统只具有的视频端口硬件,该硬件允许直接访问帧缓冲区而不需要访问CPU或使用PCI总线。
你可以先指定IDDVideoPortContainer标志,再调用QueryInterface方法来创建 DirectDrawVideoPort对象。其它相关的方法可从接口IDDVideoPortContainer和IDirectDrawVideoPort中获得。

2、硬件抽象层HAL(Hardware Abstraction Layer)
    DirectDraw通过硬件抽象层HAL提供了设备无关性。HAL是一种特殊的设备接口,由设备生产厂商提供。DirectDraw利用HAL直接在显示设备上工作。应用程序并不同HAL交互。设备制造厂商以Windows95下的16位或32位代码的形式提供HAL,在WindowsNT下,
HAL是32位代码。HAL可以是显示驱动程序的一部分,也可以是通过制造商定义的私有接口同显示驱动程序通信的动态链接库DLL。
DirectDraw HAL由芯片、板卡或OEM(Original Equipment Manufacturer)提供。HAL只提供了设备相关性代码,执行时没有仿真。如果一种功能不被硬件支持,HAL将不把该功能作为硬件性能的一部分处理。另外,HAL不对参数进行证实,DirectDraw会在调用HAL之前完成参数的证实工作。

3、软件仿真(Software Emulation)
    当硬件HAL不支持某一特性时,DirectDraw就尝试用软件来仿真这一特性。该仿真功能是由硬件仿真层HEL(Hardware-Emulation Layer)提供的。显然,软件仿真并不等同于硬件本身具有的特性。你可以用IDirectDraw2::GetCaps方法来查询硬件所支持的特性。
    有时,硬件固有的特性和软件仿真二者的结合比单独使用软件仿真的速度还要慢。例如,如果显示设备驱动程序支持DirectDraw但不支持拉伸块写方式,当从视频内存表面拉伸或位块传输时就会产生性能上的损失。因为视频内存通常比系统内存的速度要慢,在访问视频内存时CPU必须要等待。因此,如果你的应用程序使用了硬件未能提供的特性,最好在系统内存中创建表面,以避免CPU访问视频内存时出现的性能损失情况。

4、系统组成(System Integration)
    下图显示了DirectDraw、图形设备接口GDI、硬件抽象层HAL、硬件仿真层HEL和硬件之间的关系。
 
如图中所示,一个DirectDraw对象同GDI是并列关系,两者都通过设备相关抽象层访问硬件。同GDI不同的是,DirectDraw只要有可能就会充分利用硬件提供的特有功能。如果硬件不支持某一特性,DirectDraw还会尝试用HEL来仿真。DirectDraw也能以设备上下文(Device Context)的形式提供表面内存,是你能够使用GDI函数来处理表面对象。


三、DirectDraw的要素
    DirectDraw中最基本的要素有协作等级、显示模式、DirectDraw对象、表面、调色板、剪切板等,下面对各个要素逐一进行介绍。
1、协作等级(Cooperative Level)
   协作等级描述了DirectDraw如何同显示交互及对那些可能影响显示的事件的反应。你可以使用IDirectDraw2::SetCooperativeLevel方法来设置协作等级。在大多数的情况下,你可以使用协作等级来确定应用程序是否运行在独占全屏模式或窗口模式。DirectDraw协作等级还具有以下作用:
 .使DirectDraw能够使用X模式(Mode X)分辨率。
 .如果用户按了ctrl + alt + del,禁止DirectDraw释放对显示的独占控制和重新启动(仅在独占模式下)。
 .使DirectDraw能够最大化和最小化以响应激活的事件。
    标准的协作等级指明了DirectDraw程序是一个窗口应用程序。在这种情况下,你不能改变主表面的调色板和执行页翻转。另外,你还不
能调用那些对显示或视频内存影响程度大的方法,如 IDirectDraw2::Compact等。
在全屏独占协作等级下,你可以使用硬件的所有性能,设置惯用调色板和动态调色板,改变显示分辨率,紧凑内存和实现页翻转等。全屏独占模式不禁止其它的应用分配表面,也不禁止它们使用DirectDraw和GDI,但禁止其它发应用改变显示分辨率和调色板。
    因为应用程序可以在多窗口使用DirectDraw,所以当应用要求工作在DDSCL_NORMAL模式时,IDirectDraw2:: SetCooperativeLevel方法并不需要一个指定的窗口句柄。将NULL传递给窗口句柄,所有的窗口都能同时工作在标准窗口模式下。

2、显示模式(Display Modes)
    显示模式是对显示硬件从主表面传送给显示器的图象的大小和位深度(bit-depth)的描述。显示模式可以刻画为:宽、高和位深度。例如,大多数的显示适配器都能显示宽640个像素,高480个像素,每个像素的颜色值为8位的数据图象,该显示模式就几为640x480x256。
要想得到更大的分辨率或位深度,就需要更多的显示内存。
    显示模式有两种类型:调色模式和非调色模式。对于调色显示模式,每一个像素都是一个指向相关调色板的索引值。显示模式的位深度决定了调色板中能够具有的颜色的数目。例如,对于8位调色显示模式,每一个像素都是一个从0到255的值。在这种显示模式下,调色板可以包含256中颜色。非调色显示模式则不使用调色板,该模式下的位深度指明了用于描述一个像素的总的位数。
    主表面和主翻转链中的任何表面都应该和显示模式的大小、位深度和像素格式匹配。
2.1、检测所支持的显示模式
    因为显示硬件的不同,并不是所有的设备都支持所有的显示模式。要检测系统所支持的显示模式,需要调用IDirectDraw2::EnumDisplayModes方法。通过设定适当的值和标志, IDirectDraw2::EnumDisplayModes方法可以列出所有支持的显示模式,也能判断是否支持某一指定的显示模式。
    该方法的第一个参数dwFlags控制方法其它的选项,通常可以将dwFlags设为0来忽略其它的选项;第二个参数lpDDSurfaceDesc是用以描述所给显示模式的结构 DDSURFACEDESC的地址,一般将其设为NULL可以列出所有的模式。第三个参数lpContext是一个指针,DirectDraw需要将该指针传递给回调函数,如果不需要在回调函数中有额外的数据,可将其设为NULL。最后一个参数是lpEnumModesCallback,它是DirectDraw对于每一种支持的显示模式调用的回调函数的地址。
    在调用IDirectDraw2::EnumDisplayModes方法时提供的回调函数必须同EnumModesCallback函数的原型相匹配。对于硬件所支持的每一种显示模式,DirectDraw调用你的回调函数来传递两个参数,第一个是DDSURFACEDESC结构的地址,该结构描述了一种支持的显示模式;
第二个参数是调用IDirectDraw2::EnumDisplayModes时指定的应用定义的数据的地址。
    通过检查DDSURFACEDESC结构的值来得到它所描述的显示模式,其中关键的成员是dwWidth、dwHeight、和ddpfPixelFormat。dwWidth和dwHeight描述了显示模式的大小;ddpfPixelFormat是一个DDPIXELFORMAT结构,它包含了有关位深度的信息。
    DDPIXELFORMAT结构包含了描述显示模式位深度的信息,并且说明该显示模式是否使用调色板。如果dwFlags成员包含了 DDPF_PALETTEINDEXED1、DDPF_PALETTEINDEXED2、DDPF_PALETTEINDEXED4或DDPF_PALETTEINDEXED8标志,该显示模式的位深度就是1、2、4或8位,并且每个像素都是一个相公调色板的索引。如果dwFlags包含了DDPF_RGB标志,该显示模式就是非调色显示模式,并且其位深度由DDPIXELFORMAT结构中的dwRGBBitCount成员提供。
2.2、设置显示模式
    你可以调用IDirectDraw2::SetDisplayMode方法来设置显示模式。该方法接受四个参数来设置显示模式的分辨率的大小、位深度和刷新率。它使用第五个参数来指定给定模式的特殊选项,目前仅用于13模式和X模式320x200x8中。
    你可以指定期望得到的显示模式的位深度,但不能指定显示硬件用于该位深度的像素格式。要检测显示硬件用于当前位深度的RGB位屏蔽,可以在设置显示模式后调用 IDirectDraw2::GetDisplayMode方法。如果当前显示模式是非调色模式,你可以通过检查dwRBitMask、dwGBitMask和dwBBitMask中的值来获得教正的红、绿、蓝颜色位。
    你还可以通过多个应用来改变显示模式,只要它们都共享同一个显示卡。也可以改变位深度,只要应用程序能独占式地访问DirectDraw对象,所有的DirectDrawSurface对象在显示模式改变后都会释放表面内存,所以在更改了显示模式之后,必须使用 IDirectDrawSurface3::Restore方法重新为对象分配表面内存。
2.3、恢复显示模式
    如果显示模式是通过调用IDirectDraw2::SetDisplayMode方法(而不是 IDirectDraw::SetDisplayMode方法)完成的,你可以调用IDirectDraw2::RestoreDisplayMode方法来恢复到原来的显示模式。如果应用程序是独占式的协作等级,当你将应用程序的协作等级设回标准时,显示模式就会自动恢复为原来的模式。如果使用了DirectDraw接口,你必须显式地恢复显示模式。
2.4、X模式和13模式
    DirectDraw支持X模式和13模式两类显示模式。13模式是320x200,每个像素是8位的调色模式,其16进制BIOS模式号为13。模式X是从标准的VGA 13模式衍生出来的混合模式,该模式允许使用256KB的显示内存(13模式仅允许实用64KB的显示内存)。
    在Windows95下,DirectDraw对所有的显示卡提供两种X模式(320x200x8和320x240x8)。一些显示卡也支持线性低分辨率模式,在线性低分辨率默默上下,主表面能够被锁定也能直接访问,这在X模式下是不允许的。
   只要应用程序调用IDirectDraw2::SetCooperativeLevel方法时使用了 DDSCL_ALLOWMODEX,、DDSCL_FULLSCREEN和DDSCL_EXCLUSIVE标志,X模式就可用。如果没有指定 DDSCL_ALLOWMODEX标志, IDirectDraw2::EnumDisplayModes方法就不能列出X模式,当请求X模式时,对IDirectDraw2::SetDisplayMode方法的调用就会失败。
    当应用程序在X模式时不能使用IDirectDrawSurface3::Lock或IDirectDrawSurface3::Blt方法锁定主表面或向主表面块写数据,也不能在主表面或屏幕DC的GDI使用 IDirectDrawSurface3::GetDC方法。X模式由DDSCAPS肩胛骨中的DDSCAPS_MODEX标志来指定,DDSCAPS结构是调用IDirectDrawSurface3::GetCaps和IDirectDraw2::EnumDisplayModes方法返回的DDSURFACEDESC结构中的一部分。
2.5、高分辨率和真彩色
     DirectDraw支持由显示设备驱动程序支持的所有的屏幕分辨率和位深度。DirectDraw允许应用程序改变显示模式到任一种计算机显示驱动程序支持的显示模式,包括24位和32位(真彩色)模式。
     DirectDraw也支持真彩色表面的HEL位块传输。如果显示设备驱动程序在这些分辨率下支持位块传输,显示内存──显示内存的位块传输将使用硬件块写方式,否则将使用HEL来提高位块传输的性能。
     Windows95和NT允许你指定正在使用的显示器的类型。DirectDraw检查已知显示模式的列表,如果DirectDraw检测到要求的模式同该显示器不兼容,对 IDirectDraw2::SetDisplayMode方法的调用将会失败。当你调用方法IDirectDraw2::EnumDisplayModes时,只有显示器支持的模式才会被列出来。

3、DirectDraw对象
     DirectDraw对象是所有DirectDraw应用程序的核心,也是Direct3D应用程序的一个有机组成部分。DirectDraw对象是你创建的第一个对象,通过该对象再创建其它相关对象。一般通过调用DirectDrawCreate函数来创建一个DirectDraw对象,该函数返回一个IDirectDraw接口。
     DirectDraw对象表征了显示设备。如果该显示设备支持硬件加速功能,DirectDraw对象还能利用硬件的加速功能。每一个DirectDraw对象都能处理显示设备和创建依赖于该DirectDraw对象的表面对象、调色板对象和剪切板对象。例如,创建一个表面需要调用IDirectDraw2::CreateSurface方法,若要将一个调色板对象附在表面上,需要调用 IDirectDraw2::CreatePalette方法。另外, IDirectDraw2接口还包含了类似的创建剪切板对象的方法。
    你可以同时创建一个DirectDraw对象的多个实例。最简单的例子是在Windows95系统中使用双显示器。尽管Windows95目前不支持多显示器,但为每一个显示设备写DirectDraw硬件抽象层是可能的。Windows95和GDI能够识别的显示设备在创建缺省的DirectDraw对象的实例时将会被用到。Windows95和GDI不能识别的显示设备能被另一个独立的DirectDraw对象所表征,该对象必须用第二个显示设备的全局统一标识符GUID(Globally Unique Identifier)来创建,此GUID可以通过函数DirectDrawEnumerate获得。
    DirectDraw对象管理它所创建的其它所有对象。它控制缺省调色板、Color Key和硬件显示模式,它还对已经分配的资源和保留的资源做上标记。
    新版本的DirectDraw在以前的版本中增加了新的内容。IDirectDraw2接口通过增加了IDirectDraw2::GetAvailableVidMem方法而对IDirectDraw接口进行了扩展。该方法使你能够查询显示设备总的可用视频内存及其中有多少内存供一个指定的表面使用。
IDirectDraw2::SetCooperativeLevel方法同IDirectDraw2::SetDisplayMode之间的交互作用同IDirectDraw接口中的这些方法有所不同。如果应用程序使用IDirectDraw接口设置全屏独占方式的协作等级并且改变显示模式,当返回标准协作等级时,显示模式不会自动恢复,你必须调用
IDirectDraw::RestoreDisplayMode方法来显式地恢复。如果使用了IDirectDraw2接口,对RestoreDisplayMode的调用就不是必须的。然而IDirectDraw2::RestoreDisplayMode方法不支持显式地恢复原先的显示模式。
作为DirectX的基础的COM指明了一个对象可以通过增加新的接口来提供新的功能而不影响其向后兼容性。因此IDirectDraw2接口就可以代替IDirectDraw接口。新的接口可以通过IDirectDraw::QueryInterface方法获得,如下面的C++代码所示:
// Create an IDirectDraw2 interface.
LPDIRECTDRAW  lpDD;
LPDIRECTDRAW2 lpDD2;
 
ddrval = DirectDrawCreate(NULL, &lpDD, NULL);
if(ddrval != DD_OK)
    return;
 
ddrval = lpDD->SetCooperativeLevel(hwnd,
    DDSCL_NORMAL);
if(ddrval != DD_OK)
    return;
 
ddrval = lpDD->QueryInterface(IID_IDirectDraw2,
    (LPVOID *)&lpDD2);
if(ddrval != DD_OK)
    return;
例中创建了一个DirectDraw对象,然后调用IDirectDraw接口的 IUnknown::QueryInterface方法创建一个IDirectDraw2接口。
    获取了一个IDirectDraw2接口后,就可以调用它的方法以利用其新特性的优点。因为一些方法可能会在新版本的接口中有所变化,因此混合使用不同版本的接口(例如IDirectDraw和IDirectDraw2)可能会导致不可预测的错误。
3.1、每一进程中的多个DirectDraw对象
     DirectDraw允许在一个进程中多次调用DirectDrawCreate函数。每次调用之后对每个唯一独立DirectDraw对象都返回一个唯一的独立的接口。每个DirectDraw对象都可以用于所需,对象之间没有依赖关系。每个对象就象是单独在进程中创建的一样。
    每一个DirectDraw对象创建的DirectDrawSurface对象、DirectDrawClipper 对象和DirectDrawPalette对象都不能被其它的DirectDraw对象使用,因为这些对象在其父对象DirectDraw对象撤消时都会自动撤消。
    使用DirectDrawCreateClipper函数创建的DirectDrawClipper对象是一例外,这种情况下的DirectDrawClipper对象独立于其它任意的DirectDraw对象并能用于一个或多个DirectDraw对象。
3.2、用CoCreateInstance创建DirectDraw对象
    你也可以不使用DirectDrawCreate函数而使用CoCreateInstance函数和 IDirectDraw2::Initialize方法来创建DirectDraw对象。下面是用CoCreateInstance函数创建DirectDraw对象的步骤:
i. 在应用程序开始时调用CoInitialize初始化COM:
if (FAILED(CoInitialize(NULL)))
    return FALSE;
ii.   使用CoCreateInstance函数和IDirectDraw2::Initialize方法创建DirectDraw对象:
ddrval = CoCreateInstance(&CLSID_DirectDraw,
    NULL, CLSCTX_ALL, &IID_IDirectDraw2, &lpdd);
if(!FAILED(ddrval))
    ddrval = IDirectDraw2_Initialize(lpdd, NULL);
    在对CoCreateInstance的调用中,第一个参数CLSID_DirectDraw是DirectDraw驱动对象类的类标志符, IID_IDirectDraw2参数指明了要创建的DirectDraw接口,lpdd参数指向获取的DirectDraw对象。如果调用成功,函数将返回一贯未初始化的DirectDraw对象。
    在使用DirectDraw对象之前,还必须调用IDirectDraw2::Initialize方法,该方法不使用GUID参数(DirectDrawCreate函数则需要只要)。
DirectDraw对象初始化后就可以使用也可以释放该对象,就好象是该对象是由DirectDrawCreate函数创建的一样。若在调用IDirectDraw2::Initialize方法之前使用同DirectDraw对象相关的方法将会导致一个DDERR_NOTINITIALIZED错误。
    在关闭应用程序前,应该使用CoUninitialize函数关闭COM,形式如下:
CoUnitialize();

4、表面(Surface)
    一个表面或DirectDrawSurface对象表征了一块线性内存区域。表面通常都驻留在显示内存中,当然也可以存在于系统内存中。除非明确指定,在创建DirectDrawSurface对象时,DirectDraw会把DirectDrawSurface对象放置于能够获得最佳性能的位置。DirectDrawSurface对象还能利用显示卡上的特殊处理器,不仅可以加快任务的执行速度,还可以同系统CPU并行处理一些任务。
    使用IDirectDraw2::CreateSurface方法,你可以创建单一的表面对象,复杂的表面翻转链和三维的表面。使用CreateSurface方法创建要求的表面或翻转链并且获得主表面的IDirectDrawSurface接口的指针。
    IDirectDrawSurface3接口使你能够用位块传输方法直接访问内存,如 IDirectDrawSurface3::BltFast方法等。表面对象能提高一个设备上下文,它可以让你使用GDI函数。另外,你可以使用IDirectDrawSurface3方法之间访问显示内存。例如,你可以使用IDirectDrawSurface3::Lock方法锁定显示内存并获取相关表面的地址。显示内存的地址可能指向可见缓冲区内存(主表面)或不可见缓冲区(屏外表面或覆盖表面)。
不可见缓冲区通常驻留在显示内存中,但可在系统内存这创建,如果硬件允许或DirectDraw使用了软件仿真功能的话。另外,IDirectDrawSurface3接口扩展了一些方法。利用这些方法,能够放置或获取调色板工作于指定类型表面。
4.1、表面接口(Surface Interface)
     DirectDrawSurface对象通过IDirectDrawSurface、 IDirectDrawSurface2,和IDirectDrawSurface3接口来表现其功能。每一个新版本的接口都提高了同以前版本同样的功能,并且通过新的方法提供了旧版本中不曾具有的功能。
IDirectDrawSurface接口上该类接口中最老的版本,当你使用IDirectDraw2::CreateSurface方法创建一个表面时,缺省提供的就是这一接口。
要利用另一版本的接口提供的新的功能,你还必须用QueryInterface方法查询新版本的接口。下面的代码显示了如何完成这一工作的:
LPDIRECTDRAWSURFACE  lpSurf;
LPDIRECTDRAWSURFACE2 lpSurf2;
   // Create surfaces.
memset(&ddsd, 0, sizeof(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;
   
ddrval = lpDD2->CreateSurface(&ddsd, &lpSurf,  NULL);
if(ddrval != DD_OK)
     return;
 
ddrval = lpSurf->QueryInterface(
      IID_IDirectDrawSurface2, (LPVOID *)&lpSurf2);
if(ddrval != DD_OK)
     return;
 
ddrval = lpSurf2->PageLock(0);
if(ddrval != DD_OK)
      return;
 
ddrval = lpSurf2->PageUnlock(0);
if(ddrval != DD_OK)
      return;
 
    上面的例子在调用QueryInterface方法时通过指定IID_IDirectDraw2标志获得了DirectDrawSurface对象的IDirectDrawSurface2接口。要想得到 IDirectDrawSurface3接口,用IID_IDirectDrawSurface3标志代替IID_IDirectDraw2即可。
4.2、Color Key
    DirectDraw支持位块传输和覆盖表面方式下的源Color Key和目的Color Key。通过调用IDirectDrawSurface3::SetColorKey方法来设置表面的Color Key。当使用位块传输时,源Color Key指明了不被拷贝的颜色或颜色范围,目的Color Key指明了被替换的颜色或颜色范围。
    一些硬件只支持YUV像素格式的颜色范围。YUV数据通常是视频数据,透明背景由于转换中的量化误差可能不是一种单一的颜色。
    Color Key用表面的像素格式来指定。如果表面是调色格式的,Color Key就被指定为索引或索引的范围。如果表面的像素格式由描述YUV格式的FOURCC代码指定,YUV的Color Key就由三个DDCOLORKEY结构中的两个成员dwColorSpaceLowValue和dwColorSpaceHighValue三个字节指定,三个字节分别是V、U、Y数据。IDirectDrawSurface3::SetColorKey方法中的dwFlags参数指明了Color Key是否用于覆盖或
位块传输操作,是否是一个源Color Key或目的Color Key。下面的代码是有效的Color Key的一些实例:
// 8位调色模式
// 调色板实体26是 color key.
dwColorSpaceLowValue = 26;
dwColorSpaceHighValue = 26;
 
// 24位真彩模式
// 颜色255、128、128是color key.
dwColorSpaceLowValue = RGBQUAD(255,128,128);
dwColorSpaceHighValue = RGBQUAD(255,128,128);
 
// FourCC YUV模式
// Any YUV color where Y is between 100 and 110
// and U or V is between 50 and 55 is transparent.
dwColorSpaceLowValue = YUVQUAD(100,50,50);
dwColorSpaceHighValue = YUVQUAD(110,55,55);

4.3、像素格式(Pixel Format)
    像素格式指出了任何对表面内存中每个像素数据作出解释。DirectDraw使用DDPIXELFORMAT结构描述各种像素信息。
DDPIXELFORMAT结构的成员描述了像素格式的下述特性:
 .调色或非调色像素格式
 .如果是非调色格式,像素格式是RGB还是YUV格式
 .位深度
 .像素格式组成部分的位屏蔽
你可以调用IDirectDrawSurface3::GetPixelFormat方法来获取存在的表面的像素格式。

4.4、创建表面
    DirectDrawSurface对象表征了驻留在显示内存中的一个表面。如果显示内存用完了或者是显式地创建,该表面也可存在于系统内存中。
你可以使用IDirectDraw2::CreateSurface方法创建一个或多个表面。调用CreateSurface时,必须指定表面的大小、表面类型(是单一表面还是复杂表面)、像素格式(如果表面不使用索引的调色板)。所有的这些特性都包含在DDSURFACEDESC结构中,在调用时需要将该结构的地址传送过去。如果硬件不支持请求的特性或者此前已经将那些资源分配给了另一个DirectDrawSurface对象,调用就会失败。
    创建单一的表面或多表面只需要几行简单的代码。创建表面有四个主要的步骤。每一个步骤都需要比前一个步骤更多的准备工作,不过并不太难,它们是:
 (1). 创建主表面
 (2). 创建一个屏外表面
 (3). 创建复杂表面和翻转链
 (4). 创建宽表面
    在缺省的情况下,DirectDraw在本地视频内存创建一个表面,如果足够的本地视频内存保存该表面,DirectDraw就尝试利用非本地视频内存(仅在一些AGP设备系统中)。你可以在调用CreateSurface时对DDSCAPS结构赋以适当的标志来显式地指明在哪类内存中创建表面。
4.4.1、创建主表面
    主表面是当前在显示器上可见的并且由DDSCAPS_PRIMARYSURFACE标志指明的表面,每一个DirectDraw对象只能有一个主表面。
当你创建一个主表面时,其大小应该同当前的显示模式匹配。因此,在这种情况下你不需要指明表面的大小。事实上,如果你指明了表面的大小,即使同当前的显示模式匹配,也会导致创建过程的失败。
    下面的例子显示了如何设置DDSURFACEDESC结构的相关成员来创建主表面:
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
   // Tell DirectDraw which members are valid.
ddsd.dwFlags = DDSD_CAPS;
    // Request a primary surface.
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
 
4.4.2、创建屏外表面
    屏外表面通常用于位图的缓存,该位图将被位块传输到主表面或后台缓冲区中。你必须设定包含DDSC_WIDTH和DDSD_HEIGHT标志并设定dwWidth和dwHeight成员 为适当的值来说明屏外表面的大小。另外,还必须在DDSCAPS结构中包含DDSCAPS_OFFSCREENPLAIN标志。
    当没有足够的显示内存使用时,DirectDraw就使用系统内存来创建表面。你可以在DDSCAPS结构中的dwCaps成员包含DDSCAPS_SYSTEMMEMORY或 DDSCAPS_VIDEOMEMORY标志来显式地指明是在显示内存还是在系统内存中创建表面。下面的例子显示了创建一个屏外表面之前的准备工作:
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
 
// Tell DirectDraw which members are valid.
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
 
// Request a simple off-screen surface, sized
// 100 by 100 pixels.
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
dwHeight = 100;
dwWidth = 100;
    在DirectX以前的版本中,屏外表面的最大宽度不能超过主表面的宽度。在DirectX 5中,你可以创建任意宽度的屏外表面,只要显示设备指出就行。需要注意的是,当声明了屏外表面的宽度时,如果显示内存容不下请求的表面的宽度,该表面将换在系统内存中创建。如果显式地声明使用视频内存,而视频内存又不够时,创建表面的工作就会失败。
4.4.3、创建复杂表面(Complex Surface)和翻转链(Flipping Chain)
    一个复杂表面就是用IDirectDraw2::CreateSurface方法创建的一组单一表面的组合。若在调用CreateSurface时设定了DDSCAPS_COMPLEX标志,除了显式指定的表面外,DirectDraw还将隐式地创建一个或多个表面。你可以象管理单一表面一样管理复杂表面。调用IDirectDraw::Release方法会释放所有的表面,调用IDirectDrawSurface3::Restore则会将这些表面恢复。
    最常用的复杂表面是翻转链。通常,一个翻转链由一个主表面和一个或多个后台缓冲区组。DDSCAPS_FLIP标志说明了某一表面是一翻转链一部分。用这种办法创建一个翻转链还需要包含DDSCAPS_COMPLEX标志。下面的例子创建一个主表面翻转链所需要的准备工作:
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
 
// Tell DirectDraw which members are valid.
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
 
// Request a primary surface with a single
// back buffer
ddsd.ddsCaps.dwCaps = DDSCAPS_COMPLEX | DDSCAPS_FLIP |
DDSCAPS_PRIMARYSURFACE;
ddsd.dwBackBufferCount = 1;
    该例构建了一个双缓冲翻转环境。调用IDirectDrawSurface3::Flip方法将交换主表面和后台缓冲区的表面内存。如果DDSURFACEDESC结构中的成员dwBackBufferCount取值为2,就会创建两个后台缓冲区,每次调用Flip就会循环地翻转表面,并提供三缓冲翻转环境。
4.4.4、创建宽表面(Wide Surface)
    DirectDraw允许创建比主表面宽的屏外表面,当然这需要显示硬件支持才行。要确定显示设备是否支持宽表面,需要调用IDirectDraw2::GetCaps方法,查看第一个DDCAPS结构中的dwCaps2成员是否包含DDCAPS2_WIDESURFACES标志。如果该标志存在,就表明可以在视频内存中创建比主表面还要宽的屏外表面。如果该标志不存在,在视频内存中创建宽表面将会失败,并返回一个DDERR_INVALIDPARAMS错误。
系统内存表面、视频端口表面和执行缓冲区都支持宽表面。
4.4.5、翻转表面(Flipping Surface)
    DirectDraw中的任何表面都可以构建为一个翻转表面。一个翻转表面就是能够在前台缓冲区(front buffer)和后台缓冲区(back buffer)之间互相交换的任意一块内存。通常,前台缓冲区就是主表面,但这并不是必需的。
当使用IDirectDrawSurface3::Flip方法执行表面翻转操作时指向主表面的表面内存的指针和后台缓冲区的表面内存的指针互换。因此,表面的翻转是显示设备用于指向内存的指针的交换,而不是对表面内存的拷贝。唯一的例外是,当DirectDraw用软件仿真翻转时只是简单地拷贝表面。如果一个后台缓冲区不能装入到显示内存中或硬件不支持DirectDraw时,DirectDraw就用软件仿真翻转操作。当一个翻转链包含了一个主表面和多个后台缓冲区时,指针的转换就采用了循环的方式周而复始,如下图所示:
 
    DirectDraw对象的其它附加表面不是翻转链的一部分,不受Flip方法的影响。切记,DirectDraw翻转表面是通过交换DirectDrawSurface对象表面内存的指针实现的,而不是交换这些对象。这就意味着,位块传输到后台缓冲区时,必须一直使用同一个DirectDrawSurface对象,该缓冲区是你创建翻转链的后台缓冲区。而指向翻转操作一直是通过调用前台缓冲区的Flip方法完成的。
4.4.6、丢失表面(Losing Surface)
    当表征表面内存的DirectDrawSurface对象不必要释放时,同该表面关联的表面内存却被释放了。这就是失去了表面。当DirectDrawSurface对象事情了它的表面内存时,许多方法就不执行其它的操作并返回DDERR_SURFACELOST。
因为改变了显示模式或另一个应用获得了对显示设备的独占访问而释放了当前分配的所有表面内存,表面就可能会丢失。
IDirectDrawSurface3::Restore方法重新创建这些丢失的表面并同DirectDrawSurface对象重新联结。恢复表面并不装入表面丢失之前存在的位图。因此,如果丢失的表面,必须完全重新装入此前曾装入的图形。
4.4.7、释放表面(Releasing Surface)
    象所有的COM接口一样,不需要表面时,必须调用Release方法释放表面。每一个单独创建的表面都必须显式地释放。如果是一次调用 IDirectDraw2::CreateSurface或IDirectDraw::CreateSurface方法显式地创建的多个表面(如翻转链等),你只需要释放前台缓冲区即可。在这种情况下,指向后台缓冲区表面的任何指针都会显式地释放,并且不可再用。
4.4.8、更新表面特征(Updating Surface Characteristics)
    你可以使用IDirectDrawSurface3::SetSurfaceDesc方法来更新已有表面的特征。用该方法可以改变像素格式,也可以改变DirectDrawSurface对象的表面内存在系统内存中的位置,该对象是应用程序显式地分配的。它允许一个表面直接使用先前分配的缓冲区中的数据而不需要拷贝,这一新的表面内存由客户应用来分配,也由该客户应用来释放。
    调用 IDirectDrawSurface3::SetSurfaceDesc方法时,lpddsd参数必须是描述新的表面内存的结构DDSURFACEDESC的地址,也就是指向该内存地址的指针。在DDSURFACEDESC结构中,可以只设定dwFlags成员来反映表面内存的位置、大小和像素格式。因此dwFlags只能包含DDSD_WIDTH、DDSD_HEIGHT、DDSD_PITCH、DDSD_LPSURFACE和DDSD_PIXELFORMAT等标志的组合,用这些组合来设定有效的结构成员。
    在设置结构的值前,必须分配内存来装入表面。分配的内存的大小非常重要 。你不仅需要分配足够的内存来容纳表面的宽和高,还需要为表面间距(pitch)留下足够的内存空间,表面间距是一个QWORD(8个字节),间距是用高度而不是用像素度量的。
    设置结构中表面的值时,lpSurface成员是你分配的内存的指针, dwHeight和dwWidth成员以像素单位描述了表面的大小。如果指定了表面的大小,也需要填充Ipitch成员来反映表面间距。ddpfPixelFormat成员描述了表面的像素格式。除了lpSurface成员外,若你不为这些成员设定值,IDirectDrawSurface3::SetSurfaceDesc方法就从当前的表面中使用缺省的值。
在调用IDirectDrawSurface3::SetSurfaceDesc方法时需要注意一些问题。例如,lpSurface必须是系统内存的一个有效指针(该方法目前不支持视频内存指针);dwWidth和dwHeight必须是非0的值;不能在主翻转链中重新分配主表面或其它任意表面。
    你可以为多个DirectDrawSurface对象分配同一内存,但必须注意当内存分配给任意表面对象后,就不能取消对该内存的分配。对SetSurfaceDesc方法不正确的使用会导致不可预料的反应。因此,当不再需要表面内存时,你必须记住释放它。不过,在调用该方法时,DirectDraw将释放创建表面时显式分配的表面内存。
4.4.9、直接访问帧缓冲区(Accessing the Frame-Buffer Directly)
    你可以使用IDirectDrawSurface3::Lock方法直接访问帧缓冲区或系统内存中的表面内存。当调用该方法时,lpDestRect参数是一个RECT结构的指针。该结构描述了要直接访问的表面上的矩形。若想锁定整个表面,可将lpDestRect设为NULL。也可以指定一个只包含了表面的一部分的RECT。如果没有两个矩形重叠,两个进程可以同时锁定一个表面中的多个矩形。
    Lock方法用需要适当地访问表面内存的所有信息填充DDSURFACEDESC结构。该结构包含了有关表面间距和像素格式的信息(在同主表面的像素格式不同时)。在完成了对表面的访问后,调用IDirectDrawSurface3::Unlock方法对其进行解锁。
4.4.10、使用非本地视频内存表面(Using Non-local Video Memory Surfaces)
    DirectDraw支持高级图形端口AGP(Advanced Graphics Port)特性,能够在非本地视频内存中创建表面。在AGP系统中,如果本地视频内存已用完或应用程序显式地要求使用非本地内存,DirectDraw将会使用非本地视频内存创建表面。
    目前,有两种AGP体系。一种是“执行模型”(Execute Model),一种是“DMA模型”(Direct Memory Access Model)。在执行模型中,显示设备支持非本地视频内存和本地视频内存同样的特性。因此,当使用 IDirectDraw2::GetCaps方法获取硬件的性能时,同位块传输相关的标志dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps和dwNLVBRops将指明用于本地视频内存。在执行模式下,如果本地视频内存被用完了,DirectDraw将自动使用非本地视频内存。
    在DMA模型中,对从非本地视频内存中位块传输的纹理贴图的支持是有限的。当显示设备使用了DMA模型时,欲获得硬件的性能,dwCaps成员将被设为DDCAPS2_NONLOCALVIDMEMCAPS标志。同位块传输相关的标志包含在dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps和dwNLVBRops等成员中。除非显式地指明,DirectDraw将不使用非本地视频内存创建表面。DMA模型的实现在对非本地视频内存表面的支持是不同的。如果驱动程序支持从非本地视频内存表面的纹理贴图,在用IDirect3DDevice2::GetCaps方法获取3D设备的性能时,D3DDEVCAPS_TEXTURENONLOCALVIDMEM标志将被设定。
4.4.11、转换颜色和格式(Converting Color and Format)
    非RGB表面格式由四个字符的代码(FOURCC codes)表示。如果应用程序调用了IDirectDrawSurface3::GetPixelFormat方法来要求使用像素格式,并且表面是一个非RGB表面, DDPF_FOURCC标志就会设定,DDPIXELFORMAT结构中的成员dwFourCC将成为有效的。如果FOURCC码表示的是一个YUV格式,DDPF_YUV标志将被设定,dwYUVBitCount、dwYBits、dwUBits、dwVBits和dwYUVAlphaBits
成员将是有效的掩码,能用于从像素中提取信息。
    如果当前是RGB格式,DDPF_RGB标志就会被设定,dwRGBBitCount、dwRBits、dwGBits、dwBBits和dwRGBAlphaBits成员将是能够用于从像素中提前信息的有效掩码。
在颜色和格式的转过程中,有两组FOURCC代码用于应用程序中。一组表征了硬件位块传输的能力,另一组表征了硬件覆盖的能力。
4.4.12、覆盖表面(Overlay Surfaces)
    覆盖表面是具有特殊硬件支持能力的表面,通常用于显示活动视频、录制视频或静止位图而不需要位块传输到主表面或改变主表面的内容。对覆盖表面的字此完全由硬件提供,DirectDraw支持显示设备驱动程序所支持的特性,DirectDraw不仿真覆盖表面。可以将覆盖表面想象为一片塑料纸,我们可以在这片塑料上画图并可将其放置在显示器前面。塑料纸覆盖在显示器前面时,你可以看到覆盖和主表面,移去塑料纸后,主表面并没有改变。覆盖表面的工作原理同透明塑料纸覆盖的原理很相似。的显示一个覆盖表面时,就是告诉设备驱动程序在哪里怎样使覆盖表面可见。当显示设备扫描线重画到显示器上时,它检查主表面上的每一个像素,看是否被覆盖所代替。如果是,显示设备就从覆盖表面中抽取相关像素的数据替代。
    使用这种方法,显示适配卡在显示器上生成主表面和覆盖表面的合成表面, 产生透明和拉伸效果而不需要改变每个表面的内容。合成表面被送入视频数据流直接送到显示器。因为这种处理和像素替代是硬件级的操作,所以不存在明显的性能损失。另外,这种方法还使得能够用不同的像素格式无缝地合成主表面和覆盖表面。
创建覆盖表面需要在DDSCAPS结构中指定 DDSCAPS_OVERLAY标志,然后调用IDirectDraw2::CreateSurface方法覆盖表面只能在支配内存中创建,因此还必须包含DDSCAPS_VIDEOMEMORY标志。同其他类型的表面一样,通过包含合适的标志,可以 创建单一的覆盖表面,也可以创建由多个覆盖组成的翻转链。
    你可以调用IDirectDraw2::GetCaps方法来获取有关支持的覆盖特性的信息。该方法用描述所有的特性信息填充一个DDCAPS结构。在报告硬件特性时,设备驱动程序设置dwCaps成员的标志来指明何时强制执行硬件提供的某种类型的约束。获得的驱动程序的能力后,通过检查dwCaps成员的标志可知道提供的约束的信息。DDCAPS结构包含了9个成员,这9个成员描述了覆盖表面的约束信息。下标列出了同覆盖相关的成员既它们的标志。
 
成员
标志
dwMaxVisibleOverlays
该成员始终有效
dwCurrVisibleOverlays
该成员始终有效
dwAlignBoundarySrc
DDCAPS_ALIGNBOUNDARYSRC
dwAlignSizeSrc
DDCAPS_ALIGNSIZESRC
dwAlignBoundaryDest
DDCAPS_ALIGNBOUNDARYDES
T
dwAlignSizeDest
DDCAPS_ALIGNSIZEDEST
dwMinOverlayStretch
DDCAPS_OVERLAYSTRETCH
dwMaxOverlayStretch
DDCAPS_OVERLAYSTRETCH

    dwMaxVisibleOverlays和dwCurrVisibleOverlays成员指明了硬件可以显示的覆盖的最大数目,以及当前有多少个可见。
dwAlignBoundarySrc、dwAlignSizeSrc、dwAlignBoundaryDest、dwAlignSizeDest和dwAlignStrideAlign成员是硬件报告的矩形的位置和大小的约束。这些成员的值指明了在显示覆盖表面时如何确定源矩形和目的矩形的大小和位置。
dwMinOverlayStretch和dwMaxOverlayStretch是有关拉伸因子的信息。
a、源矩形和目的矩形(Source and Destination Rectangles)
    要显示一个覆盖表面,需调用IDirectDrawSurface3::UpdateOverlay方法,在dwFlags 参数中指定 DDOVER_SHOW标志。该方法要求你在lpSrcRect和lpDestRect参数中指定一个源矩形和目的矩形。若使用整个表面,将lpSrcRect参数设为NULL即可。目的矩形是在主表面上产生覆盖表面的位置。
   源、目的矩形不必大小相同。一般让目的矩形必源矩形大一些或小一些都可以,硬件在显示时会自动地压缩或拉伸。要想成功地显示一个覆盖表面,可能需要调整源、目的矩形的大小和位置,这一过程是否必要依赖于设备驱动程序的限制。
b、边界和大小调整(Boundary and Size Alignment)
    由于不同硬件的限制,一些设备驱动程序对用于显示覆盖表面的源矩形和目的矩形的大小和位置做了约束。要找出设备应用的约束,可调用IDirectDraw2::GetCaps方法,然后检查DDCAPS结构中同覆盖相关的dwCaps成员的标志。下标列出了指定边界和大小调整约束的成员和标志。

类别
标志
成员
边界约束
DDCAPS_ALIGNBOUNDARYSRC
dwAlignBoundarySrc

DDCAPS_ALIGNBOUNDARYDEST
dwAlignBoundaryDest
大小约束
DDCAPS_ALIGNSIZESRC
dwAlignSizeSrc

DDCAPS_ALIGNSIZEDEST
dwAlignSizeDest

约束有两种,边界约束和大小约束。两种约束都以像素的方式表示,并且能够用于源矩形和目的矩形。当然,由于覆盖表面和主表面的像素格式的不同,这些约束也可以不一样。
    边界约束影响源矩形和目的矩形放置的位置。dwAlignBoundarySrc和dwAlignBoundaryDest成员的值告诉你如何调整相关矩形的左上角。
矩形左上角的X坐标(RECT结构中的left成员)必须是报告出的值的整数倍。
大小约束影响源矩形和目的矩形的有效宽度。dwAlignSizeSrc和dwAlignSizeDest成员的值以像素的格式指出怎样调整相关矩形的宽。如果按照一个最小拉伸因子拉伸矩形,应确保拉伸后的矩形仍然是大小调整过的。拉伸矩形之后,通过向上圆整来调整宽度,就可以保持最小的拉伸因子。
c、最大和最小拉伸因子(Minimum and Maximum Stretch Factors)
    由于硬件的局限性,一些设备限制了目的矩形同相关的源矩形宽度的比较。DirectDraw将这些约束作为拉伸因子。一个拉伸因子就是源矩形同目的矩形的宽度之间的比率。若驱动程序提供有关拉伸因子的信息,在调用 IDirectDraw2::GetCaps 方法时后,它将在DDCAPS结构中设置 DDCAPS_OVERLAYSTRETCH标志。注意,拉伸因子都已经乘以1000,所以值为1300的拉伸因子实际上是1.3。
    不压缩和拉伸覆盖目的矩形的设备所报告出的最大最小拉伸因子通常是0。
    最小拉伸因子指出目的矩形比源矩形宽多少或窄多少。如果最小拉伸因子大于1000,就必须增加目的矩形的宽度。例如,若拉伸因子为1300,则目的矩形的宽度应该至少是源矩形宽度的1.3倍。如果拉伸因子小于1000,目的矩形就比源矩形的宽度要小。最大拉伸因子是目的矩形能够拉伸的最大倍数。例如,若最大拉伸因子是2000,则目的矩形的宽度最多可以是源矩形宽度的2倍。若最大拉伸因子小于1000,
目的矩形就需要压缩。经过拉伸之后,目的矩形必须遵守设备要求的任何大小调整约束。因此,最后在大小调整之前拉伸目的矩形。硬件并不要求调整目的矩形的高度。你可以增加矩形的高度来保持方向比率的不变。
d、覆盖Color Key
    象其它类型的表面一样,覆盖表面也使用源、目的Color Key来控制表面之间的透明位块传输操作。因为覆盖表面的显示不是通过位块传输完成的,所以在调用IDirectDrawSurface3::UpdateOverlay方法时就需要采取不同的办法来控制覆盖表面显示在主表面上的方式。答案就是覆盖Color Key。同位块传输相关的Color Key相似,覆盖Color Key也有源Color Key和目的Color Key,可通过调用方法
IDirectDrawSurface3::SetColorKey并利用DDCKEY_DESTOVERLAY标志来设置源Color Key和目的Color Key。覆盖表面能够将位块传输和覆盖Color Key结合在一起来控制位块传输操作和覆盖显示操作,两种不同类型的Color Key并不互相冲突。
    IDirectDrawSurface3::UpdateOverlay方法用源Color Key检查覆盖表面中哪个像素应该是透明的,允许主表面透过覆盖表面显示。同样,该方法使用目的覆盖Color Key来确定主表面显示时哪部分允许倍覆盖表面所覆盖,其显示效果同位块传输Color Key相同。
e、 定位覆盖表面(Positioning Overlay Surfaces)
    在最先调用IDirectDrawSurface3::UpdateOverlay方法显示一个覆盖时,可以用方法 IDirectDrawSurface3::SetOverlayPosition来更新目的矩形。必须确保你指定的目的矩形的位置遵守边界对齐约束, IDirectDraw2::SetOverlayPosition方法并不执行剪切工作,使用了可能引起覆盖越界的坐标会使调用该方法失败,并返回 DDERR_INVALIDPOSITION。
f、创建覆盖表面(Creating Overlay Surfaces)
    象所有的表面一样,你可以调用IDirectDraw2::CreateSurface方法来创建一个覆盖表面。创建覆盖表面还需要在相关的结构DDSCAPS 中包含 DDSCAPS_OVERLAY标志。覆盖支持许多显示设备,因此不能判断一个给定的像素格式是否被大多数的驱动程序所支持,必须做好准备使之能工作于多种像素格式。你可以调用 IDirectDraw2::GetFourCCCodes方法来获得驱动程序支持的有关非RGB格式的信息。要创建一个覆盖表面,最好使用最常用的像素格式,若给定的像素格式不被支持,DirectDraw会使用显示设备所支持的其他像素格式。创建覆盖表面翻转链也是允许的。
g、翻转覆盖表面(Flipping Overlay Surfaces)
    同其他类型的表面一样,你可以创建覆盖表面翻转链。一旦创建了覆盖的翻转链,就可以调用方法IDirectDrawSurface3::Flip来翻转这些覆盖。软件解压在调用Flip方法显示覆盖表面时可使用DDFLIP_ODD和 DDFLIP_EVEN标志以减少运动赝象。如果驱动程序支持奇──偶翻转,在获得了驱动程序的能力后,DDCAPS2_CANFLIPODDEVEN标志将会在DDCAPS结构中设定。一旦设定了DDCAPS2_CANFLIPODDEVEN,就可在调用IDirectDrawSurface3::UpdateOverlay方法时包含DDOVER_BOB标志以通知驱动程序使用“Bob”算法最小化运动赝象。此后,用DDFLIP_ODD或 DDFLIP_EVEN标志调用Flip时,驱动程序将会自动调整覆盖的源矩形来弥补抖动赝象。
  如果获取硬件的能力后没有设置DDCAPS2_CANFLIPODDEVEN标志,但在调用UpdateOverlay时又使用了
DDOVER_BOB标志,那么该调用将会失败。

5、调色板(Palette)
    调色表面需要有调色板才能正确地显示出来。一个调色表面(既颜色索引标)是一些数字的简单集合,其中每个每个数字都表征了一个像素。数字的值是一个颜色表的索引,它告诉DirectDraw显示每个像素时该使用什么颜色。DirectDrawPalette对象通常称为调色板,它提供了管理颜色表的简单方法。使用16位或更高位数的像素格式的表面不使用调色板。
     DirectDrawPalette对象表征了具有2、4、16或256个实体的用于颜色索引表面的索引颜色表。调色板中的每个实体都是一个RGB三元组,描述了显示表面内像素所使用的颜色。颜色表可以包含16位和24位RGB三元组来表征所用的颜色。对于16色的调色板,颜色表也能包含另一个256色的调色板的索引。调色板可以应用在纹理、屏外表面和覆盖表面中,它们都不要求具有和主表面相同的调色板。
你可以调用IDirectDraw2::CreatePalette方法创建一个调色板,该方法取得调色板对象的IDirectDrawPalette接口的指针。然后就可以利用该接口的方法处理调色板的实体,获取有关对象能力的信息或初始化对象(若调色板是用COM函数CoCreateInstance创建的)。
    调用IDirectDrawSurface3::SetPalette方法可将一个调色板附加在一个表面上。一个单一的调色板可以应用到多个表面上。DirectDrawPalette对象对8位的调色板保留实体0和实体255,除非你指定了 DDPCAPS_ALLOW256标志要求这两个实体可用。你可以用IDirectDrawPalette::GetEntries方法获得调色板实体,用IDirectDrawPalette::SetEntries方法修改调色板实体。
5.1、调色板类型
    DirectDraw支持1位、2位、4位和8位的调色板。一个调色板只能附给具有相同像素格式的表面。例如,一个由DDPCAPS_1BIT标志创建的2实体调色板只能附给用 DDPF_PALETTEINDEXED1标志创建的1位表面。另外,也可以创建完全不包含颜色表的调色板,即索引调色板。同颜色表不同的是,索引调色板包含了索引值,这些值表征了另一个调色板颜色表的位置。
    要创建一个索引调色板,需调用IDirectDraw2::CreatePalette方法,指定 DDPCAPS_8BITENTRIES标志。例如,要创建一个4位调色板,还需要指定DDPCAPS_4BIT和DDPCAPS_8BITENTRIES标志。当创建索引调色板时,要将一个指针传递给字节数组。
5.2、设置非主表面上的调色板(Setting Palettes on Nonprimary Surfaces)
    调色板能够附在任意的调色表面(主表面,后台缓冲区,屏外表面和纹理贴图)上,但只有附在主表面上的调色板才能影响系统调色板。
必须注意的是,DirectDraw位块传输不会执行颜色变换操作,任何附于位块传输的源表面和目的表面都被忽略。非主表面上的调色板主要用于Direct3D应用程序。
5.3、共享调色板(Sharing Palettes)
    调色板可以在多个表面之间共享。同一个调色板可以附于一个翻转链的前台缓冲区和后台缓冲区,也可以共享于多个纹理表面。当使用IDirectDrawSurface3::SetPalette方法将一个调色板附于一个表面时,该表面就会增加该调色板的索引值,当表面的索引值达到0时,该表面就会减少附着的调色板的索引值。另外,如果一个调色板同一个表面分离开,该表面的调色板的索引值也会减少。
5.4、调色板动画(Palette Animation) 
    调色板动画是指在显示时通过改变表面的调色板来更改表面的外观的过程。重复地改变调色板,表面看起来就象是改变了,但实际上,表面的真实内容并无变化。因此,调色板动画提供了一种改变表面的显示表现而不改变表面的具体内容的方法。对于直线式的调色板动画可以提供两种办法:
   i. 在单一的调色板修改调色板实体
   ii. 在多个调色板之间切换 
使用前一种方法,你可以改变耽搁调色板中那些相关颜色的实体,然后调用 IDirectDrawPalette::SetEntries方法一次复位这些实体。第二种方法要求2个或多个DirectDrawPalette对象,然后调用IDirectDrawSurface3::SetPalette方法将一个接一个的调色板对象附给表面对象从而产生
动画。两种方法都同硬件无关,因此使用哪种方法主要是看是否适合应用程序。

6、剪切板(Clipper)
    剪切板或DirectDrawClipper对象允许位块传输到表面中选定的部分。一个剪切板对象拔海内一个或多个剪切板列表。一个剪切板列表就是一个有界矩形或几个有界矩形列,它是表面中允许位块传输到的一块或几块区域。这些区域都由RECT结构以屏幕坐标的方式来表示。
    剪切列表是一个非常有用的工具,其中最常用的是禁止应用程序将数据块写到超出屏幕边界的地方。例如,若要显示一个精灵从屏幕边界进入,你不希望精灵一下子就弹出出现在屏幕上,而是平滑地慢慢从屏幕边界一点一点移动到屏幕中央。若没有剪切板对象,就需要包含限制位块操作以保护表面内存的逻辑。下图显示了这一类的剪切。
 
     你可以使用剪切板对象来芝麻一个目的矩形的一定区域是可写的,DirectDraw将在这些区域对位块传输加以剪切以保护指定的剪切矩形之外的像素。这种剪切方式如下图所示:
 
6.1、剪切列表(Clip List)
     DirectDraw用DirectDrawClipper对象来管理剪切列表。一个剪切列表就是描述表面可见区域的一系列矩形。一个DirectDrawClipper对象可以附在任何表面之上。一个窗口句柄也可以附在DirectDrawClipper对象上,在这种情况下,DirectDraw将使用窗口中更新的剪切列表来修改DirectDrawClipper剪切列表。
    尽管DirectDraw HAL中剪切列表可见,DirectDraw也只在用矩形位块传输时才调用HAL来满足剪切列表要求。例如,若一个表面的左上角矩形被剪切掉了,在应用程序将该表面块写到主表面时,DirectDraw将使用HAL执行两次位块传输操作,第一次是传输表面的左上角,第二次操作是传输表面的下半部分。
    通过 IDirectDrawClipper::SetClipList方法可以将这个剪切列表传送给驱动程序(如果该驱动程序支持剪切),而不是多次调用驱动程序。
另外,还能通过指定目的窗口的句柄,调用 IDirectDrawClipper::SetHWnd方法将剪切板设到一个单一的窗口。只有当覆盖硬件支持剪切或者目的Color Key还没有被激活时,覆盖表面才支持剪切功能。
6.1、共享DirectDrawClipper对象
    DirectDrawClipper对象能在多个表面之间共享。例如,同一个DirectDrawClipper对象可以同时用于翻转链的前台和后台缓冲区。当应用程序用IDirectDrawSurface3::SetClipper方法将一个DirectDrawClipper对象附于一个表面时,次表面就增加该对象的索引值。如果一个DirectDrawClipper对象同一个表面(该表面是用IDirectDrawSurface3::SetClipper方法由一个空剪切板接口指针创建的)分离,该表面的
DirectDrawClipper对象的索引值就会递减。
6.3、独立的剪切板对象
    你可以创建一个不直接从属于任何一个DirectDraw对象的DirectDrawClipper对象。这种剪切板对象可以在多个DirectDraw对象之间共享。驱动程序独立性的剪切板对象可用新的DirectDraw函数DirectDrawCreateClipper创建,应用程序可在创建任何DirectDraw对象之前调用该函数。
    因为DirectDraw对象不能拥有这些独立的DirectDrawClipper对象,所以当应用中的对象释放后,这些DirectDrawClipper对象并不会自动释放。若应用程序没有显式地释放这些剪切板对象,DirectDraw将在程序结束时释放它们。
当然,你还是可以用 IDirectDraw2::CreateClipper方法创建DirectDrawClipper对象,用这种方法创建的剪切板对象在与其相关的DirectDraw对象释放后会自动释放。
6.4、用COM函数创建DirectDrawClipper对象
    DirectDrawClipper对象完全支持COM应用。除了使用标准的IDirectDraw2::CreateClipper方法和DirectDrawCreateClipper函数外,还可以用COM函数 CoGetClassObject 创建来创建剪切板对象。下面是用CoCreateInstance函数和IDirectDrawClipper::Initialize方法创建剪切板对象的实例:
 ddrval = CoCreateInstance(&CLSID_DirectDrawClipper,
    NULL, CLSCTX_ALL, &IID_IDirectDrawClipper, &lpClipper);
if (!FAILED(ddrval))
    ddrval = IDirectDrawClipper_Initialize(lpClipper,lpDD, 0UL);
     在对CoCreateInstance的调用中,第一个参数CLSID_DirectDrawClipper是剪切板对象类的类标志, IID_IDirectDrawClipper参数表明了当前支持的接口,lpClipper参数是剪切板对象的指针。
    应用程序必须必须使用IDirectDrawClipper::Initialize方法来初始化剪切板对象, 0UL是dwFlags参数的值,本例中表示没有使用任何标志。lpDD是拥有该剪切板对象的DirectDraw对象;若用NULL代替lpDD,将会创建一个独立的DirectDrawClipper对象,也将等价于用DirectDrawCreateClipper函数创建的剪切板对象。关闭应用程序之前,应该先用CoUninitialize函数关闭COM。
    DirectDraw通常为用户提供一种引导使用鼠标的方法。对于使用页翻转的全屏独占模式的应用程序,唯一的选择是在一个精灵上手工实现鼠标的光标,依靠由DirectInput从设备中获取的数据或窗口的鼠标消息来移动精灵。任何不使用页翻转的应用程序也可以使用系统的鼠标光标支持。
    使用系统的鼠标光标时,在块写主表面的一部分时可能会出现图像赝象现象。在位块传输操作中,剪切板对象可以防止这些赝象的出现。
在应用中,先用IDirectDraw2::CreateClipper方法创建剪切板对象,再用 IDirectDrawClipper::SetHWnd方法将应用的句柄分配给剪切板。一旦剪切板附上了,任何随后的用IDirectDrawSurface3::Blt方法实现的位块传输的操作将不会出现赝象现象。
    你可以将一个剪切板对象块写到多个窗口。首先要创建一个只有一个主表面的DirectDraw对象,然后创建一个剪切板对象,再用 IDirectDrawSurface3::SetClipper方法将剪切板对象分配到主表面。要块写到窗口的客户区(client area),需要在块写到主表面之前用IDirectDrawClipper::SetHWnd方法将剪切板设到窗口的客户区。如果需要块写到另一个窗口的客户区域,就用目标窗口的句柄再调用
IDirectDrawClipper::SetHWnd方法一次。

四、DirectDraw高级特性
1、直接内存访问DMA
    有些显示设备能够在系统内存表面支持位块传输操作(或其它操作),这些操作就是直接内存访问DMA(Direct Memory Access)。 你可以利用DMA的支持来加速一定的操作。例如,在这种设备上你可以从系统内存位块传输到视频内存上,同时处理器还准备处理下一帧的画面。
    在使用DMA操作之前,必须检测设备是否支持DMA及支持的程度。步骤是先用 IDirectDraw2::GetCaps方法来获取设备的能力,然后再查看 DDCAPS结构中的dwCaps成员是否含有DDCAPS_CANBLTSYSMEM标志,若是则表明设备支持DMA。
    如果设备支持DMA,还需要知道驱动程序是如何支持的。这就需要查看有关的结构成员,这些成员包含了有关系统内存——视频内存、视频内存——系统内存和系统内存——系统内存之间位块传输操作的信息。DDCAPS结构的几个成员提供了这些信息,如下表所示:
System-to-video
Video-to-system
System-to-system
   dwSVBCaps
   dwVSBCaps
   dwSSBCaps
   dwSVBCKeyCaps
   dwVSBCKeyCaps
   dwSSBCKeyCaps
   dwSVBFXCaps
   dwVSBFXCaps
   dwSSBFXCaps
   dwSVBRops
   dwVSBRops
   dwSSBRops

    例如,系统内存到视频内存的位块传输的性能标志由dwSVBCaps、dwSVBCKeyCaps、dwSVBFXCaps和dwSVBRops提供。视频内存到系统内存的位块传输性能标志包含在以“dwVSB”开头的成员中,系统内存到系统内存之间的位块传输性能标志包含在以“dwSSB”开头的成员中。
    这些成员最关键的特性是对异步DMA位块传输操作的支持。如果驱动程序支持表面间的异步DMA位块传输操作,DDCAPS_BLTQUEUE 标志将会设在dwSVBCaps、dwVSBCaps、和dwSSBCaps成员中。若该标志不存在,就表明驱动程序不支持异步DMA位块传输操作。
    系统内存到视频内存之间的SRCCOPY传输是硬件支持的最常见的位块传输操作。因此,对这种操作最典型的应用是从系统表面内存将许多纹理移动到视频内存。系统内存到视频内存的DMA传输大约同处理器控制的传输速度一样快,但具有更大的优点,因为这些操作可以同主机处理器并行运行。
    硬件传输使用物理内存地址而不是虚拟内存地址,某些设备驱动程序要求提供物理内存地址。该机制是通过对IDirectDrawSurface3::PageLock方法的调用完成的。如果设备驱动程序不要求页锁定,在用IDirectDraw2::GetCaps方法获取硬件的能力时,DDCAPS2_NOPAGELOCKREQUIRED标志将被 设定。
    页锁定一个表面可以防止系统将表面的物理内存再做她用,并且保持该表面的物理内存不变,直到调用IDirectDrawSurface3::PageUnlock方法为止。如果设备驱动程序请求页锁定,DirectDraw只允许DMA操作在应用程序已经页锁定了的系统内存表面上。如果在这种情况下没有调用IDirectDrawSurface3::PageLock,DirectDraw将用软件仿真执行传输工作。需要注意的是,锁定的表面多了之后将会大大降低Windows运行的效率。因此,最好只在全屏独占模式下处理较多数量的系统内存,并且当应用程序最小化时能够对这些表面进行解锁。当然,应用程序恢复后,应该重新对系统内存表面页锁定。

2、在窗口模式下使用DirectDraw调色板
    显示在全屏独占方式下时,IDirectDrawPalette接口方法是直接写向硬件的。显示在窗口模式下时,IDirectDrawPalette接口方法将调用GDI调色板处理函数使得能与其它的Windows应用程序和谐地工作在一起。
2.1、窗口模式下调色板实体的类型
   同全屏独占式的应用程序不一样,窗口模式的应用程序必须同其它的应用程序共享桌面调色板。用于同 DirectDrawPalette对象和GDI一同工作的PALETTEENTRY 结构包含了一个peFlags成员,该成员含有系统应该怎样解释PALETTEENTRY结构的信息。peFlags成员描述了三种类型的调色板实体。
2.1.1、窗口静态实体
    在标准模式下,Windows保留调色板实体0~9和实体246~255用于系统颜色、显示菜单条、菜单文本和窗口边界等等。为了使应用程序保持一致的外观,避免破坏其它应用程序的外观表现,你需要保护这些设给主表面的调色板的实体。通常,程序员可以调用Win32函数GetSystemPaletteEntries来获取系统调色板,然后显式地在自定义调色板中设定相同的调色板,使之在分配给主表面前同系统调色板匹配。尽管系统调色板的实体复制到自定义调色板中在最开始也可以工作,但在用户改变了桌面颜色的配色时,该自定义调色板就会变的无效。
    为了避免用户改变颜色配色后调色板的效果很差,你可提供一个系统调色板的索引来替代指定的颜色值,从而保护合适的实体。用这种方法,不管系统对所给的实体使用了什么颜色,调色板都会始终匹配,不需要做任何更新工作。用于 peFlags成员的PC_EXPLICIT标志使你能够直接索引到一个系统调色板实体。使用了这一标志后,系统就不再认为结构的其它成员包含了颜色信息。你可以将peRed成员的值设置为需要的系统调色板索引而将其它的颜色都设为0。例如,若想保证你的调色板中的适当的实体总是同系统调色板颜色配色匹配,可以使
用以下代码:
// Set the first and last 10 entries to match the system palette.
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;
}
 
    你可以调用Win32函数SetSystemPaletteUse强制Windows只使用第一个和最后一个调色板实体0和255。在这种情况下,应该在PALETTEENTRY结构中只将实体0和255设置为PC_EXPLICIT。
2.1.2、运动实体(Animated entries)
    你可在PALETTEENTRY结构中使用PC_RESERVED标志来指定将用于运动是调色板实体。Windows不允许其它的应用程序将它自己的逻辑调色板实体映射到你指定的物理调色板实体,从而在你的应用程序运动调色板时,避免其它的应用程序改变它们的颜色。
2.1.3、非运动实体(Nonanimated entries)
    你可在PALETTEENTRY结构中使用PC_NOCOLLAPSE标志来指定非运动调色板实体。PC_NOCOLLAPSE标志通知Windows不用已经分配了的调色板实体替代该非运动实体。
2.2、在窗口模式下创建调色板
  下面是在非独占窗口模式下创建DirectDraw调色板的例子。为了使调色板能够正常的工作,最好在提交给 IDirectDraw2::CreatePalette方法的PALETTEENTRY结构中将256个实体都设置好。
LPDIRECTDRAW        lpDD; // Assumed to be initialized previously
PALETTEENTRY        pPaletteEntry[256];
int                 index;
HRESULT             ddrval;
LPDIRECTDRAWPALETTE lpDDPal;
 
// First set up the Windows static entries.
for (index = 0; index < 10 ; index++)
{
    // The first 10 static entries:
    pPaletteEntry[index].peFlags = PC_EXPLICIT;
    pPaletteEntry[index].peRed = index;
    pPaletteEntry[index].peGreen = 0;
    pPaletteEntry[index].peBlue = 0;
 
    // The last 10 static entries:
    pPaletteEntry[index+246].peFlags = PC_EXPLICIT;
    pPaletteEntry[index+246].peRed = index+246;
    pPaletteEntry[index+246].peGreen = 0;
    pPaletteEntry[index+246].peBlue = 0;
}
 
// Now set up private entries. In this example, the first 16
// available entries are animated.
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;
}
 
// Now set up the rest, the nonanimated entries.
for (; index < 246; index ++) // Index is set up by previous for loop
{
    pPaletteEntry[index].peFlags = PC_NOCOLLAPSE;
    pPaletteEntry[index].peRed = 25;
    pPaletteEntry[index].peGreen = 6;
    pPaletteEntry[index].peBlue = 63;
}
 
// All 256 entries are filled. Create the palette.
ddrval = lpDD->CreatePalette(DDPCAPS_8BIT, pPaletteEntry,
    &lpDDPal,NULL);
 
2.3、窗口模式下设置调色板实体
    用于IDirectDraw2::CreatePalette方法的PALETTEENTRY中的规则同样适用于IDirectDrawPalette::SetEntries方法。因此,你可以维持PALETTEENTRY结构组而不需要重建它。在需要的时候,更改结构组并调用IDirectDrawPalette::SetEntries方法。在大多数的环境中,处于窗口模式时,不要试图来设置任何窗口静态实体,否则将会出现无法预料的结果。只有在对实体复位时才允许设置窗口静态实体。
    对应调色板动画,你只需要改变PALETTEENTRY结构组中的很少一部分实体,将这些实体提交给IDirectDrawPalette::SetEntries方法。
如果想对这些实体进行进行复位操作,只能复位那些用PC_NOCOLLAPSE标志和PC_RESERVED标志标记过的实体。试图运动其它的实体会带来不可预知的错误。下面的是非独占模式下的调色板动画的例子:
LPDIRECTDRAW        lpDD;        // Already initialized
PALETTEENTRY pPaletteEntry[256]; // Already initialized
LPDIRECTDRAWPALETTE lpDDPal;     // Already initialized
int                 index;
HRESULT             ddrval;
PALETTEENTRY        temp;
 
// Animate some entries. Cycle the first 16 available entries.
// They were already animated.
temp = pPaletteEntry[10];
for (index = 10; index < 25; index ++)
{
    pPaletteEntry[index] = pPaletteEntry[index+1];
}
pPaletteEntry[25] = temp;
 
// Set the values. Do not pass a pointer to the entire palette entry
//structrue, but only to the changed entries.
ddrval = lpDDPal->SetEntries(
    0,                      // Flags must be zero
    10,                     // First entry
    16,                     // Number of entries
    & (pPaletteEntry[10])); // Where to get the data
 
3、多显示器系统
    Windows 98(Memphis)和Windows NT支持单一系统上的多个显示设备和显示器。多显示器系统(通常称为 “MultiMon”)使得操作系统能够使用两个或两个以上的显示设备和显示器来创建单一的逻辑桌面。例如,在有两台显示器的MultiMon系统中,用户既能够在其中一个显示器上显示应用程序,也能够将窗口从一台显示器拖放到另一台显示器上。而DirectDraw支持这一体系结构。一个DirectDraw应用程序能够列出硬件设备,选择一种设备,然后利用该设备的GUID为该设备创建一个DirectDraw对象。这一技术可以保证不管是在MultiMon
系统还是在单一显示器系统都能获得最佳的性能表现。
    当前的活动显示设备被认为是“缺省设备”,或者是“空设备”(Null Device,因为当前活动的显示设备在列出来时是以NULL作为它的GUID的)。已有的许多应用程序都可以为“空设备”创建DirectDraw对象,假定该设备是硬件加速的。但在MultiMon系统中,“空设备”并不总是硬件加速的,它依赖于当时采用的协作等级。
    在全屏独占模式下,“空设备”是硬件加速的,但系统并不知道其它已安装的设备。这就意味着,全屏独占模式的应用程序运行在MultiMon系统上的速度同运行在其它系统上的速度一样快,但不能使用内嵌的对跨越显示设备的图象操作的支持。需要使用多个设备的全屏独占模式的应用程序能为每一个要使用的设备创建一个DirectDraw对象。注意,为指定的设备创建DirectDraw对象,你必须提供该对象的GUID。调用DirectDrawEnumerate方法时可以列出每一个设备的GUID。
    当设定了标准协作等级后,“空设备”就不具有硬件加速能力,实际上,它只是将两个物理设备的资源结合在一起来仿真一个逻辑设备。
另一方面,“空设备”自动具有跨越显示器的图形操作能力。所以,当第二个显示器的逻辑位置处于主显示器的左边时,负的坐标值就是合法的。
    如果应用程序在设定了标准协作等级后要求实现硬件加速功能,它就必须使用指定设备的GUID创建一个单一的DirectDraw对象。如果不使用“空设备”,就不能获得跨越显示设备的图象操作能力。也就是说,跨越主表面边界的位块传输操作将会被剪切(若使用了剪切板)或失败(返回DDERR_INVALIDRECT)。
    不管在哪种系统下,都应该在获取对象的能力和查询其它的接口之前创建DirectDraw对象之后立即设置协作等级,还要避免在MultiMon系统中多次设置协作等级。如果需要从全屏模式切换到标准模式,最好创建新的DirectDraw对象,而不要继续使用老的DirectDraw对象。

4、视频端口(Video Ports)
    DirectDraw视频端口扩展是一个低级编程接口,它的目的并不是用于目前的系统多媒体编程。视频端口接口的对象是视频软件公司,如开发DirectShow之类的公司。想要在自己的软件中使用视频技术的程序员也可以使用视频端口扩展。不过,在很多软件中提供的高级编程接口就足以满足需要了。
    DirectDrawVideoPort对象表征了某些计算机系统中安装了的视频端口硬件。一般来说,视频端口对象控制了视频端口硬件怎样将从视频解码器接受到的视频信号直接应用到帧缓冲区。根据需要,可以创建多个DirectDrawVideoPort对象来控制多个视频通道。因为每个通道都可以分别列出和配置,所以,视频硬件不需要对每个通道做完全相同的处理。
    视频端口硬件能够直接在帧缓冲区中对表面进行存取,而绕过CPU和PCI总线。直接对帧缓冲区的存取使得能有效地播放活动视频和录制视频而不必装载入CPU中。一旦存在于某个表面中,图象就能在屏幕上如覆盖一样的显示,它可用于 Direct3D纹理、CPU的截获存取和其它的处理。
    在配备了视频端口设备的计算机中,视频流中的数据能够从视频源通过视频解码器和视频端口直接流向帧缓冲区。这些部件通常和显示适配器连接在一起,也可以作为单独的硬件互相连接在一起。下图给出了数据流的处理过程:
    
    视频源是什么呢?在视频端口技术范围内,严格来说,视频源就是一个硬件视频输入(Video Input)设备,如Zoom视频端口、MPEG编码器等。这些硬件源将信号以不同的格式(包括NTSC、PAL和SECAM)通过连到视频解码器的物理连接发布出去。视频解码器(Viceo Decoder)是另一个硬件部件。它的工作是翻译视频源提供的信息并将其用遵循的连接格式发送到视频端口。解码器保持同视频端口的物理连接,它还负责将视频数据、时针信息和同步信息送到视频端口。
    视频端口(Video-Port)也是一组硬件。它存在于显示适配器的VGA芯片上,具有直接访问帧缓冲区的能力。它处理来自于解码器的信息并放置于帧缓冲区中以备显示。在对数据的处理过程中,视频端口能操作图象数据以提供比例变化、拉伸压缩、颜色控制和剪切等服务。
帧缓冲区(Frame Buffer)接受视频端口提供的视频数据,然后应用程序就可用编程的方法处理这些图象数据,将其位块传输到其它的位置或使用覆盖将其显示在屏幕上。
    DirectDraw视频端口扩展的实质是包含了DirectDrawVideoPort对象,可通过IDDVideoPortContainer和IDirectDrawVideoPort接口利用视频端口技术提供视频服务。DirectDrawVideoPort对象并不能控制视频解码器,只能提供自己的服务。DirectDraw也不能控制视频源,因为视频源已经超出了视频端口的范围。DirectDrawVideoPort对象只是表征视频端口本身。它用接口方法设定的参数将输入的信号和图象数据送到帧缓冲区执行翻转或其它的处理。IDDVideoPortContainer接口(可通过IDirectDraw2::QueryInterface获取)提供了查询硬件创建视频端口对象的能力。你可以用IDDVideoPortContainer::CreateVideoPort方法创建一个视频端口对象,该对象通过IDirectDrawVideoPort接口显示其功能。
利用这些接口,你能够检查视频端口的能力,分配覆盖表面以接收图象数据、开始和停止视频播放、设置硬件参数处理图象数据使产生剪切、颜色转换、比例缩放和拉伸压缩效果。DirectDraw视频端口扩展支持同一台计算机上的多个视频端口,允许创建多个视频端口对象。

5、获取翻转和位块传输状态
    我们已经知道,调用IDirectDrawSurface3::Flip方法之后,主表面和后台缓冲区就会互相交换。但交换的动作并不是立即就会发生。例如,若前面的一个翻转任务还未完成时,该方法就返回DDERR_WASSTILLDRAWING,DirectDraw继续对IDirectDrawSurface3::Flip进行调用直到返回DD_OK为止。如果应用程序在调用IDirectDrawSurface3::Flip方法返回DD_OK之前一直处于等待状态,那么该程序的效率就会非常低。取而代之的是,你可以创建一个函数在后台缓冲区调用
    IDirectDrawSurface3::GetFlipStatus方法检测前面的翻转工作是否完成。如果未完成,返回的还是DDERR_WASSTILLDRAWING,应用程序就可以在下一次检查该状态之间的一段时间内执行其它的任务。下面是这一思想的例子:
while(lpDDSBack->GetFlipStatus(DDGFS_ISFLIPDONE) == DDERR_WASSTILLDRAWING);
 
    // Waiting for the previous flip to finish. The application can
    // perform another task here.
 
ddrval = lpDDSPrimary->Flip(NULL, 0);
 
  你也可以用同样的方法调用IDirectDrawSurface3::GetBltStatus来检测一次位块传输是否完成。因为IDirectDrawSurface3::GetFlipStatus和IDirectDrawSurface3::GetBltStatus能够立即返回应用程序目前所处的状态,所以可以在程序中周期地使用它们而不至于太影响程序的运行速度。
    执行颜色填充的工作需要调用IDirectDrawSurface3::Blt方法。例如,若程序中最常显示的颜色是兰色,你就可以以兰色调用 IDirectDrawSurface3::Blt方法,利用DDBLT_COLORFILL标志首先填充表面,然后再将其它的物体写到上面。该方法允许你非常快速地填充最常用的颜色,然后只需要向表面写很少数量的颜色。下面是执行颜色填充的例子:
DDBLTFX ddbltfx;
ddbltfx.dwSize = sizeof(ddbltfx);
ddbltfx.dwFillColor = 0;
ddrval = lpDDSPrimary->Blt(
    NULL,        // Destination
    NULL, NULL,  // Source rectangle
    DDBLT_COLORFILL, &ddbltfx);
 
switch(ddrval)
{
    case DDERR_WASSTILLDRAWING:
        .
        .
        .
    case DDERR_SURFACELOST:
        .
        .
        .
    case DD_OK:
        .
        .
        .
    default:
}
 
6、检测显示硬件的能力
    DirectDraw使用软件仿真来执行那些不被用户的硬件支持的DirectDraw功能。为了提高DirectDraw应用程序的执行速度,应该在创建DirectDraw对象后马上检测显示硬件的能力,然后再尽可能地利用硬件提供的特性构件程序。你可以利用IDirectDraw2::GetCaps方法检测硬件的性能。并不是所有的硬件特性都可以用软件来仿真。如果你想使用一种仅仅被一些硬件支持的特性,最好还是作好系统可能不支持这种硬件的准备,也就是要提供如果硬件不支持这种硬件的解决方法。

7、在显示内存中存储位图
    通常,从显示内存到显示内存之间的位块传输比从系统内存到显示内存之间的位块传输效率要高。因此,最好在显示内存中尽可能多地存储将要用到的精灵位图。大多数的显示适配器都包含了足够的额外内存以存储不止一个主表面和后台缓冲区。你可以利用 DDCAPS结构中的dwVidMemTotal和dwVidMemFree成员来检测显示内存中还有多少内存可用来存储位图。包含在DirectX SDK中的DirectX Viewer范例程序给出了这一过程。

8、三缓冲(Triple Buffering)
    在某些情况下,显示适配器可能有比较多的显示内存,这就有可能使用三缓冲来提高显示速度。三缓冲使用一个主表面和两个后台缓冲区。下面是初始化一个三缓冲的例子:
// The lpDDSPrimary, lpDDSMiddle, and lpDDSBack are globally
// declared, uninitialized LPDIRECTDRAWSURFACE variables.

DDSURFACEDESC ddsd;
ZeroMemory (&ddsd, sizeof(ddsd));

// Create the primary surface with two back buffers.
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);

// If we successfully created the flipping chain,
// retrieve pointers to the surfaces we need for
// flipping and blitting.
if(ddrval == DD_OK)
{
    // Get the surface directly attached to the primary (the back buffer).
    ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddsd.ddsCaps,
        &lpDDSMiddle);
    if(ddrval != DD_OK) ;
        // Display an error message here.
}
 
    你不需要保留三缓冲翻转链中所有表面的轨迹,只需要保留主表面和后台缓冲表面的指针。主表面指针用来翻转处于翻转链中的主表面,后台缓冲的指针则是用于位块传输。三缓冲允许应用程序继续位块传输到后台缓冲区,只要后台缓冲的上一次位块传输已经完成,即使翻转未完成也没有关系。执行翻转并不是一个同步事件,一次翻转可能比另一次翻转的时间还要长。因此,如果只使用了一个后台缓冲区,当
IDirectDrawSurface3::Flip方法返回DD_OK之前,程序可能会耗用不少时间处于等待状态。

9、DirectDraw应用程序和窗口风格
    如果应用程序在窗口模式下使用DirectDraw,就可以使用任何的风格建立窗口。全屏独占模式的出现不能用 WS_EX_TOOLWINDOW风格建立窗口,它应该用WS_EX_TOPMOST扩展窗口风格和WS_VISIBLE窗口风格确保图象正确显示。这两种风格可将应用程序保持在窗口Z方向的最前端,避免GDI向主表面画图。下面的例子显示了如何使独占全屏模式的应用程序安全运行:
// Register the window class, display the window, and init all DirectX and graphic objects.
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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值