首页  编辑  

Delphi中使用DirectDraw技术进行图形处理

Tags: /超级猛料/Picture.图形图像编程/DirectX_OpenGL等/   Date Created:

Delphi中使用DirectDraw技术进行图形处理

DirectDraw是一套名为DirectX复杂工具的一部分,DirectX是由许多不同的技术组成,

比如:DirectDraw、Direct3D、DirectSound、DirectPlay、DirectInput和DirectSetu

p等等。这其中的每一种技术都是集中了几种处多媒体的技术或游戏的技术,像声音播放

、3D图形、网络播放、硬件设备如鼠标和强制反馈等等。不过,在本章中将只介绍Dire

ctDraw,并且这个主题很容易就会占用一章或更多的章节关于DirectX的其他技术内容,

读者可以去参阅其他关于DirectX的书籍。

DirectDraw程序要求用户的系统必须有DirectDraw运行时的DLL,这些运行时文件(实际

只是DLL的集合,可能许多机器已经安装了),还可以从Micorsoft的Web站点获取;该站

点有各种各样的产品,包括游戏、Windows 98、将来的操作系统Windows NT 5等。如果

读者正在使用的是Windows NT 4,那么至少要用Service Pack 3(SP3)去升级,之后才

能够访问作为SP3一部分的DirectDraw 3。不要试图直接在Windows NT系统下安装运行时

的DirectDraw,而应该安装最新的补丁(Service Pack),直接安装运行时的DirectDr

aw是针对Windows 95/98系统而言的。确定一个系统是否安装了DirectDraw的一个方法是

查看Windows/System或Winnt/System32目录是否存在DDRAW.DLL和DSOUND.DLL,如果有,

则说明系统已经安装了DirectDraw。

在可能的情况下,读者应该从Microsoft获取DirectDraw SDK,通常它可以从Microsoft

的Web站点下载得到,不过请注意,它至少有30M。安装了SDK后,它在硬盘上创建一个名

为DXSDK的目录,在这个目录之下是SDK目录,其中包含有各种各样的文档、用C/C++编写

的示例文件和帮助文件。另外,还可以直接从Microsoft得到SDK,在有些特殊情况下,

SDK可能是作为MSDN的一个部分而打包发行的;Microsoft出版的Inside DirectX一书也

有DirectX SDK。

ssDirectDraw是一套名为DirectX复杂工具的一部分,DirectX是由许多不同的技术组成,

比如:DirectDraw、Direct3D、DirectSound、DirectPlay、DirectInput和DirectSetu

p等等。这其中的每一种技术都是集中了几种处多媒体的技术或游戏的技术,像声音播放

、3D图形、网络播放、硬件设备如鼠标和强制反馈等等。不过,在本章中将只介绍Dire

ctDraw,并且这个主题很容易就会占用一章或更多的章节关于DirectX的其他技术内容,

读者可以去参阅其他关于DirectX的书籍。

DirectDraw程序要求用户的系统必须有DirectDraw运行时的DLL,这些运行时文件(实际

只是DLL的集合,可能许多机器已经安装了),还可以从Micorsoft的Web站点获取;该站

点有各种各样的产品,包括游戏、Windows 98、将来的操作系统Windows NT 5等。如果

读者正在使用的是Windows NT 4,那么至少要用Service Pack 3(SP3)去升级,之后才

能够访问作为SP3一部分的DirectDraw 3。不要试图直接在Windows NT系统下安装运行时

的DirectDraw,而应该安装最新的补丁(Service Pack),直接安装运行时的DirectDr

aw是针对Windows 95/98系统而言的。确定一个系统是否安装了DirectDraw的一个方法是

查看Windows/System或Winnt/System32目录是否存在DDRAW.DLL和DSOUND.DLL,如果有,

则说明系统已经安装了DirectDraw。

在可能的情况下,读者应该从Microsoft获取DirectDraw SDK,通常它可以从Microsoft

