在开始学习DirectDraw编程之前,有一些题外话要说明,以下内容均是个人的心得和体会,如果其中有什么谬误之处,敬请谅解,同时个人不对可能造成的后果负责。。
以下几点是在编制DirectX应用程序时应该注意的:
- 尽管使用VB或DELPHI都可以制作DirectX应用程序,但考虑到代码的效率,还是应使用C或C++。其中,C++是面向对象的编程语言,可以使你的程序更易于维护,如果再考虑到代码的兼容性,我推荐使用Microsoft Visual C++ 5.0集成开发环境。当然,使用Borland C++ 5.0也是一个不错的选择。
- 你应该已经有编制Windows应用程序的经验,以及C++的基础。
- 熟悉你将要使用的编程工具的运用。例如,如何为集成开发环境设置各种参数,以及编译程序和连接程序的命令行参数。
- 确认你已下载了微软的DirectX 5.0 SDK中的相关文件,特别是头文件和库文件,这些都是编程所必需的。
- 确认你已经安装了DirectX 5.0或更高版本的运行时刻库,因为本教程的所有程序均使用了DirectX的新接口(Interface),必须要5.0或更高版本支持。
DirectDraw是什么?
DirectDraw是DirectX中的关于视频输入输出的基本部分,使用DirectDraw可以方便地编制出高效的视频处理程序,只要用户的硬件支持DirectDraw,就能保证你的代码可以处理它们。 简单地说,一个DOS程序员可以方便地直接访问视频显存,从而高效地处理视频动画,而在Windows的32位环境中,使用DirectDraw可以做类似的工作(而且做得更好)。例如,在DOS环境中的320x200x256色图形模式中,你可以通过在地址A000:0000开始处的一片内存区进行直接读写的方式来处理视频操作,而在Windows环境中,DirectDraw也提供一片内存区供你直接读写,使你能更好地完成相似的操作。 当然,DirectDraw提供的远不止这些,但一个游戏程序员可能更关心如何直接访问视频显存,以及如何高效地完成位块拷贝操作等等。
让我们从消息循环开始
DirectX最初是为游戏开发而推出的,编制游戏的程序员都很贪婪,他们会尽量榨取系统资源,并试图让自己的程序永远具有最高的效率。但Windows是一个多任务的操作系统,当它发现所有的程序都处于空闲时,便会减少给这些程序的资源,其中之一就是开始清理交换文件,为了让自己的程序给Windows以始终繁忙的假象,不妨用一些新的代码来代替常规的方法。
这是常规的消息循环处理
while(GetMessage(&msg,NULL,NULL,NULL)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
//---------------
这是改进的消息循环处理
for(;;){
if(PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE)){
if(msg.message==WM_QUIT) break;
TranslateMessage(&msg);
DispatchMessage(&msg)
}else{
if(AppPaused) WaitMessage();
else{
// 这里进行任何不基于消息循环的处理
// 例如动画制作
}
}
}
return msg.wParam; |
上例中,新的消息循环处理方式使得Windows认为程序始终繁忙,从而提高了程序的性能。注意,其中的AppPaused变量为真时表示程序未在前台运行,该变量的取值为WM_ACTIVATEAPP消息的wParam参数。
第一个DirectDraw程序
现在咱们开始编制第一个DirectDraw的应用程序,来示例如何建立一个全屏幕独占方式的应用程序,并利用HDC来实现简单的文本输出。 这个示例与微软的示例程序不同,它使用了DirectX 5.0中的新接口,在制作最终可执行程序时应确保为LINK程序指定ddraw.lib和dxguid.lib这两个文件。 示例程序的可执行程序由VC 5.0生成,只在基于Intel奔腾芯片、Windows95环境的机器上才能运行。
下面重点介绍一下建立和销毁DirectDraw基本对象的方法,以及如何请求新接口。 首先来说明一下以后要用到的术语:
- DirectDraw基本对象。
这个对象的实体由DirectX建立,程序只能通过指向该对象实体的指针来访问它,这一点与Delphi十分相似,在Delphi中,一个TLabel类型的变量实际只是一个指针,它指向一个TLabel类的对象。建立DirectDraw基本对象的函数是DirectDrawCreate(),该函数可在内部建立一个DirectDraw对象实体,并将指向该实体的指针返回给应用程序,以后的所有操作都基于这个指针。
- 主平面。
可以将主平面简单地理解为“视频显存区”,任何直接对主平面的访问都立刻反映到屏幕上。主平面也是一个对象(程序员只能使用指向该对象的指针),该对象封装了几乎所有的输入输出操作,包括直接访问、利用设备相关(DC)访问以及位块拷贝(Blt)操作等等。主平面可以附带一个或多个“后台缓冲区”。
- 后台缓冲区。
后台缓冲区同样也是一个对象,也同样只提供一个指向该对象的指针供程序员使用。后台缓冲区与主平面的操作方式几乎一样,不同的是所有对后台缓冲区的操作不直接反映在屏幕上,例如,你可以在后台缓冲区中放置好图片,然后通过位块拷贝操作(或“弹出”操作)将后台缓冲区中的内容快速映射到主平面(即屏幕),这种操作方式可以避免出现不愉快的闪烁现象。
- 位块拷贝。
位块拷贝是将一块矩形区域从一个平面拷贝到另一个平面,可以进行缩放、旋转、镜像、透明等附加动作。DirectDraw提供的位块拷贝方法效率十分高,程序员勿需自行编制代码来完成相似工作。
- “弹出”操作。
Flip这个单词有反转、弹出等含义,在DirectDraw中,可以将其理解为迅速互换两个平面,这种互换操作并不是内容的互换,而只是指针的互换,因此其速度比位块拷贝快得多。例如,在全屏幕独占方式下,可以利用“弹出”操作快速地将后台缓冲区与主平面互换(指针的互换),从而获得高速的视频动画效果。只有具有可“弹出”属性的平面才能使用“弹出”操作。
- 释放。
在DirectX中,各个对象均有Release方法,这个方法(成员函数)并不见得就是销毁对象,它只是对内部计数器进行递减操作。例如,用DirectDrawCreate()函数建立了DirectDraw基本对象后,该对象的内部计数器就置为值1,如果此时再调用该对象的Release方法,就会使内部计数器减去1,若结果为零则该对象被销毁,但若在调用Release方法之前又为该对象请求新的接口(如IDirectDraw2),则内部计数器又会加一,需要两次Release方法才能完全销毁该对象。
在示例程序一中,从WinMain函数开始,在登记了窗口类并建立了程序主窗口后,就开始建立DirectDraw的基本对象了:
ddrval = DirectDrawCreate(NULL, &_lpDD, NULL);
if (ddrval!=DD_OK){
// 失败
} |
函数DirectDrawCreate在成功时返回DD_OK(零),若是失败则返回一个32位(HRESULT)的错误代码。不止是这个函数,所有DirectX对象的成员函数(方法)均采用这种方式来返回表示成功或失败的值。 不必去管该函数的第一个和第三个参数,现在只需要把它们设为空值即可,而第二个参数是一个指针,这个指针指向了一个LPDIRECTDRAW类型的变量,而该变量实际上也是一个指针,如果函数成功则指向IDirectDraw对象实体--------是不是有一点绕来绕去的? 当基本对象成功建立后,就需要将它“变”成较新的对象以增进性能,这也正是本示例程序为什么需要5.0版本的DirectX来支持的缘故。
ddrval=_lpDD->QueryInterface(IID_IDirectDraw2, (void **)&lpDD);
_lpDD->Release();
if (ddrval!=DD_OK){
// 失败
} |
下一步是利用新建立的基本对象来建立与该基本对象相关联的主平面,这个主平面实际上就是可视的屏幕,所有对主平面的访问都直接反映在屏幕。在建立主平面之前我们首先将基本对象的属性设为全屏幕独占方式,这样才能改变显示模式,以及建立可“弹出”的平面集。具体的设置方法参见源程序。 在设置了全屏幕独占方式并设置了适当的显示模式之后,程序建立了一个主平面,这个主平面附带一个后台缓冲区,程序员可以使用“设备相关把柄(HDC)”或直接访问的方式(以后再讲)来对后台缓冲区进行图形操作,所有对后台缓冲区的操作并不能反映在屏幕上,在图形操作完成后,可以利用“弹出”操作来将后台缓冲区“弹”至主平面,从而在屏幕上显示出来。 本示例程序在进行图形操作示例时,首先获取后台缓冲区的HDC,然后用标准的GDI函数来绘制文本,一旦绘制完毕,就立刻释放刚获取的HDC,因为任何一个平面的HDC被获取后,该平面就被隐式地锁定,无法进行位块拷贝或弹出操作,只有当该HDC被释放后,一个隐式的Unlock才被调用,程序才能对该平面进行前述操作。 完成后台缓冲区的图形绘制后,程序调用主平面的成员函数(方法)Flip来将后台缓冲区弹至主平面,你将看到屏幕内容已被改变。
这是我个人搞的一个小封装,可用于非C++的编程语言(如Delphi)。使用此封装库可比较容易地编制DirectDraw应用程序。此封装为静态链接库,提供Visual C++和BorlandC++两种版本,其中包括: Visual C++版库文件 Borland C++版库文件 相关的头文件 两个示例源程序 说明文档(HLP文档格式) |