DirectX5.0最新游戏编程指南 综述篇 对于众多的游戏制作者来说,Microsoft的DirectX 5的推出不啻是一个极好的消息。它包含了编制下一代计算机游戏和多媒体应用程序 的最新技术和工具,为广大的程序员提供了一整套的应用程序接口API,使程序员能够设计高性能实时的应用程序。DirectX使运行在Microsoft Windows操作系统下的游戏比运行在 MS-DOS操作系统下的游戏具有更高的性能的更好的交互性,甚至比专用的游戏机也不遑多让。 Microsoft开发DirectX的首要目的就是要提高Windows环境下游戏开发的容易性。在此之前,个人电脑上的大部分游戏都是基于MS-DOS 的,游戏开发人员必须针对不同的硬件接口卡编制不同的程序,尽管代码类似,但不可避免存在许多的重复劳动。利用DirectX, 游戏开发 人员就可以获得DirectX的硬件独立性的优点,同时又可以直接访问硬件。DirectX的最主要的目标就是提供MS-DOS中直接的硬件访问特 性,同时去掉个人电脑中再添加新的硬件时所带来的硬件识别问题。 因为硬件的升级是不可避免的,所以,DirectX实际上也提供了另一种即插即用的方法。因此,利用DirectX可以开发出高性能实时的应 用程序,可以直接访问计算机中的硬件和将来系统中回具有的硬件设备。DirectX在硬件和应用之间提供了一致的接口以减少安装和配置的 复杂性,并且使硬件的利用达到最优。利用DirectX提供的接口,程序员能充分利用硬件的特性而不需要考虑其具体细节。一个优秀的基于 Windows的高性能游戏应该能够利用系统的如下技术: .为提高性能而专门设计的加速卡 .即插即用PnP(Plug and Play) .内嵌于Windows的通信服务,包括DirectPlay DirectX 5包括七个部分,下面对各部分作以简要说明。 (1).DirectDraw 通过支持访问屏外显示内存中位图的软硬件加速技术,快速直接存取,利用硬件的位块传输和缓冲区翻转功能。 (2).DirectSound 提供软硬件声音混合和录音再生功能。 (3).DirectPlay 使得游戏在调制解调器和网络之间的连接更加简单方便。 (4).Direct3D 提供了高级保留模式(Retained-Mode)接口和低级即时模式(Immediate-Mode)接口。前者允许程序很容易地完成一个完 全的三维图形系统,后者使得程序能完全控制着色管道。 .DirectInput 提供了基于Windows游戏的输入的API和驱动程序,它不仅支持目前的键盘、鼠标和操纵杆,也支持将来的基于Windows的 输入设备。 .DirectSetup 提供了DirectX的一次性安装过程。 (7).AutoPlay 它是Windows 95的特性,当你将光盘放入光驱时,它能自动运行安装程序或游戏。事实上,AutoPlay也是Microsoft Win32 API的一部分,而不是仅存在于DirectX SDK中。. 通过查询各种DirectX对象接口,你可以检测系统中安装的是DirectX的哪个版本,DirectX SDK中的范例文件GetDXVersion.cpp中的 GetDXVersion函数可以完成这一任务。该函数创建了一些关键的DirectX对象──一个DirectDraw对象、一个DirectDrawSurface对象和一 个DirectInput对象── 来检测安装在系统中的DirectX的版本。如果系统中安装了老版本的DirectX SDK,必须确保不要包含它的INCLUDE 和LIB路径,使用DirectX 5头文件编译后的代码同DirectX 3的库链接后将会出现不可预知的错误。 因为C++语言更能够对计算机硬件作直接的访问,所以DirectX采用了C++语言作为开发的主语言。而DirectX又是专门为Visual C++ 设计的,所以编写DirectX程序最好能在Visual C++下进行。当然Borland C++5.0也没问题,只是配置有些不同罢了,下面将会提到这一问 题。为了降低编程的难度,一些公司也针对DirectX开发了运行在不同编程环境中的控件,如Arakelian Soft公司的ActiveX控件DirectStudio98 for Visual Basic5.0、Tegosoft公司的TegoSoft ActiveX for Visual Basic、还有John Pullen、Paul Bearne和Jeff Kurtz开发的运行在Delphi下的 Delphi Games Creator等。考虑到兼容性问题,最好还是能使用Visual C++。 一、DirectX和部件对象模型COM 1.1、 部件对象模型COM(Component Object Model) DirectX中的大部分API都由基于COM的对象和接口组成。COM是接口重利用的基于对象的系统的基础,是COM编程的核心模型, 它也是一种接口规范。在操作系统级别上,它又是一个对象模型。 许多DirectX API都创建为COM对象的实例。你可以将一个对象看做一个黑盒子,对象通过接口与对象通信,通过COM接口发送给对 象或从对象接收的命令称为方法。例如,IDirectDraw2::GetDisplayMode方法是通过IDirectDraw2接口从DirectDraw对象获得当前的显示模 式。对象在运行时可以同其他对象捆绑在一起,并且能够使用这些对象的接口。如果你已知道某个对象是COM对象,并且知道该对象支持 的接口,你的应用程序或其他对象就能够确定第一个对象所能执行的服务。所有COM对象都继承的一个方法是Query接口方法,它让你确 定一个对象支持的接口和创建这些接口的指针。 1.2、IUnknown 接口 所有的COM 接口都由一个称为Iunknown的接口衍生而来,该接口为DirectX提供了对象生存期的控制和操作多接口发能力。Iunknown 含有三个方法: .AddRef 当一个接口或另一个应用捆绑到一个对象上时,就使用AddRef方法将该对象的索引值加1。 .QueryInterface 通过指向特定接口的指针查询对象所支持的特性。 .Release 将对象的索引值减1,当索引值变为0时,该对象就从内存中释放。 其中AddRef和Release方法负责维护对象的索引值。例如,如果创建一个DirectDrawSurface对象,该对象的索引值就被设定为1,每 次当有一个函数返回该对象的指针时,该函数必须通过返回发指针调用AddRef方法将该对象的索引值加1。每一个AddRef的调用都必须有 一个Release的调用与其对应。当对象的索引值达到0时,该对象就被撤消,该对象的所有接口都不可再用。 QueryInterface方法测定一个对象是否支持指定的接口,如果支持,QueryInterface就返回指向该接口的指针。然后你可以使用该接口包 含的方法同对象通信。如果QueryInterface成功地返回接口的指针,它会自动调用AddRef方法增加对象的索引值。在撤消接口指针之前必须 调用Release来减少对象的索引值。 1.3、 DirectX COM 接口 DirectX中的接口是用相当基本的COM编程创建的。表征了设备的对象的每个接口都由IUnknown COM接口派生而来,如IDirectDraw2、 IDirectSound和IDirectPlay都是这样。基本对象的创建工作由动态链接库DLL中的特殊函数来处理。 典型的情况是,DirectX对象模型为每个设备提供了一个主对象。其它支持服务的对象由主对象派生而来。例如,DirectDraw对象就表 征了显示适配器。你可以利用DirectDraw来创建表征显示内存的DirectDrawSurface对象和表征硬件调色板的DirectDrawPalette对象。同样 道理,DirectSound对象表征了音频卡,利用它可创建表征音源的DirectSoundBuffer对象。 除了能够产生子对象外,设备的主对象还能测定它所表征的设备的性能,如屏幕的大小和颜色数、声卡是否支持波表合成等。 1.4、 C++和COM 接口 对C++程序员来说,COM接口就象一个抽象的基类。在C++的基类中,所有的方法都定义为纯虚的,这就意味着没有任何代码同方法 关联在一起。纯虚的C++函数和COM接口都使用一种称为虚表(vtable)的设备。一个虚表包含了应用于所给接口的所有函数的声明。如果 你想让程序或对象使用这些函数,可以先用QueryInterface方法检查对象存在的接口,获取该接口的指针,调用QueryInterface后,应用或对 象实际上就从对象中接收到了虚表的指针,通过该指针就可以调用应用于该对象的所有接口方法。 COM对象和C++的另一个相似之处是方法的第一个参数就是接口或类的名字,C++中称为this参数。因为COM对象和C++对象是完全 二进兼容的,编译器就将COM接口同C++抽象类同样处理,并且具有相同的语法,这会使得代码简单一些。 1.5、 获取新的接口 部件对象模型不是通过改变存在的接口中的方法而是通过扩展包含有新特性的新接口来更新对象的功能。在保留已有的接口状态的情况 下,COM对象能够自由扩展服务并维持同旧的应用兼容。DirectX部件遵循这一原则,例如,DirectDraw部件支持IDirectDrawSurface 接口 的三个版本: IDirectDrawSurface、IDirectDrawSurface2和IDirectDrawSurface3。要想利用新接口提供的特性,你必须调用对象的 IUnknown::QueryInterface方法,指定欲获取的接口的全局统一标识GUID(globally unique identifier)。接口的GUID在相关的头文件中声明。 下面的例子说明了如何查询一个新的接口: LPDIRECTDRAW lpDD1; LPDIRECTDRAW2 lpDD2; ddrval = DirectDrawCreate( NULL, &lpDD1, NULL ); if( FAILED(ddrval)) goto ERROROUT; // Query for the IDirectDraw2 Interface ddrval = lpDD1->Query接口(IID_IDirectDraw2, (void **)&lpDD2); if( FAILED(ddrval)) goto ERROROUT; // Now that we have an IDirectDraw2, release the original Interface lpDD1->Release(); 新的接口不支持以前版本接口所提供的方法的情况极少,IDirect3DDevice2接口刚好是这样的一个接口。如果你的应用需要早期版本的 接口提供的特性,你可以利用老的接口的GUID,使用上述例程来查询该接口。 1.6、 用C存取COM对象 使用C语言也可以调用任何COM接口方法。用C语言调用一个接口方法需要主要两件事: .方法的第一个参数始终是已经创建的调用该方法的对象(即this) .接口中的每个方法都通过对象虚表的指针来引用 下面的例子使用C语言调用IDirectDraw2::CreateSurface方法创建一个同一个DirectDraw对象关联的表面: ret = lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS, NULL); 其中 lpDD参数是同新表面关联的DirectDraw对象,同时,该方法填充一个描述表面(Surface)的结构&ddsd,返回指向新接口&lpDDS 的指针。调用 IDirectDraw2::CreateSurface方法,首先要指向该DirectDraw对象的虚表,然后获取虚表中的方法。方法中提供的第一个参数 是已经创建的DirectDraw 对象的索引。下面的代码说明了C和C++对COM对象方法调用的不同之处: ret = lpDD->CreateSurface(&ddsd, &lpDDS, NULL) 二、DirectX 5中的新内容 大家知道, DirectX 5是DirectX的最新版本,在此之前,Microsoft曾发布过DirectX、DirectX2、DirectX3(没有DirectX4)。显然,DirectX 5比DirectX3提供了更多的功能和服务,不过,利用DirectX3 API写的程序不需要修改就可以成功地编译和运行。下面是DirectX5和DirectX3 的主要不同之处: 2.1、DirectDraw DirectDraw扩展了新的视频端口能力,允许应用程序控制从硬件视频端口传送到显示内存中的DirectDraw表面的数据流。另外,DirectDraw 硬件仿真层HEL(Hardware Emulate Layer)能够利用Pentium MMX处理器提供的加速性能。DirectDraw在你第一次创建一个表面时会检测 处理器是否MMX处理器,在非奔腾处理器的计算机上,该测试会导致调试器报告出一个事件,该事件不会影响程序的性能和稳定性。 DirectDraw支持比主表面(Primary Surface)更宽的屏外表面(Off-screen Surface),只要硬件允许,你可以创建足够宽的表面。 DirectDraw支持高级图形端口AGP(Advanced Graphics Port)特性。在一个AGP设备系统,你可以在非本地显示内存创建表面。DDSCAPS 结构支持标准(本地)显示内存和AGP(非本地)显示内存之间不同的标志。DirectDraw 可以向非本地显示内存位块传输DDCAPS结构包 含的成员信息。 2.2、DirectPlay DirectPlay包含了一个新的接口IDirectPlay3,除了新的方法之外,其它同IDirectPlay2完全相同。同样地,IDirectPlayLobby2也是 IdirectPlayLobby的升级版本。DirectPlay的新功能包括通过创建连接快捷方式来隐藏服务提供者(Service Provider)对话的能力,还可以保 持可用会话的更新列表,应用SetSessionDesc方法更好地支持口令保护会话,支持保密服务连接及使用CoCreateInstance直接创建多个 DirectPlay对象。 2.3、DirectSound DirectSound 包含了一个新的接口IksPropertySet,使得支持由声卡和相关驱动程序提供的扩展服务。基于COM的函数DirectSoundCapture 也是新的,它可以直接访问驱动程序。 2.4、Direct3D Direct3D即时模式(Immediate Mode)现在支持Drawing Primitive,而不必直接在缓冲区中工作。Direct3D保留模式(Retained Mode) 支持插值使你能够调和颜色,平滑地移动物体,变形网格,并且执行许多其他的变换。保留模式也支持渐进网格,即允许你先开始于一个粗 糙的网格,然后再细化它。 5、DirectInput DirectInput提供了操纵杆的COM接口,同时还提供了键盘和鼠标等力反馈装置的COM接口。 6、DirectSetup DirectSetup现在包含了更多的用户接口定制能力,而这是由一个回调函数提供的。该回调函数将当前的安装状态传递给安装程序,你可 以通过定制的用户接口利用这一信息来显示安装状态。另外,DirectSetup提供了多个玩家的游戏的方法,在游戏中,玩家可以使用 DirectPlayLobby从注册表中去掉注册信息。 6、AutoPlay AutoPlay同DirectX3中一样,未做变动。 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; } DirectX5.0最新游戏编程指南 DirectDraw教程篇 DirectX是为Visual C++的用户准备的,因此要编制DirectDraw游戏程序,最好对VC要有一定的了解。不愿意使用VC的用户也可以 利用消息Arakelian Soft公司开发的专门针对Visual Basic5.0用户的ActiveX控件DirectStudio98或Tegosoft公司的TegoSoft ActiveX for Visual Basic。不过,如果想充分发挥DirectX的性能,并且希望保持程序的兼容,那么最好还是使用Visual C++。 为了叙述方便,假定已经安装了DirectX5.0 SDK 和Visual C++ 5.0,其目录分别是C:/DX5SDK和C:/Program Files/DevStudio。如果你使 用了另一种编译器或安装到了其它目录下,必须将下面的例子做适当的修改才能运行。有人安装了DirectX SDK后却不知怎样使用,因为它 是基于Visual C++的,却没有一个界面友好的集成开发环境,因此必须对Visual C++进行适当的配制。 一、配置DirectX SDK 1.1、配置Microsoft Developer Studio 为了编译DirectX SDK提供的例子,需要打开一个新的project workspace,插入适当的文件,设置环 境变量使得编译器能够找到需要的链接库和包含文件,下面描述了设置的全部过程。启动Microsoft Developer Studio,安装下述步骤创建工 程: .在File菜单,选择New; .在New对话框中选择Project中的Win32 Application,在Project Name输入DDEX1 .在Location文本框输入放置工程文件的位置,点OK按钮 .一个新的DDEX1 Classes文件夹就出现在workspace窗口的左边了。 创建了工程后,需要使用如下步骤向工程插入适当的文件: .在Project菜单选择Add toProject|Files .浏览到C:/DX5SDK/SDK/SAMPLES/DDEX1 目录,选择所有的文件 .选择OK,该目录下的DDEX1.CPP、DDEX1.RC、RESOURCE.H就加入到工程了。 然后设置包含文件的路径: .在Tools菜单,选择Options,就弹出Options对话框 .选择Directories ,在Show Directories For列表框选择Include files .在Directories:列表框双击列表底部的空白行,输入C:/DX5SDK/SDK/INC. .同样再加入另一个路径C:/DX5SDK/SDK/SAMPLES/MISC .选择OK按钮, 设置链接库目录: .在Show Directories For列表框选择Library files .在Directories:列表框双击底部空白行,输入C:/DX5SDK/SDK/LIB. .单击OK按钮。 最后设置建立应用程序时链接的模块: .在Project菜单单击Settings. .选择Link .在Category下拉框选择General. .在Object/Library模块列表框加入Ddraw.lib和Winmm.lib. .单击OK. 1.2、配制NMAKE路径 有时候命令行的方式比集成环境更加方便,所以许多有经验的程序员更愿意用命令行的方式来建立应用程序。下面是包含文件和链接库模块 的路径: @echo off set PATH=C:/Program Files/DevStudio/SharedIDE/Bin; C:/Program Files/DevStudio/Vc/Bin;%PATH% set INCLUDE=C:/Program Files/DevStudio/Vc/include; C:/Program Files/DevStudio/Vc/Mfc/include;C:/DX5SDK/SDK/INC;%INCLUDE% set LIB= C:/Program Files/DevStudio/SharedIDE/Vc/lib; C:/Program Files/DevStudio/Vc/Mfc/lib; C:/DX5SDK/SDK/LIB;%LIB% set INIT= C:/Program Files/DevStudio;%INIT% 将上述内容加入Autoexec.bat。在例子的目录下输入 NMAKE 将会在当前目录下创建一个DEBUG目录,并将生成的可执行文件放在该目录下。 为了在学习的过程中熟悉DirectX SDK,我们将按照DirectX SDK提供的范例程序的顺序由浅入深,循序渐进。 3、为Borland C++5.0配置DirectX SDK 尽管DirectX 5 SDK是主要为Visual C++用户准备的,但Microsoft并未忘记众多的Borland C++用户,所以在DirectX SDK中也提供了 DirectX的Borland C++库。不过,可能出于竞争的缘故(猜测而已),安装后的DirectX SDK中不没有Borland C++库。这就需要用户自己来 处理这一恼人的问题了。我们知道,DirectX 5 SDK是以一个IDX5SDK.EXE发布的,运行IDX5SDK后,它先将压缩的文件全部解压到某个 目录下(如D:/DX5SDK),然后再运行该目录下的SETUP.EXE安装DirectX SDK(假设目录为C:/DX5SDK)。实际上,在解压后的目录中包 含了一个D:/DX5SDK/SDK/LIB/BORLANDC目录,该目录下就是Borland C++的链接库文件。但在SETUP安装时,安装程序并没有把该目 录复制到安装目录中。解决方法很简单,即SETUP安装完成后,再建立一个C:/DX5SDK/SDK/LIB/Borland,将目录D:/DX5SDK/SDK/LIB/Borland 下的所有文件都复制到C:/DX5SDK/SDK/LIB/Borland目录下。然后在Borland C++5.0的集成环境中如同配置Visual C++5.0那样配置工程文 件。 二、第一个DirectDraw实例 要使用DirectDraw,首先必须创建DirectDraw对象的一个实例来表征计算机上的显示适配卡,然后使用接口方法来处理对象。另外还需 要创建一个或多个DirectDrawSurface对象的实例来显示游戏。DDEX1首先创建一个DirectDraw对象,再创建一个主表面(primary surface) 和一个后台缓冲区(back buffer),然后在表面之间转换。DDEXx例子都是用C++写成的,如果你使用的是C编译器,必须将代码做适当改 动,至少要加入虚表和指向接口方法的指针。 1、首先初始化DirectDraw对象 DDEX1 程序在doInit函数包含了DirectDraw 的初始化代码: // 创建主DirectDraw 对象 ddrval = DirectDrawCreate( NULL, &lpDD, NULL ); if( ddrval == DD_OK ) { // 获取独占模式 ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ); if(ddrval == DD_OK ) { ddrval = lpDD->SetDisplayMode( 640, 480, 8 ); if( ddrval == DD_OK ) { //创建带有一个后台缓冲区的主表面 ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ); if( ddrval == DD_OK ) { //获取后台缓冲区的指针 ddscaps.dwCaps = DDSCAPS_BACKBUFFER; ddrval=lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack); if( ddrval == DD_OK ) { // 画出一些文本 if (lpDDSPrimary->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) ); lpDDSPrimary->ReleaseDC(hdc); } if (lpDDSBack->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) ); lpDDSBack->ReleaseDC(hdc); } // 创建翻转页面的计时器 if( SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL ) ) { return TRUE; } } } } } } wsprintf(buf, "Direct Draw Init Failed (%08lx)/n", ddrval ); ............ 下面详细说明初始化DirectDraw 对象的创建和准备表面的每一步骤。 2、创建DirectDraw 对象 创建DirectDraw 对象的一个实例,应该用DirectDrawCreate API 函数,也可以用COM中的CoCreateInstance函数。DirectDrawCreate 用一个全局统一标志符GUID(Globally Unique IDentifier)来表征显示设备,在大多数情况下GUID为NULL(使用系统的缺省显示设备,既“空 设备”);指针指向DirectDraw对象的地址;第三个参数总是NULL(供将来扩展使用)。下述代码表明了如何创建一个DirectDraw对象,并且 检验是否成功。 ddrval = DirectDrawCreate( NULL, &lpDD, NULL ); if( ddrval == DD_OK ) { // lpDD is是合法的DirectDraw 对象 } else { // DirectDraw对象不能被创建 } 3、设置显示模式 设置DirectDraw 应用程序的显示模式需要两步:首先调用IDirectDraw::SetCooperativeLevel方法来设定该模式下的要求,一旦确定了要 求,再用IDirectDraw::SetDisplayMode 方法来选择显示分辨率。 在改变显示分辨率之前,还必须通过IDirectDraw::SetCooperativeLevel方法来指定DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN 标志。 这样能使游戏程序完全控制显示设备,其它的应用程序不能同时共享显示设备。DDSCL_FULLSCREEN标志表示将程序设为全屏模式。下 面的代码显示了如何使用IDirectDraw::SetCooperativeLevel方法: HRESULT ddrval; LPDIRECTDRAW lpDD; // already created by DirectDrawCreate ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ); if( ddrval == DD_OK ){ // 全屏独占方式设置成功 } else { // 调用不成功,但程序仍然能继续运行 } 如果IDirectDraw::SetCooperativeLevel不返回DD_OK,你仍然可以运行该程序,但不是全屏模式,有时可能产生一些无法预料的错误。 因此你应该显示一条错误信息,让用户知道发生了什么事,由用户来决定是否继续运行游戏。 使用IDirectDraw::SetCooperativeLevel时,必须向窗口 (HWND)传送一个句柄,让窗口决定何时非正常地终止应用程序。例如,若发生 了GP错误或GDI被翻转(flip)到了后台缓冲区,用户就无法访问当前屏幕。为了避免这种情况,DirectDraw有一个后台等待进程,它俘获所 有发往该窗口的消息,用这些消息来确定应用程序何时终止。如果创建了新的窗口,必须确定该窗口为活动的,否则,就会有一系列的事件 无法继续工作。 4、改变显示模式 一旦选择了应用程序的工作模式,就可以使用IDirectDraw::SetDisplayMode方法来改变显示模式,下面的代码将显示模式设置成 640x480x256: HRESULT ddrval; LPDIRECTDRAW lpDD; // already created ddrval = lpDD->SetDisplayMode( 640, 480, 8 ); if( ddrval == DD_OK ) { // 改变模式成功 } else { // 显示模式不能改变 // 系统可能不支持该模式 } 当设定显示模式时,应该确保如果用户的设备不支持更高的分辨率,应用程序应该返回系统支持的标准模式。如果显示示配卡不支持设 计的分辨率,IDirectDraw::SetDisplayMode返回一个DDERR_INVALIDMODE 错误值。因此,在设置分辨率时,应该先用 IDirectDraw::EnumDisplayMode方法检测用户的显示设备的性能。 5、创建可翻转表面(Flippable Surface) 设定了显示模式后,必须创建放置应用程序的表面。在DDEX1例中,我们使用IDirectDraw::SetCooperativeLevel 方法将程序设成独占 全屏模式,然后可以创建翻转表面。如果使用IDirectDraw::SetCooperativeLevel 设成DDSCL_NORMAL模式,就只能创建块写方式的表面 了。 6、定义表面要求 创建可翻转表面的的第一步是在DDSURFACEDESC结构中定义表面的要求。下面的代码描述了结构的定义及创建可翻转表面所需要的 标志: // 创建带有一个后台缓冲区的主表面 ddsd.dwSize = sizeof( ddsd ); ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; ddsd.dwBackBufferCount = 1; 例中,成员变量dwSize被设为DDSURFACEDESC结构的大小。dwFlags标志指定DDSURFACEDESC结构中哪些域可存放有效信息。 在 DDEX1例中,dwFlags指出了需要使用DDSCAPS结构(DDSD_CAPS)并创建一个后台缓冲区 (DDSD_BACKBUFFERCOUNT)。dwCaps指 出 DDSCAPS结构会用到的标志,本例中它指定了一个主表面(DDSCAPS_PRIMARYSURFACE),一个翻转表面(DDSCAPS_FLIP)和一个复 杂表面 (DDSCAPS_COMPLEX)。最后,程序指定了一个后台缓冲区。后台缓冲区是背景图像和人物将写入的位置,它可以转化为主表面。 本例中,后台缓冲区的数目为1,事实上,只要有足够的显示内存,可以创建任意多个后台缓冲区,一般每1M的显示内存只能用来创建一 个后台缓冲区。表面的内存既可以是显示内存,也可以是系统内存。DirectDraw在使用完了显示内存时(例如在仅有1M的显示内存创建了 2个后台缓冲区)会自动使用系统内存。你可以通过将DDSCAPS结构中的dwCaps设定为DDSCAPS_SYSTEMMEMORY或 DDSCAPS_VIDEOMEMORY来指定只使用系统内存或只使用显示内存。如果指定了 DDSCAPS_VIDEOMEMORY又没有足够的显示内存来 创建表面,IDirectDraw::CreateSurface 将返回一个DDERR_OUTOFVIDEOMEMORY错误。 7、创建表面 填完了DDSURFACEDESC结构,就可以使用该结构和lpDD了,lpDD是用DirectDrawCreate 方法创建的DirectDraw 对象的指针,下 面的代码显示了这一过程: ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ); if( ddrval == DD_OK ) { // lpDDSPrimary points to new surface } else{ // surface was not created return FALSE; } 如果调用成功,IDirectDraw::CreateSurface函数就返回指向主表面的指针lpDDSPrimary 。若主表面指针可用,就可以调用 IDirectDrawSurface::GetAttachedSurface 方法取得后台缓冲区的指针,如下所示: ddscaps.dwCaps = DDSCAPS_BACKBUFFER; ddrval = lpDDSPrimary->GetAttachedSurface( &ddcaps, &lpDDSBack ); if( ddrval == DD_OK ) { // lpDDSBack points to the back buffer } else{ return FALSE; } 如果IDirectDrawSurface::GetAttachedSurface调用成功,通过提供主表面的地址和设置DDSCAPS_BACKBUFFER 标志,lpDDSBack 参 数就指向后台缓冲区。 8、着色表面 创建了主表面和后台缓冲区后,DDEX1使用标准的Windows GDI 函数将一些文本提交到主表面和后台缓冲区,代码如下: if (lpDDSPrimary->GetDC(&hdc) == DD_OK){ SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) ); lpDDSPrimary->ReleaseDC(hdc); } if (lpDDSBack->GetDC(&hdc) == DD_OK){ SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) ); lpDDSBack->ReleaseDC(hdc); } 例中使用了IDirectDrawSurface::GetDC方法来设备上下文的句柄且锁定该表面。如果不想使用要求句柄的Windows函数,还可以使用 IDirectDrawSurface::Lock和IDirectDrawSurface::Unlock 方法来锁定和解锁后台缓冲区。锁定表面的内存(不管是整个表面还是其中的一部 分)能确保你的应用程序和系统不会同时访问这块内存。另外,除非给内存解锁,程序不能翻转表面。本例在锁定表面后使用Windows GDI 函 数SetBkColor来设置背景颜色,使用SetTextColor来设置文本颜色,然后使用TextOut将文本输出到表面。当文本写入缓冲区后,例中使用 了IDirectDrawSurface::ReleaseDC方法来解锁表面并释放句柄。良好的习惯是,向后台缓冲区写数据完成后,马上调用 IDirectDrawSurface::ReleaseDC或IDirectDrawSurface::Unlock。 一般来讲,当向表面写数据时,该表面就是后台缓冲区,然后将缓冲区翻转成主表面显示出来。在DDEX1中,第一次翻转表面之前有 一个重要的延迟。于是DDEX1就将数据写入主缓冲区,避免开始显示时有太长的时间间隔。后面将会讲到,DDEX1只在 WM_TIMER期 间向后台写数据。初始化函数或标题头可能会写入主缓冲区。应该注意的是,一旦使用IDirectDrawSurface::Unlock对表面解锁,指向表面的 指针就变成无效,必须再次使用IDirectDrawSurface::Lock方法才能获取该表面内存的有效指针。 9、写表面及翻转表面 完成了初始化后,DDEX1开始处理消息循环。在消息循环的过程中,完成锁定后台缓冲区——写入新的文本——解锁后台缓冲区—— 翻转表面的过程。WM_TIMER包含了写数据和翻转表面的大部分代码。 WM_TIMER消息的前半部分用于向后台缓冲区写数据,“phase”变量决定是写主缓冲区消息还是写后台缓冲区消息。如果phase为1, 表示写主缓冲区的消息,然后将phase改变为0;若为0,表示写后台缓冲区的消息,然后将phase改变为1。注意,两种情况中的消息都是 写向后台缓冲区。后台缓冲区写入了消息后,使用IDirectDrawSurface::ReleaseDC方法解锁。下面的代码实现了这一点: case WM_TIMER: // Flip surfaces if( bActive ) { if (lpDDSBack->GetDC(&hdc) == DD_OK) { SetBkColor( hdc, RGB( 0, 0, 255 ) ); SetTextColor( hdc, RGB( 255, 255, 0 ) ); if( phase ) { TextOut( hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) ); phase = 0; } else { TextOut( hdc, 0, 0, szBackMsg, lstrlen(szBackMsg) ); phase = 1; } lpDDSBack->ReleaseDC(hdc); } 表面内存解锁后,使用IDirectDrawSurface::Flip方法将后台缓冲区翻转成主表面,代码如下: while( 1 ) { HRESULT ddrval; ddrval = lpDDSPrimary->Flip( NULL, 0 ); if( ddrval == DD_OK ) { break; } if( ddrval == DDERR_SURFACELOST ) { if(ddrval = lpDDSPrimary-&g>val != DD_OK ) { break; } } if( ddrval != DDERR_WASSTILLDRAWING ) { break; } } 例中,lpDDSPrimary指明了主表面及其后台缓冲区。调用IDirectDrawSurface::Flip方法后,主表面和后表面交换。调用成功后,返回 DD_OK,程序终止While循环;如果返回DDERR_SURFACELOST,表明可能是表面丢失,需要用IDirectDrawSurface::Restore 方法恢复该 表面,若恢复成功,就再一次调用IDirectDrawSurface::Flip方法;如果失败,程序终止While循环并返回一个错误值。另外,如前所述,即 使调用IDirectDrawSurface::Flip成功,交换也不是立即完成,它将等到系统中在此之前的表面交换都完成后才进行。例如,前一次的表面翻 转还未发生时,IDirectDrawSurface::Flip 就返回DDERR_WASSTILLDRAWING。本例中,IDirectDrawSurface::Flip继续循环直到返回DD_OK.。 10、释放DirectDraw 对象 当按了F12后,DDEX1程序在退出之前先处理WM_DESTROY消息,该消息调用了finiObjects函数,而finiObjects函数包含了所有的 IUnknown Release的调用,代码如下: static void finiObjects( void ){ if( lpDD != NULL ) { if( lpDDSPrimary != NULL ) { lpDDSPrimary->Release(); lpDDSPrimary = NULL; } lpDD->Release(); lpDD = NULL; } } /* finiObjects */ 程序检测DirectDraw对象的指针(lpDD)和DirectDrawSurface对象的指针(lpDDSPrimary) 是否等于NULL,本例中显然不为NULL。然 后DDEX1调用 IDirectDrawSurface::Release方法将DirectDrawSurface 对象的参考值减1,这将会使得其参考值变为0,DirectDrawSurface 对 象就被释放了,DirectDrawSurface的指针被设为NULL值,然后撤消。程序又调用IDirectDraw::Release就DirectDraw对象的参考值减1变 为0,释放DirectDraw对象及其指针。 上述的DDEX1是DirectDraw最基本的应用,它首先创建DirectDraw对象和DirectDrawSurface对象,创建一个主表面及其后台缓冲区, 将文本输出到后台缓冲区,然后转化表面。第二个例子DDEX2扩展了DDEX1的功能,它可以将一个位图文件调入后台缓冲区。第三个例 子DDEX3 则更进一步,除了一个主表面及后台缓冲区外,还创建了两个屏外表面,将位图调入每一个屏外表面,然后使用 IDirectDrawSurface::BltFast方法将一个屏外表面的内容位块传输到后台缓冲区,然后翻转表面并将另一个屏外表面的内容位块传输到后台缓 冲区。下面将详细讨论这些功能。 11、将位图调入表面 如DDEX1中一样,doInit函数是DDEX2的初始化函数,两者的实质一样,一直到下面的代码: lpDDPal = DDLoadPalette(lpDD, szBackground); if (lpDDPal == NULL) goto error; ddrval = lpDDSPrimary->SetPalette(lpDDPal); if( ddrval != DD_OK ) goto error; // load a bitmap into the back buffer. ddrval = DDReLoadBitmap(lpDDSBack, szBackground); if( ddrval != DD_OK ) goto error; 代码的第一行从函数DDLoadPalette返回一个值,该函数在C:/ DX5SDK/SDK/SAMPLES/MISC中的Ddutil.cpp文件中,因此编译DDEX2 时需要将Ddutil.cpp和Ddutil.h加入过程。大部分的DirectDraw程序都需要该文件。在DDEX2中,DDLoadPalette函数从Back.bmp文件中 创建一个DirectDrawPalette对象。DDLoadPalette函数首先检查用于创建调色板的文件或资源十分存在,如果不存在,就创建一个缺省调色 板。在DDEX2中,它从位图文件中抽取调色板信息并存储在由ape指向的结构,然后如下创建 DirectDrawPalette对象: pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL); return ddpal; 当IDirectDraw::CreatePalette方法返回时,ddpal就指向该DirectDrawPalette对象。ape是指向一个结构的指针,该结构能包含2/4/16/256 个线性的实体,实体的数目由IDirectDraw::CreatePalette 调用的dwFlags参数决定。本例中,dwFlags设定为DDPCAPS_8BIT,这表示该结 构中有256个实体,每个实体有四个字节(红色、绿色、蓝色和标志字节)。 12、设置调色板,将位图调入后台缓冲区 创建了调色板之后,可以通过调用IDirectDrawSurface::SetPalette 方法将DirectDrawPalette 对象的指针ddpal传送给主表面,代码如下: ddrval = lpDDSPrimary->SetPalette(lpDDPal); if( ddrval != DD_OK ) // SetPalette failed 调用了IDirectDrawSurface::SetPalette方法之后,DirectDrawPalette对象就和DirectDrawSurface对象挂(hook)在一起了,需要改变调色板 时,只需要创建一个新的调色板对其进行设置就可以了。 DirectDrawPalette对象同DirectDrawSurface对象挂在一起后,DDEX2使用以下代码将Back.bmp文件装入后台缓冲区: // load a bitmap into the back buffer. ddrval = DDReLoadBitmap(lpDDSBack, szBackground); if( ddrval != DD_OK ) // Load failed DDReLoadBitmap是Ddutil.cpp中的另一个函数,它将位图从文件或资源中调入已经存在的DirectDraw表面。在本例中,它将szBackground 指向的Back.bmp装入lpDDSBack指向的后台缓冲区。DDReLoadBitmap调用DDCopyBitmap函数将文件拷贝到后台缓冲区并延展为适当的 尺寸。DDCopyBitmap函数将位图拷入内存,使用GetObject函数获取位图的大小,然后用下述代码获取将要放置位图的后台缓冲区的大小: // get size of surface. ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH; pdds->GetSurfaceDesc(&ddsd); ddsd 是指向DDSURFACEDESC结构的指针,该结构储存了DirectDraw表面的当前描述。DDSURFACEDESC 结构的成员描述了由 DDSD_HEIGHT 和DDSD_WIDTH指定了表面的高和宽。IDirectDrawSurface::GetSurfaceDesc方法使用合适的值调入结构,例中高为480, 宽为640。DDCopyBitmap函数锁定表面并将位图拷贝到后台缓冲区,然后用StretchBlt函数对位图进行拉伸或压缩,代码如下: if ((hr = pdds->GetDC(&hdc)) == DD_OK){ StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY); pdds->ReleaseDC(hdc); } 13、从屏外表面位块传输 DDEX2同DDEX1基本相同。DDEX2打开一个位图文件并将它送往后台缓冲区,然后翻转后台缓冲区和主表面。但这对显示位图并不 特别理想,DDEX3扩展了DDEX2的功能,它加入了两个屏外缓冲区,每个缓冲区都存储一个位图。下面是DDEX3中的doInit函数的一部 分,功能是创建两个屏外缓冲区: // Create an offscreen bitmap. ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwHeight = 480; ddsd.dwWidth = 640; ddrval = lpDD->CreateSurface( &ddsd, &lpDDSOne, NULL ); if( ddrval != DD_OK ){ return initFail(hwnd); } // Create another offscreen bitmap. ddrval = lpDD->CreateSurface( &ddsd, &lpDDSTwo, NULL ); if( ddrval != DD_OK ){ return initFail(hwnd); } 从代码中可以看到,dwFlags指明了程序使用DDSCAPS结构,并设置缓冲区的高和宽。由DDSCAPS 结构中的DDSCAPS_OFFSCREEN 标志指定该表面是屏外缓冲区,在DDSURFACEDESC结构中将高和宽设为480和640,然后使用IDirectDraw::CreateSurface方法来创建表 面。因为两个屏外表面的大小一样,创建第二个缓冲区只需要再运行一次IDirectDraw::CreateSurface即可(当然要用不同的指针)。你还可以 在DDSCAPS 中设置DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY 来指定屏外缓冲区放在显示内存还是系统内存。将位 图存放在显示内存可以加快后台缓冲区与屏外表面之间的数据传输速度,这在位图动画中非常重要。但是,如果你为屏外缓冲区指定了 DDSCAPS_VIDEOMEMORY又没有足够的显示内存调入整个位图,当创建该表面时,程序就会返回一个DDERR_OUTOFVIDEOMEMORY 的错误值。 14、将位图文件调入屏外表面 创建了两个屏外表面后,DDEX3使用了InitSurfaces函数将位图从Frntback.bmp文件装入到两个表面。InitSurfaces函数使用了 DDCopyBitmap函数调入两个位图,代码如下: // Load our bitmap resource. hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), szBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); if (hbm == NULL) return FALSE; DDCopyBitmap(lpDDSOne, hbm, 0, 0, 640, 480); DDCopyBitmap(lpDDSTwo, hbm, 0, 480, 640, 480); DeleteObject(hbm); return TRUE; Frntback.bmp文件由两部分组成,一半在上,一半在下。DDCopyBitmap函数将上半部分调入第一个屏外表面lpDDSOne,下半部分调入第 二个表面lpDDSTwo。 15、将屏外表面位位块传输到后台缓冲区 WM_TIMER包含了写表面和翻转表面的代码。在DDEX3中,它选择适当的屏外表面,并将其位块传输到后台缓冲区,代码如下: rcRect.left = 0; rcRect.top = 0; rcRect.right = 640; rcRect.bottom = 480; if(phase) { pdds = lpDDSTwo; phase = 0; } else { pdds = lpDDSOne; phase = 1; } while( 1 ) { ddrval = lpDDSBack->BltFast( 0, 0, pdds, &rcRect, FALSE ); if( ddrval == DD_OK ) { break; } “phase”决定了准备将哪一个屏外表面位块传输到后台缓冲区,然后调用IDirectDrawSurface::BltFast方法将选定的屏外表面位块传输到 后台缓冲区,从左上角的位置0,0开始。rcRect指向定义了屏外表面的左上角和右下角的RECT结构。最后一个参数设为FALSE或0,指明 不使用特殊的传输标志。一旦屏外表面被传送到后台缓冲区,就可以利用前面的方法将后台缓冲区和主表面相互翻转了。 三、创建动画 上面的例子都只是将数据写入后台缓冲区,然后将后台缓冲区与主表面翻转,其速度并不太快。下面的例子DDEX4和DDEX5优化了 实时功能,使看起来更象一个真正的游戏。DDEX4显示了怎样为表面设置 Color key,怎样使用IDirectDrawSurface::BltFast方法将屏外表面 各部分拷贝到后台缓冲区以产生动画。DDEX5加入了读取调色板并在动画运行时改变调色板的功能。 1、Color Key和位图动画 在DDEX3例中描述了将位图放入屏外缓冲区的一种主要方式。DDEX4则使用了将背景和一系列的精灵(sprite,本例中精灵是圆环)装入 屏外表面的技术,然后使用, IDirectDrawSurface::BltFast方法将屏外表面的各部分拷贝到后台缓冲区。doInit函数除了具有前面例子中的功能 外,还包括了为精灵设置Color key的代码。Color key是用于设置透明度的颜色值。当使用硬件块写方式时,矩型区域内除了设为 color key 的像素,其它的像素都被块写,由此在表面上产生非矩型的精灵。设置color key的代码如下: // Set the color key for this bitmap (black) // NOTE this bitmap has black as entry 255 in the color table. // ddck.dwColorSpaceLowValue = 0xff; // ddck.dwColorSpaceHighValue = 0xff; // lpDDSOne->SetColorKey( DDCKEY_SRCBLT, &ddck ); // if we did not want to hard code the palette index (0xff) // we can also set the color key like so... DDSetColorKey(lpDDSOne, RGB(0,0,0)); return TRUE; 例中给出了设置color key的两种不同方法。第一种方法是注释内的3行,先设定DDCOLORKEY 结构中color key的范围,再调用 IDirectDrawSurface::SetColorKey方法将color key 设置成黑色(假定位图在颜色表中以黑色作为调色板索引项255)。第二种方法是调用 DDSetColorKey 函数设置颜色的RGB值来选择color key,黑色就是RGB(0,0,0)。DDSetColorKey 函数调用了DDColorMatch函数, DDColorMatch 存储放置于lpDDSOne表面的位图的0,0位置像素的颜色值,然后用提供的RGB值赋给0,0位置的像素,并将该颜色值屏蔽。 完成了这一步骤后,原来的颜色就可重新放回0,0处并用正确的Color Key调用DDSetColorKey函数,调用成功后,color key就放入 DDCOLORKEY 结构中的成员变量dwColorSpaceLowValue ,同时也拷贝到dwColorSpaceHighValue成员,然后再调用 IDirectDrawSurface::SetColorKey 设置Color key。 CLR_INVALID是DDSetColorKey 和DDColorMatch函数中另一个有用的变量。如果在 DDSetColorKey中以该值作为color key,位图 左上角的像素就会作为color key使用。要想实现这一功能,需要调入位图文件All.bmp,将0,0处的像素值该为黑色,保存更改,然后如下 改变对DDSetColorKey 的调用: DDSetColorKey(lpDDSOne, CLR_INVALID); 重新编译DDEX4,DDEX4就会使用0,0处的像素值作为color key了。 2、DDEX4中的动画 DDEX4利用All.bmp中的红色圆环调用updateFrame 函数来创建一个简单的动画。动画由圆环的3各位置组成。例子通过比较Win32 中的GetTickCount和上次该函数开始运行的时间来判断是否重画哪个圆环,然后使用IDirectDrawSurface::BltFast方法将背景从屏外表面 lpDDSOne位块传输到后台缓冲区,然后再使用已经设定好的color key将圆环块写入后台缓冲区。在所有的圆环都块写到后台缓冲区后,调 用IDirectDrawSurface::Flip方法翻转后台缓冲区和主表面。 3、动态改变调色板 DDEX5描述了任何在程序运行时动态地改变调色板,尽管在游戏中这并不总是用到。DirectDraw确实能很好地控制调色板。DDEX5中 的下述代码将All.bmp文件的下半部分中的调色板装入: // First, set all colors as unused for(i=0; i<256; i++) { torusColors[i] = 0; } // lock the surface and scan the lower part (the torus area) // and remember all the index's we find. ddsd.dwSize = sizeof(ddsd); while (lpDDSOne->Lock(NULL, &ddsd, 0, NULL) == DDERR_WASSTILLDRAWING); // Now search through the torus frames and mark used colors for( y=480; y<480+384; y++ ){ for( x=0; x<640; x++ ) { torusColors[((BYTE *)ddsd.lpSurface)[y*ddsd.lPitch+x]] = 1; } } lpDDSOne->Unlock(NULL); 数组torusColors用于指定All.bmp中的下半部分调色板的索引值,数组在使用之前都初始化为0。然后锁定屏外表面来检测某颜色索引 值是否已用。数组torusColors开始于位图的第0行第480列,数组中的颜色索引值由位图表面 放置于内存的位置的一个字节决定,该位置 由DDSURFACEDESC 结构中的lpSurface成员变量来决定,lpSurface 指向对应于位图(0,480)处的内存地址(y*lPitch+x)。数组中设定的颜 色索引值用来检测调色板中哪些颜色被替换。因为背景和红色圆环之间没有公用的颜色,所以只有那些同圆环联在一起的颜色值才会被替换。 4、替换调色板 DDEX5中的updateFrame函数同DDEX4中的基本相同,先将背景块写入后台缓冲区,再将3个红色圆环块写到前景。但在翻转表面之 前,updateFrame用doInit函数创建的调色板索引值来改变主表面的调色板,代码如下: // Change the palette if(lpDDPal->GetEntries( 0, 0, 256, pe ) != DD_OK) { return; } for(i=1; i<256; i++){ if(!torusColors[i]) { continue; } pe[i].peRed = (pe[i].peRed+2) % 256; pe[i].peGreen = (pe[i].peGreen+1) % 256; pe[i].peBlue = (pe[i].peBlue+3) % 256; } if(lpDDPal->SetEntries( 0, 0, 256, pe) != DD_OK){ return; } IDirectDrawPalette::GetEntries方法在DirectDrawPalette对象中查询调色板的值,因为 pe指向的调色板实体的值有效,方法就返回 DD_OK,程序继续运行。然后循环检测torusColors在初始化中是否被设为1,如果索引值被设为1,由pe指向的调色板的红色、绿色、蓝 色的值就被替换。在所有的被标记的调色板实体替换完毕后,再调用IDirectDrawPalette::SetEntries方法来真正改变DirectDrawPalette 中的实 体。如果该调色板已经设给主表面,上面的改变就会立即完成。完成了这一工作,剩下的就是同前面一样的翻转表面了。 四、使用覆盖表面 本例将使用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程序来说,覆 盖就不再需要了。如果在应用程序中还需要使用该覆盖,就只需简单地隐藏覆盖,而不要释放它,然后在需要的时候再重新显示。 五、DirectDraw中其它的DirectDraw范例 要熟练掌握有关DirectDraw的应用,还应该多研究包含在DirectX SDK:之中的下述范例。 1、Stretch 描述了怎样在一个窗口中创建一个非独占模式的动画,它具有剪切位块传输和拉伸剪切位块传输的功能。 2、Donut 描述了多个独占模式的应用同非独占模式应用之间的交互。 3、Wormhole 该范例描述了详细的调色板动画。 4、Dxview 详细描述了怎样获取显示硬件的能力。 其它的还有Duel、Iklowns、Foxbear、Palette和Flip2d等,只要多这些范例多加分析,掌握DirectX最基本的技术DirectDraw是不难的。
DirectX5.0最新游戏编程指南
最新推荐文章于 2008-05-28 14:02:00 发布