的Web站点下载得到,不过请注意,它至少有30M。安装了SDK后,它在硬盘上创建一个名

为DXSDK的目录,在这个目录之下是SDK目录,其中包含有各种各样的文档、用C/C++编写

的示例文件和帮助文件。另外,还可以直接从Microsoft得到SDK,在有些特殊情况下,

SDK可能是作为MSDN的一个部分而打包发行的;Microsoft出版的Inside DirectX一书也

有DirectX SDK。

在介绍代码的运作方式之前,我们将花一点时间说说双缓冲的有关内容。这项技术的思

路是尽可能模仿胶片电影的基于帧的技术,即双缓冲只是以足够快的速度将一系列的静

止画面显示出来从而使用户产生流畅动画的感觉。

说得详细点儿,程序员所做的是先在屏幕偏移缓冲区中构造一幅图,然后把它显示在观

众面前;当观众全神贯注于当前这幅图时,程序员接着构造另外一幅场景图,然后用这

幅场景图取代先前的那幅图显示到屏幕上。比如,开始用第一帧在屏幕的最左端显示一

个球;然后,只要把球从左到右每一帧移动几个像素就可以产生运动的效果,位置上的

每一个小的改变,只需要用一幅新图去更新屏幕;程序员实际只是显示球的一些的静止

图片,但观众却得到"受骗"后的感觉:看到了动画。

刷新屏幕的平均速度是每秒大约25帧,这个速率已经足够快,以至人眼根本感觉不到单

独一帧的存在,相反地,只是觉得看到的是真正的动画。如果以25帧/秒的速率从屏幕左

边移动球到屏幕右边,观众就会认为它们看到的不是球的一系列静止画面,而是球真正

地在空间运动。根据程序员编写的代码的质量的好坏,DirectX是能够实现高于25帧/秒

的速率的。

我们也许该说这么件事:当我们最初开始创建动画时认为,建立一个完全的缓冲区然后

再把缓冲区的内容显示到屏幕上是愚蠢的作法,这完全是不必要的操作;我们那时决定

想办法直接写屏,修改前后两帧不同的地方,避免资源的无谓开销。

后来证明,我们的方法不一定完全行得通,尤其是在想创建出相对复杂一点的效果时。

很多情况下,直接写屏所作的修改很难瞒过用户的眼光;剔除屏幕上的某一块区域、然

后绘制上下一帧的显示内容,这个操作产生的是显而易见的蹩脚的拼凑效果,即使使用

了尽可能有效的作法,效果也得不到改善。这样的操作应该使用用户看不见的后台缓冲

区(back buffer),然后用整个缓冲区的内容取代原来屏幕的显示,这时,用户看到的

是就是一幅完整的构图而不会是拼凑的效果。

DirectDraw子系统会确保在屏幕刷新时同步执行页面交换操作。我们还从来没碰上谁真

正看到了72MHz的屏幕刷新率。如果一幅画以那种速率刷新的话,那么在向屏幕刷新显示

时,用户根本不能觉察到两次刷新的片刻闪烁。

我们还有些担心:不管我们说什么,一些读者还是想尝试在用户的目光注视下直接写屏

修改屏幕上的某些区域。尽管在某些情况下这可能行得通,但是,更多的时候还只能先

在缓冲区里构图,然后直接"贴"到屏幕显示给用户,这样才能够获得好的效果。如果

读者真要尝试直接写屏,尽管去尝试;不过,到走进死胡同时,不妨回过头来尝试双缓

冲,你会惊喜地发现它在解决看似棘手问题的快速性和有效性。

DirectDraw允许程序员创建一个后台缓冲区(back buffer),向缓冲区绘制图形,然后

把它交换到可见视频内存,再由可见视频内存显示到屏幕上。假定是在独占模式下,拥

有足够的视频内存来把主表面(primary surface)和后台表面(back surface)都装入

视频RAM,那么页面交换操作就不再是一个复制的过程;相反的,它只要简单地改变一下

被视频卡可见内存引用的内存块的地址就可以了。也就是说,当执行页面交换操作时,

内存中只有四个字节的内容需要改变,而其它内容都不变;这唯一需要做的改变就是:

改变指向当前活动视频页的指针,该指针存放在内存的四个字节中。因此,该操作速度

很快;另外,它还能保证与显示器的刷新操作同步发生。这样,使用DirectDraw就可以

实现非常平滑的动画。

DirectDraw和别的DirectX技术尽最大的努力为程序员提供一套高级的、完善的视频例

程;比如3D例程尽可能利用硬件来实现超快速的操作。然而,许多视频卡或别的硬件设

备并不能提供程序员所希望的全部功能,在这种情况下,DirectX将试图从软件上仿真硬

件缺少的功能。

到底软件能提供何种功能、硬件又能提供何种功能,这整个的问题是一个相当技术化的

主题,大大超出了本书的范围。但是,可以在特定的系统上运行Microsoft SDK所带的D

irectX Viewer(DirectX浏览器)来查看DirectDraw的状态,该浏览器通过执行一系列

的DirectX的功能例程来报告特定系统上各种各样多媒体硬件的状态;还可以在运行时刻

亲自查询这些子系统,不过这个内容在本书中我们只是稍微介绍一点儿。

到现在为止,所应清楚的一点是:DirectX的一些功能在硬件中执行,而其余部分由软件

仿真完成。仿真功能是由一个名称看起来有些奇怪的子系统来处理的,它就是HEL,即H

ardware Emulation Layer (硬件仿真层);而程序中非仿真的部分由另一个名称看起

来更奇怪的子系统处理:HAL,即Hardware Abstraction Layer(硬件抽象层)。HEL和

HAL处理所有DirectDraw的各种操作;它们的驱动程序使得DirectDraw成为可能。

可以应用HEL和HAL中的任意一个来初始化DirectDraw;不过,这是一个非常高级的功能

,即使在以后的课程中用到很苛刻的性能测试时都可能用不着调用它。

在初始化DirectDraw的实例后,下一步就是设置合作层次(cooperative level):

hr : = FDirectDraw.SetCooperativeLevel(Handle,

DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN);

这些代码把CooperativeLevel设置成全屏独占模式,这样,编写的程序将占据整个屏幕

,不许别的窗口覆盖它,除非通过按Ctrl+Alt+Tab组合键从程序中显式地把该窗口从视

频缓冲区移走。也可以把合作层次初始化成DDSCL_NORMAL,它是一个窗口模式。我们现

时将略过该模式,因为它在某些方面比独占模式复杂得多。

在独占模式下,应用程序可以选择分辨率及色深,像下面这样:

hr : = FDirectDraw.SetDisplayMode(640, 480, 8);

这行代码把屏幕的分辨率设置为640×480,8位色深(就是说至多可以显示256色)。

直到最近,选择比这更高的分辨率和色深还是没有结果的。要保持一幅640×480的屏幕

的单个拷贝需要大约300KB的空间,也就是说,需要600KB的空间来保持一个屏幕拷贝及

其后台缓冲区;这是期望从大多数系统得到的最大空间了,所以要获取更高分辨率通常

是不可行的。现在,事实上有些视频卡在更高的分辨率下能够提供相当好的性能,但对

大多数人来说,还得指望一阵子。正因为这样,所以我们一直使用这相对简单的分辨率

,尽管它意味着要学会使用调色板。

设置了合作层次和屏幕分辨率后,下一步是获取两个表面,以便在表面之间进行页面交

换。换句话说,要创建一个指针指向用户注视的视频内存,也指向后台缓冲区(以便为

双缓冲或者说页面交换使用)。可以如下进行:

       SurfaceDesc.dwSize := sizeof(SurfaceDesc);

       SurfaceDesc.dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT;

       SurfaceDesc.ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE or

                             DDSCAPS_FLIP or

                             DDSCAPS_COMPLEX;

       SurfaceDesc.dwBackBufferCount := 1;

       hr := FDirectDraw.CreateSurface(SurfaceDesc, FPrimarySurface, nil);

       if(hr = DD_OK) then begin

         // Get a pointer to the back buffer

         ddscaps.dwCaps := DDSCAPS_BACKBUFFER;

         hr := FPrimarySurface.GetAttachedSurface(ddscaps,

                                          FBackSurface);

         if(hr = DD_OK) then begin

           PaintSurfaces;

           Exit;

         end;

       end;

这段显然非常不直接的代码创建了主表面和后台表面,主表面存放显示给用户看的内容

,后台表面存放的是当调用DirectDraw的Flip函数来实现双缓冲时要通过页面交换操作

显示到主表面的内容。

下面是DDSCaps的结构:

TDDSCaps = record

dwCaps: DWORD;//需要的表面功能

end;

这个结构初看起来相当简单,但稍微观察一下就会发现它实际上很复杂。事实上,DDSC

aps和SurfaceDesc结构都有几乎令人绝望的复杂的结构,这些结构来处理一大堆常量。

为了避免在描述它们时让我们和读者发疯,我们提示只要用DirectX SDK的在线帮助查看

一下这些常量参数即可。比如,DDSCaps结构的一个名为dwCaps的字段就有30来个可能的

常量,其中的很多解释起来都很复杂。

下面是TDDSurfaceDesc结构的定义:

 TDDSurfaceDesc = record

   dwSize: DWORD;                 // size of the DDSURFACEDESC structure

   dwFlags: DWORD;                // determines what fields are valid

   dwHeight: DWORD;               // height of surface to be created

   dwWidth: DWORD;                // width of input surface

   lPitch: Longint;                 // distance to start of next line (retu

rn value only)

   dwBackBufferCount: DWORD;      // number of back buffers requested

   case Integer of

   0: (

     dwMipMapCount: DWORD;          // number of mip-map levels requested

     dwAlphaBitDepth: DWORD;        // depth of alpha buffer requested

     dwReserved: DWORD;             // reserved

     lpSurface: Pointer;              // pointer to the associated surface  

memory

     ddckCKDestOverlay: TDDColorKey;      // color key for destination over

lay use

     ddckCKDestBlt: TDDColorKey;          // color key for destination blt  

use

     ddckCKSrcOverlay: TDDColorKey;       // color key for source overlay u

se

     ddckCKSrcBlt: TDDColorKey;           // color key for source blt use

     ddpfPixelFormat: DDPIXELFORMAT;        // pixel format description of  

the surface

     DDSCaps: TDDSCaps;                // direct draw surface capabilities

     );

   1: (

     dwZBufferBitDepth: DWORD;      // depth of Z buffer requested

     );

   2: (

     dwRefreshRate: DWORD;          // refresh rate (used when display mode

is described)

     );

 end;

该结构也是非常复杂的。我们没有必要逐个字段逐个字段地说明这个结构,相反,只是

指出:该记录可用来获取一个表面的大小、高度、宽度、像素深度以及一个指向表面实

际占用字节的指针。其中更复杂的一个字段叫做Pitch,它描述到表面下一条线起始位置

的距离,这未必就是表面的宽度;所以,要获取表面的实际宽度,还需要特殊的函数调

用。在直接处理一个DirectDraw表面的字节(或位)时,请特别注意字段pitch。

创建了主表面后,很容易就能获取先创建的后台表面,它是用来完成页面交换操作的:

ddscaps.dwDaps : = DDSCAPS_BACKBUFFER;

hr : = FPrimarySurface.GetAttachedSurface(ddscaps, FBackSurface);

这些代码得到后台表面;我们总是让一个指针指向后台表面,以便需要时访问它。