第四章 diectxdarw基础篇
第一节 DirectDraw简介
Grubers的一个观点是DirectDraw“只是一个bltting发动机”。这是相当准确的,但却太简化了。更准确地讲,DirectDraw是一个可以提供软件仿真测试的独立于硬件设备的bltting发动机。DirectDraw的主要用途是尽可能快、尽可能可靠并且尽可能连续地将图形考贝到视频显示设备上。
另外一个定义DirectDraw的方式是把它作为一个视频存储器管理器,同常规的存储器管理器一样,DirectDraw发放存储器信息包,跟踪每一个信息包的状态。信息包可以随意地创建、复制、修改或破坏,同时这些操作的细节被程序员隐含起来,这样讲是过于简单了。此外,DirectDraw是能够使用系统RAM和视频RAM的。存储器管理器也经常被设计成和主要目标一样强健,而不只是追求性能。对于DirectDraw,性能只是设计目标之一。
从技术角度讲,DirectDraw是随同设备驱动器集合的便携式API。DirectDraw设计成完全避开传统意义上的Windows图形机构(GDI,或称图形设备接口)。GDI由于性能低而名声不好,所以DirectDraw的设备独立性在提供最佳性能方面是至关重要的。
第二节 DirectDraw基本概念
1. 显示模式
显示模式是由允许将要显示的图形输出的显示硬件支持的可视配置。最常用的显示模式属性是分辨率。Windows使用的显示模式的默认值是640×480的分辨率。这意味着,水平方向有640个像素,垂直方向有480个像素。其他一些常见的显示模式分辨率有800×600,1024×768。一些显示卡支持Mode X显示模式。一个典型的Mode X显示模式的分辨率为320×200。
显示模式也随像素深度的变化而变化。像素深度决定着每一个像素所容纳的多少不同的值,因而也就可以显示多少种颜色。例如对于8位像素深度的显示模式,每个像素能够再现256种颜色的一种。像素深度为16位的显示模式支持65536种颜色(即2的n次方),典型的像素深度为8、16、24和32位。
显示模式由安装在机器中的显示设备或视频卡支持。显示设备有自己的RAM,从计算机的RAM中分离出来。我们把位于显示设备中的存储器称为显示RAM,而把常规存储器称为系统RAM。
支持一个给定的显示模式的RAM的容量取决于显示模式的分辨率和像素深度。例如,640×480×8(640×480像素,深度为8位)的显示模式需要307200字节。1024×768×16的显示模式需要1572864字节。支持显示模式的存储器必须是显示RAM。一个给定显示设备所支持的显示模式因此也被可以利用的显示RAM的容量所限制。例如,1024×768×16的显示模式因为需要一兆字节以上的内存,所以就不能被只有一兆字节RAM的显示设备所支持。
DirectDraw的一个主要特征是显示模式切换。这允许一个DirectDraw应用程序检测和激活所安装的显示设备所支持的任何显示模式。我们将在第4章中讨论显示模式切换的细节。
2. 硬件加速
DirectDraw具有最优性能的最重要的原因是,它尽可能地使用硬件加速来进行设计。硬件加速发生在当显示设备能够用建立在显示设备之中的处理功能执行操作时。硬件加速具有两个优点,首先,当硬件加速出现的时候,硬件按指定要求设计成支持图形操作,这提供了执行给定任务的最快的方法:其次,硬件加速使得计算主处理器从执行操作中解放出来,这使得主处理器可以执行其他任务。
3. 表面
表面是存储器的一个矩形部分的DirectDraw术语,通常包括图像数据。该存储器通常用来表示一个存在于显示RAM或系统RAM中的表面。驻留在显示RAM中的表面享有超高性能,因为绝大多数显示硬件不能对系统RAM直接存取。
表面分为向大类,最简单的类型是脱离屏幕表面。脱离屏幕表面可以驻留在显示RAM中或系统RAM中,但却不能被显示。这类表面一般用于存储子画面和背景。
另一方面,一个主表面是可在屏幕上看到的视频RAM的一部分部分。所有的DirectDraw程序(可以提供视频输出)都拥有主表面。主表面必须驻留在显示RAM中。
主表面通常很复杂,或是可翻转的。可翻转表面允许页面翻转,这是一项整个表面的内容可以通过一个硬件操作而瞬时可见的技术。页面翻转用于许多基于DirectDraw或其他的图形应用程序中,因为它可以产生相当平滑、不闪烁的动画。一个可翻转的主表面实际上是两个表面,一个可见,另一个不可见。不可见的表面称为后备缓冲区。当发生页面翻转时,以前是后备缓冲区的表面就成为可见的,而以前可见的表面则成为后备缓冲区。
离屏表面和主表面都有两类:调色板的和无调色板的。在DirectDraw中,只有8位表面是调色板表面。调色板表面并不包含色彩数据,但是却引入一个色彩表。该表称为调色板。像素深度为16、24或32位的表面是无调色板表面。无调色板表面存储实际色彩值,而不引入调色板。
因为在无调色板表面中的每一个像素都存储色彩数据,所以知道表面的像素格式是很重要的。像素格式描述了存储于像素中的红色、绿色和蓝色(RGB)元件的方式。像素格式随像素深度、显示模式和硬件设计的不同而不同,在第5章中可以了解所有的像素格式。
4. Bltting
Bltting是用于复制的图形语言。典型的blt操作是将离屏表面的内容拷贝到一个后备缓冲区中。当Bltting通过硬件完成的时候,执行速度相当快。如果无法得到硬件加速,DirectDraw将使用一个软件操作来仿真blt。这种仿真操作虽然也能够完成任务,但却比硬件慢得多.一般只有驻留在显示RAM中的表面能够通过使用显示硬件来完成blt。
blt操作调用一个源表面和一个目标表面,源表面的内容被拷贝到目标表面中。源表面中的内容在操作中不会改变,只有目标表面受blt的影响。blt操作也并不需要使用全部的源表面或目标表面。源表面中的任何矩形区域可以被放置于目标表面中的任何位置。
不规则形状表面的bltting(例如典型的子画面)是以透明方式完成的。透明性是通过指定表面中某个不被blt操作拷贝的像素而获得的。像素值通过使用色彩键码给以标志。
色彩键码可以附加到源表面或目标表面上。源色彩键码是很普遍的。源色彩键码允许透明性,因为源表面中的像素值并未被考贝。至于目标色彩,只有目标表面中通过色彩所指定的像素值能够被源表面的内容所覆盖。
DirectDraw也支持一些特定的操作,包括拉伸、压缩、镜像映射,以及混合等。这些功能的实现往往取决于显示硬件。DirectDraw能够仿真其中的某些操作,但是跟性能相比,价格往往是昂贵的。
DirectDraw也有不能仿真的功能(例如目标色彩键码)。使用这些功能是冒险的,除非该功能为所安装的显示硬件支持,否则使用该功能的操作将失败。这给DirectDraw的开发者带来两种基本选择:要么放弃使用这些功能:要么往应用程序中增加定制软件。
5. 调色板
使用8位显示模式的应用程序需要提供调色板。调色板就是任何时候都可以使用的色彩表。如果8位显示模式不需要调色板,应用程序将被迫使用256种颜色的固定设置。调色板允许用户定义将要使用的256种颜色之一。
当你使用调色板显示模式时,必须保证在应用程序中的图像也使用同一调色板。如果没有做到这一点,所显示的一些或全部图像中将出现错误的颜色。调色板也会带来麻烦,尤其是用一个调色板来显示大量图像的时候。调色板也有一些优势。正如前面提到的,调色板允许在一个有限色彩的场合使用最多的色彩。调色板也允许调色板动画。
调色板动画是动画通过改变调色板项目,而不是改变像素值来执行的技术,这就使得一个屏幕上的很多像素可以瞬时改变颜色。对于一些有限的应用程序,诸如分配的、重复的动画,调色板动画很有用处。
6. 剪裁
理想状态下,一个blt操作就是整个表面被blt成为另一个表面。通常源表面被blt成为目标表面的边,或者目标表面被另一个表面或窗口遮蔽。像这样的情况就需要进行剪裁。剪裁只允许一部分或一个表面的一部分被blt。
在编写窗口DirectDraw应用程序时经常用到剪裁,因为这些应用程序必须遵守Windows桌面的规则。我们将在本章后面讨论窗口应用程序。
DirectDraw提供全矩形剪裁支持。也有这种情况,就是付费提供定制剪裁例程,我们将在第3章中研究定制剪裁解决方案。
7. 其他表面
离屏表面和主表面(具有任选的后备缓冲区)是绝大多数DirectDraw应用程序的主干。然而一些其他的表面就有不同,包括重叠表面、alpha通道表面、Z-缓冲区以及3D设备表面等。
重叠表面是硬件单色画面,因而也就在仅在支持重叠的显示硬件上获得。和软件单色画面不同,它可以被移动而不需要背景图像被恢复。
alpha通道表面用来执行alpha调配。Alpha调配是透明的高级形式。允许表面以透明度或半透明方式来拷贝。alpha通道表面可用来控制每一像素的透明度设置。alpha通道表面的深度有1、2、4、8位。1位深度alpha通道表面仅支持两种透明设置,不透明(非透明)或不可见(全透明)。另一方面,8位alpha通道表面允许256种不同的透明度设置。Alpha调配是不被DirectDraw仿真的功能的一个例子。为了使用alpha调配,因而就需要有支持它的显示硬件或建立在应用程序之中的定制调配方案。
Z-缓冲区和3D设备表面用于3D应用程序中。这些类型的表面已被特别地加入到DirectDraw之中,以支持Direct3D。Z-缓冲区用于景象绘制时期,以跟踪景象中离浏览者最近的对象,从而该对象可以在其他对象的前面出现。3D设备表面可以用来作为Direct3D绘制目标的表面。本书并不包括Z-缓冲区或3D设备。
第三节 元件对象模型(COM)
1.Microsoft的COM规格
DirectDraw根据Microsoft的COM(Component Object Model)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构,COM是一个大的项目,但是它并不是本软件讨论的对象。我们讨论COM只是为了方便使用DirectDraw进行编程。
COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
2. 对象和接口的比较
COM在对象和接口之间具有很大的区别。COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。因为我们无法存取COM对象,所以这里绝大多数时候是根据接口来讲的。
一个COM对象能够支持多个接口。这听起来像个特例,但是它经常出现,因为根据COM规格,一个COM接口一旦定义之后,就不能再被改变或增加。这样做是为保证旧程序在一个COM对象升级的时候不会被停止使用。这个初始接口始终存在,一个新的、替换的接口在提供存取对象的新的函数性的时候才被提供。
3. IUnknown接口
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即界面)。DirectDraw接口总以“I”开头。但是在文献中经常看不到这个标志。以后提到接口时也将省略“I”标志。
IUnknown接口将提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef()
●Release()
●QueryInterface()
AddRef()和Release()成员函数为称为生命期封装(lifetime encapsulation)的COM功能提供支持。生命期封装是一个将每一个对象根据它自己的结构放置的协议。
生命期封装通过引用值来实现。每一个对象拥有一个可以跟踪对象的指针数,或者引用的内部值。当对象创建之后,该值为1。如果附加的接口或接口的指针被创建,则该值递增。与此类似,如果接口的指针被破坏,则该值递减。当它的引用数到0的时候,该对象自行破坏。
AddRef()函数用来使对象的内部引用值递增。绝大部分时间里,该函数通过DirectDraw API被用户调用。例如,当你使用DirectDrawaw API创建一个新的接口时,创建函数就自动调用AddRef()。
Release()函数用来给对象的内部引用值递减。用户应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。AddRef()和Release()函数都返回一个值,表示对象新的引用值。
QueryInterface()函数允许COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
4. GUID
为了查询一个对象是否支持使用QueryInterface()函数的指定接口,就有秘要识别有问题的接口。这通过接口的GUID(Globally Unique IDentifier)来实现。一个GUID是一个128位的值,也就是说,对于所有意图和目的是唯一的。所有DirectDraw接口的GUIDs都包含在DirectX头文件中。
上述对于COM的简单介绍,就是为有效使用DirectDraw API所需要的全部内容。以后当我们再讨论DirectDraw API时,你会发现这些内容是有联系的。
第四节 DirectDraw接口函数
1.关于 DirectDraw API
衡量API的一个方法就是看它的大小。一个庞大复杂的API可能就是计划不周的结果。另一方面,一个庞大的API有时就意味着每一种情况都有可能出现。一个小的API就是一个新的、缺乏功能的软件包的证据。它也意味着,一个API只能做它所需要做的,而不能多做一点。
DirectDraw API是比较小的,因此本章中所讨论的每一个函数不致于使本章看起来像一本参考手册。DirectDraw提供很少的方便,也很少有限制。
DirectDraw由个COM对象构成,每个对象可以通过一个或多个接口存取。这些接口包括:
●DirectDraw
●DirectDraw2
●DirectDrawSurface
●DirectDrawSurface2
●DirectDrawSurface3
●DirectDrawPalette
●DirectDrawClipper
我们将讨论每一个接口,并随后讨论它们的成员函数。但我们并不讨论每个函数的细节,因为我们并不是向您提供一份参考手册。相反,我们将讨论每个函数是干什么的,为什么这样使用,以及你有可能如何去使用它。
当DirectX首次推出的时候(早先它被称作Games SDK),DirectDraw核心函数性以DirectDraw接口表示。当DirectX2推出的时候,DirectDraw也已经被升级了。DirectDraw遵守COM规格而未被改变。新的函数性可能通过DirectDraw2接口存取。
特别要注意的是,DirectDraw2接口是DirectDraw接口的超级设置。DirectDraw2接口可提供DirectDraw接口的所有函数,另外还增加了一些新的函数。如果你正在使用DirectX或更高版高,那么你可以随意选用DirectDraw接口或DirectDraw2接口。但是,由于DirectDraw2接口较DirectDraw接口的功能更强,所以没有必要使用DirectDraw接口。同样,Microsoft并不主张使用这些无组织的、网络可变的接口。因此,在本书以后的程序中我们只使用DirectDraw2接口。
DirectDraw和DirectDraw2接口提供的成员函数如下(按字母顺序排列):
●Compact()
●CreateClipper()
●CreatePalette()
●CreateSurface()
●DuplicateSurface()
●EnumDisplayModes()
●EnumSurfaces()
●FlipToGDISurface()
●GetAvailableVidMem()
●GetCaps()
●GetDisplayMode()
●GetFourCCCodes()
●GetGDISurface()
●GetMonitorFrequency()
●GetScanline()
●GetVerticalBlankStatus()
●RestoreDisplayMode()
●SetCooperativeLevel()
●SetDisplayMode()
●WaitForVerticalBlank()
接下来我们讨论DirectDraw接口函数。注意,在本章以后的内容中,DirectDraw接口既表示DirectDraw接口,也表示DirectDraw2接口。只有在区分DirectDraw接口和DirectDraw2接口的函数时,才加以区别。
1. 接口创建函数
DirectDraw接口表示DirectDraw本身。该接口在被用于创建其他DirectDraw接口实例时,是一个主接口。DirectDraw接口提供三个这样的接口实例创建函数:
●CreateClipper()
●CreatePalette()
●CreateSurface()
CreateClipper()函数用于创建DirectDrawClipper接口实例。并非所有的DirectDraw应用程序都用到剪裁器,所以该函数并不是在所有的程序中都出现。我们将很快讨论DirectDrawClipper的细节。
CreatePalette()函数用于创建DirectDrawPalette接口实例。同DirectDrawClipper一样,并非所有的DirectDraw应用程序都用到调色板。比如,应用程序使用16位显示模式时,就不用调色板。但是,当应用程序使用8位显示模式时,就必须创建至少一个DirectDrawPalette实例。
CreateSurface()函数用于创建DirectDrawSurface接口实例。任何一个DirectDraw应用程序都要用表面来生成图像数据,因此经常要用到这一函数。
DirectDraw接口自己的实例是由DirectDrawCreate()函数创建的。DirectDrawCreate()是DirectDraw函数中少有的几个常规函数之一,但并不是COM接口成员函数。
2. GetCaps()函数
DirectDraw接口允许准确确定软硬件都支持的特征。GetCaps()函数可以对两个DDCAP结构实例进行初始化。一个结构表明哪些特征由显示硬件直接支持,另一个结构表明哪些特征由软件仿真支持。最好是用GetCaps()函数来决定你将用到的特征是否被支持。
提示:DirectX浏览器
DirectX SKD是与DXVIEW程序同时推出的。DXVIEW说明了DirectX组件的功能,包括DirectDraw。大多数系统中,有两个DirectDraw项目:主显示驱动器和硬件仿真层。第一项说明了显示硬件的功能。第二项说明了在缺乏硬件支持的情况下,DirectDraw将要仿真的一些特征。在具有两个以上的DirectDraw支持的显示卡的计算机中,DXVIEW会显示卡的功能。
3. SetCooperativeLevel()函数
SetCooperativeLevel()函数用于指定应用程序所要求的对显示硬件的控制程度。比如,一个正常合作度意味着应用程序既改变不了当前显示模式,也不能指定整个系统调色板的内容。而一个专有的合作度允许显示模式切换,并能完全控制调色板。不管你决定使用哪种合作度,都必须调用SetCooperativeLevel()函数。
4. 显示模式函数
DirectDraw接口提供4种显示模式操作函数。它们是:
●EnumDisplayModes()
●GetDisplayMode()
●RestoreDisplayMode()
●SetDisplayMode()
EnumDisplayModes()函数可用于查询DirectDraw使用何种显示模式。通过设置EnumDisplayModes()函数默认值可以得到所有的显示模式,而且可以通过显示模式描述消除那些不感兴趣的模式。进行显示模式切换的过程中最好使用EnumDisplayModes()函数。现在市场上有各种各样的显示设备,每种显示设备都有自己的特征和局限。除了默认的640×480×8窗口显示模式,最好不要依靠任何给定的显示模式的支持。
GetDisplayMode()函数可以检索到有关当前显示模式的信息,并在DDSURFACEDESC结构实例中显示当前显示模式的宽度、高度、像素深度以及像素格式等信息。还有别的途径可以检索到同样的信息(比如检索主表面描述),因此该函数并不出现在所有的程序中。
SetDisplayMode()函数用于激活所支持的显示模式。SetDisplayMode()函数的DirectDraw2版本还允许设定显示模式的刷新率。而DirectDraw接口版本的SetDisplayMode()函数只能进行显示模式宽度、高度和像素深度的设置。任何一个要进行显示模式切换的程序都要用到SetDisplayMode()函数。
RestoreDisplayMode()函数用于存储调用SetDisplayMode()函数之前的显示模式。SetDisplayMode()和RestoreDisplayMode()函数都要求优先使用SetCooperativeLevel()函数得到的专有合作存取。
5. 表面支持函数
除了CreateSurface()函数之外,DirectDraw接口还提供了以下向个表面相关函数:
●DuplicateSurface()
●EnumSurfaces()
●FlipToGDISurface()
●GetGDISurface()
●GetAvailableVidMem()
●Compact()
DuplicateSurface()函数用于考贝当前表面。该函数只复制表面接口,不复制内存。被复制的表面与源表面共享内存,因此改变内存的内容就同时改变了两个表面的图像。
EnumSurfaces()函数可用于迭代所有满足指定标准的表面。如果没有指定标准,那么所有当前表面都被枚举。
FlipToGDISurface()函数的作用是在终止页面翻转应用程序前确保主表面得以正确存储。取消页面翻转时,有两个表面交替显示。这就是说,在终止应用程序之前有可能没有保存最初的可见表面。这种情况下,Windows通过绘制一个不可见表面来恢复。利用FlipToGDISurface()函数就可以轻而易举地避免发生这种情况。
GetGDISurface()函数可以向只被GDI认可的表面返回一个提针。GDI表面是Windows用于输出的表面。在进行屏幕捕捉时,这个函数非常有用,DirectDraw可以捕捉到Windows桌面的任一部分。
GetAvailableVidMem()函数用于检索正在使用中的视频存储器(显示RAM)的数量。这一函数由DirectDraw2接口提供,而不是由DirectDraw接口提供。该函数用于确定应用程序利用显示RAM可创建表面的数量。
Compact()函数不是通过DirectX5实现的,但它可以为视频存储器提供碎片整理技巧。在基于显示RAM的表面被不断创建或受到破坏的时候,可以释放大量内存。
6. 监视器刷新函数
DirectDraw接口提供了4种适于计算机显示设备或监视器的函数,但这些函数不适于显示卡,它们是:
●GetMonitorFrequency()
●GetScanLine()
●GetVerticalBlankStatus()
●WaitForVerticalBlank()
这些函数尤其与监视器的刷新机制紧密机连。这在确保生成动画时尽可能不产生闪烁和图像撕裂现象时是至关重要的。但必须注意,并非所有的显示卡/监视器组合都支持这些函数。
GetMonitorFrequency()函数用于检索监视器当前的刷新率。刷新率通常用赫兹表示,缩写为Hz。例如,60Hz的刷新率表示屏幕每秒更新60次。
GetScanLine()函数用于向监视器返回当前正在被刷新的扫描行(水平像素行)。不是所有的显示设备/监视器组合都支持该函数。如果这一功能得不到支持,该函数将返回DDERR-UNSUPPORTED。
对于高性能图形应用程序来说,通常要求利用垂直刷新同步地更新屏幕。尤其是,当显示器刚完成屏幕刷新时,最好能够更新主表面。否则,屏幕的一部分显示新的图像数据,而另一部分仍显示旧的图像数据,这种现象就是所谓的图像撕裂。DirectDraw默认利用垂直刷新同步更新屏幕。如果不是这样还可以利用GetVerticalBlankStatus()和WaitForVerticalBlank()函数实现同步刷新。
7. GetFourCCCodes()函数
DirectDraw接口提供的最后一个函数是GetFourCCCodes()函数。该函数用于返回显示卡所支持的FourCC代码。FourCC代码用于描述非RGB或YUV表面。我们不在此讨论YOV表面,它们已超出本书的范围。
第五节 DirectDrawSurface接口函数
同DirectDraw接口一样,DirectDrawSurface接口也遵守COM规格.最初,表面支持是由DirectDrawSurface接口提供的。DirectX2介绍了DirectDrawSurface2接口的新的函数性,DirectX5介绍了DirectDrawSurface3接口。
尽管本软件中讨论的是DirectDraw2接口,而不是DirectDraw接口,但我们仍忠于最初的DirectDrawSurface接口,因为DirectDrawSurface2和DirectDrawSurface3接口新增的函数并不十分重要。在以后的内容里,我们将用DirectDrawSurface接口表示这3种接口,除非特别注明。
DirectDrawSurface是最大的DirectDraw接口,它允许表面内容的拷贝、清除以及被调用程序直接存取。DirectDrawSurawSurface接口总共提供36个成员函数,按字母顺序排列如下:
●AddAttachedSurface()
●AddOverlayDirtyRect()
●Blt()
●BltBatch()
●BltFast()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●EnumOverlayZOrders()
●Flip()
●GetAttachedSurface()
●GetBltstatus()
●GetCaps()
●GetClipper()
●GetColorKey()
●GetDC()
●GetDDInterface()
●GetFlipStatus()
●GetOverlayPosition()
●GetPalette()
●GetPixelFormat()
●GetSurfaceDesc()
●IsLost()
●Lock()
●PageLock()
●PageUnlock()
●ReleaseDC()
●Restore()
●SetClipper()
●SetColorKey()
●SetOverlayPosition()
●SetPalette()
●SetSurfaceDesc()
●Unlock()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
1. 表面描述函数
我们首先讨论的个可用于检索表面自身信息的函数,它们是:
●GetCaps()
●GetPixelFormat()
●GetSurfaceDesc()
●SetSurfaceDesc()
同DirectDraw接口提供的GetCaps()函数一样,DirectDrawSurface接口提供的GetCaps()函数用于输出表征哪些特征可被表面支持的数据。该信息包括:表面是主表面还是离屏表面;表面使用的存储器定位于显示RAM还是系统RAM。
GetPixelFormat()函数在用于高彩和真彩表面时是非常重要的,这是由于像素格式因显示卡的不同而不同。该函数返回表征码,这些表征码表明每一种颜色部件是如何存储的。
GetSurfaceDesc()函数返回一个表面描述。该信息包括表面的宽度、高度和深度。表面像素格式(同样被GetPixelFormat()函数检索)也包含在其中。
SetSurfaceDesc()函数(对于DirectX5)来讲是新增的,只由DirectDrawSurface3接口提供)允许设定某些表面属性。该函数可用于指定表面使用的内存。这一点在设计定制表面存储器管理器策略时非常有用。
2。 表面Blt函数
DirectDrawSurface接口提供3个支持blt操作的函数:
●Blt()
●BltBatch()
●BltFast()
Blt()函数是一个主要函数。Blt()函数能够进行常规的blting(无特殊影响的简单的表面到表面的blt),同时支持延伸、旋转、镜像和颜色填充的操作。当用于同剪裁器关联的表面时,Blt()可进行剪裁blt操作。
BltBatch()函数不是在DirectX3下实现的(你可以调用该函数,但什么也不会发生)。执行BltBatch()函数时,如果可能,它可同时进行多blt操作。
BltFast()函数是Blt()函数的优化版本。BltFast()函数的效率提高了,但性能却下降了。BltFast()函数不能进行一些特殊的blt操作,而Blt()函数可以。而且,BltFast()函数不能用于剪裁。但是BltFast()函数支持源和目标色彩键码blt的操作。在遵循定制剪裁例程的情况下,BltFast()函数可进行DirectDraw能够提供的最快捷、灵活的blt操作。在下章中我们将执行一个定制剪裁例程。
以上3个blt函数均将源表面和目标表面作为变量。其他的数据,例如blt在目标表面上的理想定位,是通过指定理想blt操作的确切属性来提供的。一旦可能,这3个函数将进行硬件加速blt。
3. Flip()函数
Flip()函数用于页面翻转操作。调用Flip()函数可隐藏屏幕上先前可见的表面,并使一个后备缓冲区显现。只有被明确地创建为翻转表面的表面,才响应该函数的调用。
必须牢记,真正的翻转操作不可能总是成功。页面翻转要求有足够的显示RAM容纳两整屏有效数据。如果满足不了这一要求,系统RAM中将创建一个后备缓冲区。这时调用Flip()函数进行的是blt操作而不是页面翻转。基于系统RAM的后备缓冲区中的内容被拷贝到主表面上。这样会严重影响性能,但是,在真正的页面翻转中如果没有足够的显示RAM,又不退出程序,也只能如此了。如果你的应用程序要求最佳性能,就得设法避免激活不能进行真正页面翻转的显示模式。
4. 表面状态函数
下面讨论两个能检索有关操作和翻转操作信息的函数,它们是:
●GetBltStatus()
●GetFlipStatus()
GetBltStatus()函数用于确定当前是否进行blt操作。这一点很重要,因为正被blt的表面不能进行其他操作。该函数表明,给定的表面是否正是一个进行blt操作的源表面或目标表面。
同样地,GetBltStatus()函数表明是否正在进行翻转操作。即使DirectDraw通过blt操作仿真页面翻转,该函数而不GetBltStatus()也必须用于由Flip()函数初始化的监视器页面翻转。
5. 色彩键码函数
DirectDrawSurface接口提供了以下两个函数,来设置和检查表面色彩键码或色彩键码的范围,它们是:
●GetColorKey()
●SetColoKey()
默认状态下,表面没有色彩键码。一个色彩键码只对应一种颜色,但某些硬件支持色彩键码范围。色彩键码和色彩键码范围是DDCOLORKEY结构定义的。GetColorKey()和SetColoKey()函数都将该结构的指针作为变量。在要求表面的一部分透明时或需要进行目标色彩键码操作时,可以使用这两个函数。
6. Lock和Unlock()函数
DirectDraw的一个主要特点,就是能够提供对图像数据的直接存取。直接存取可以提供最佳性能和更好的灵活性,因为没有中间API影响运行速度,并且开发人员呆任意使用图像数据。对表面存储器的中间存取通过以下出众个函数实现:
●Unlock()
●Lock()
Lock()函数向组成表面的存储器返回一个指针,不管表面存储器位于显示RAM还是系统RAM。存储器一般按线性风格排列,以便能简单地进行图像 数据存取。Unolock()函数在完成表面存储器的存取之后指定给DirectDraw。
对图像数据的直接存取必须付出代价。为了支持这种存取方式,DirectDraw在表面锁定的时候必须关闭基本的Windows机构。在Windows95状态下,如果忘记解锁表面,必定会损坏机器。
因此,表面锁定的时间应尽量缩短。测试前应仔细检查Lock()和Unlock()函数之间的程序调用。因为这一程序无法用传统的调试程序进行调试。
锁定表面不能被blt和翻转,因此试图保持表面处于锁定状态没有任何有益之处。而且,一旦表面解锁,由Lock()函数检索的指针就失效了。
表面锁定后不能再次被锁定。在表面锁定时也就无法调用Lock()函数。
7. GetDC()ReleaseDC()函数
对表面的直接存取占用很大内存,有时候把表面作为一个常规的Windows设备会更好。在此,DirectDrawSurface接口提供以下两个函数:
●GetDC()
●ReleaseDC()
GetDC()函数提供了一个DC(设备环境),可以用常规的Win32函数写到表面上。例如,DC可以用Win32的TextOut()函数在表面上绘制文本。用完DC后必须马上调用ReleaseDC()函数。
同Lock()和Unlock()函数使用一样,在调用完GetDC()函数后必须马上调用ReleaseDC()函数。这是因为GetDC()函数内部调用Lock函数,而ReleaseDC()函数内部调用Unlock()函数。
8. PageLock()和PageUnlock()函数
接下来,我们讨论另外两个与Lock()函数和Unlock()函数看上去非常相像的函数:
●PageLock()
●PageUnlock()
尽管这两个函数看上去很像Lock()和Unlock()函数,但它们却有完全不同的作用。PageLock()和PageUnlock()函数用于控制Windows对基于系统RAM的表面的处理方式。这两个函数由DirectDrawSurface2接口提供,而不是由DirectDrawSurface接口提供。
当Windows认为当前正在运行的其他应用程序或进程更适于使用内存时,Windows会向硬盘释放部分内存。这种缓冲对于整个系统内存都起作用,因此驻留在系统内存中的DirectDraw表面有可能被存到硬盘上。如果要用到这样的表面,Windows需要花费一定的时间从硬盘上读取表面数据。
PageLock()函数提示Windows哪些给定的表面不应该释放到硬盘上。这样,在使用表面时就不用耗费时间进行硬盘存取了。相反地,PageUnlock()函数用于告知Windows哪些表面内存可被释放。
过程调用PageLock()函数会减少缓冲内存的总量,从而导致Windows的速度大大降低。至于这种情况何时发生,取决于页面锁定系统内存量及机器提供的系统内存量。
PageLock()和PageUnlock()函数主要是由DirectDraw提供而非DirectDraw应用程序。举个例子来说,DirectDraw自动使用PageLock()函数,以确保运行blt操作时,基于系统RAM的表面不被释放到硬盘。
PageLock()函数可以被同一个表面多次调用。DirectDraw用参考计数法记录PageLock()函数被调用的次数,因此多次调用PageUnlock()函数就必须避免多次调用PageLock()函数。
PageLock()和PageUnlock()函数对于驻留在显示RAM中的表面不起作用。
9. IsLost()的Restore()函数
现在讨论两个与使用驻留在显示RAM中的表面有关的函数:
●IsLost()
●Restore()
让我们来看一看下面这种情况。应用程序正在运行时,尽量把表面分配到显示RAM中,剩下的创建到系统RAM中。应用程序在运行一段时间之后,用户执行或切换到另一个应用程序。该应用程序是任意的,可以是一个常规的Windows程序,如Windows开发程序或记事本。它也可以是另外的DirectDraw应用程序,该程序也试图将尽可能多地分配显示RAM。如果DirectDraw不接受显示RAM,那么新的应用程序就很可能根本运行不了。相反的情况就意味着,应用程序不允许分配到任何显示RAM中。
因此,DirectDraw可以随意将任何一个或者所有的基于显示RAM的表面从非激活应用程序中移走。这种情况就是所谓的表面丢失。从技术上讲,程序仍具有表面,但它们不再同任何内存相关。要使用丢失的表面会导致DDERR-SURFACELOST错误。IsLost()函数可用于确定一个表面是否丢失了内存。
表面内存丢失后可通过Restore()函数恢复,但只能在应用程序被重新激活之后才可恢复。这会导致应用程序无法将处于最小化状态的所有表面复原。
Restore()函数可恢复附属于表面的任一内存,但并不恢复内存的内容。表面被复原后,应用程序就可以恢复表面内容了。
注意,这种用法不适合利用系统RAM创建的表面。如果需要用到基于系统RAM的表面所占内存,那么Windows会立即将这些表面释放到硬盘上。Windows自动地处理存储和恢复,包括恢复表面的内容。
10. GetDDInterface()函数
GetDDInterface()函数可检索用于创建给定表面的DirectDraw接口的指针。由于程序中大多数情况下只有一个DirectDraw接口实例,所以GetDDInterface()函数并不常用。但是一个应用程序中有可能使用多个DirectDraw接口,在这种情况下,GetDDInterface()函数会起到重要作用。
11. 表面连接函数
DirectDrawSurface接口提供以下4个函数,用来维持表面间的连接:
●AddAttachedSurface()
●DeleteAttachedSurface()
●EnumAttachedSurfaces()
●GetAttachedSurface()
DirectDraw支持大量的用于表面间的连接情况。最常见的情况就是页面翻转。进行页面翻转时,两个或两个以上的表面连接成环状,每次调用Flip()函数时,都会使连成环状的表面中的下一个表面显现。
表面连接函数用于创建、检查或消除表面间的连接,但这些函数并非必不可少的。DirectDraw往往是自动创建连接表面。比如,当创建一个主翻转表面时,可以指定用于连接表面的后备缓冲区的数量。DirectDraw就会创建这些表面,并将它们连接起来。
12. 重叠函数
DirectDrawSurface接口用于支持重叠的函数如下:
●AddOverlayDirtyRect()
●EnumOverlayZOrders()
●GetOverlayPosition()
●SetOverlayPosition()
●UpdateOverlay()
●UpdateOverlayDisplay()
●UpdateOverlayZOrder()
GetOverlayPosition()和SetOverlayPosition()函数用于控制重叠的位置。UpdateOverlay()函数用于更新大量的重叠设置,包括重叠是否可见,以及重叠是以色彩键码还是用alpha混合到背景表面上。
UpdateOverlayDisplay()函数用于更新显示设置。该函数用于更新整个重叠显示,或者只更新由AddOverlayDirtyRect()函数指定的矩形重叠部分。EnumOverlayZOrders()函数可根据重叠的Z值(Z值控制哪一个重叠位于最上面)重复重叠。重叠可按从前到后或从后到前的顺序枚举。
13. 剪裁器函数
DirectDraw支持的剪裁是将DirectDrawClipper接口(该接口我们尚未讨论)的一个实例连接到表面。一旦连接完毕,剪裁器对象就会有规律地blt到表面。剪裁器/表面的连接由以下两个DirectDrawSurface函数控制:
●GetClipper()
●SetClipper()
SetClipper()函数用来将剪裁器对象连接到表面。GetClipper()函数用于向前一个连接的剪裁器对象返回一个指针。SetClipper()函数还可用于解除表面同剪裁器的连接,具体的做法是通过指定NULL来替代DirctDrawClipper接口指针。
14。 调色板函数
像剪裁器一样,调色板也可连接到表面。DirctDrawSurface接口为此提供以下两个函数:
●GetPalette()
●SetPalette()
SetPalette()函数用来将DirctDrawPalette接口(该接口我们接下来就要讨论)的一个实例连接到表面。GetPalette()函数用于检索前一个连接调色板的指针。
调色板可被连接到任何表面,但只有连接到主表面时,调色板才起作用。当与主表面连接时,调色板决定显示硬件调色板的设置。
第六节 DirectDrawPlette接口函数
DirctDraw提供DirctDrawPalette接口用于调色板显示模式和表面。尽管Windows支持几种低于8位像素深度的显示模式,但DirctDraw所支持的唯一的调色板显示模式是8位模式。
DirctDrawPalette接口实例由DirctDraw CreatePalette()函数创建。CreatePalette()函数用大量的标志来定义调色板属性。
DirctDrawPalette接口只提供以下3个函数:
●GetCaps()
●GetEntries()
●SetEntries()
GetCaps()函数用于检索有关调色板的信息,包括调色板项目数量,调色板是否支持同步垂直刷新,以及在8位调色板状态下是否所有的256个调色板项目都能被设定。
SetEntries()函数允许在程序中设置调色板的色彩值。该数据从文件中读取。而这些项目在运行过程中可被计算和设定。GetEntries()函数用于检索先前设定的调色板项目。
DirctDrawPalette()接口实例可利用DirctDrawSurface SetPalette()函数连接到表面。将不同调色板连接到主表面或利用SetEntries()函数改变调色板项目都可激活调色板。
第七节 DirectDrawClipper接口函数
DirctDrawClipper接口支持剪裁。将剪裁器对象连接到表面并在blt操作中将其当作目标表面就可以进行剪裁。
directDrawClipper实例由DirectDraw CreateClipper()函数创建。DirectDrawClipper接口支持以下5个函数:
●SetHWnd()
●GetHWnd()
●IsClipListChanged()
●SetClipList()
●GetClipList()
剪裁器对象一般用于出现在窗口中的DirctDraw应用程序必需的剪裁。要求剪裁器必须确保在blt操作过程中考虑到桌面上其他的窗口。比如,当应用程序的全部或一部分被另一个窗口遮蔽,剪裁就必须确保被遮蔽的窗口不被DirctDraw应用程序破坏。
桌面剪裁由SetWnd()函数完成。SetHWnd()函数将剪裁器对象连接到一个窗口句柄。这样就初始化了Windows和剪裁器对象之间的通讯。当桌面上的任何一个窗口发生变化时,剪裁器对象就会得到通知,并作出反应。GetHWnd()函数用于决定剪裁器同哪一个窗口句柄连接。IsClipListChanged()函数用于决定内部剪裁清单是否因桌面的改变而被更新。
SetClipList()和GetClipList()函数为DirectDrawClipper接口提供便利的定制使用。SetClipList()函数用于定义一些矩形区域,这些矩形区域用来定义blt操作的合法区域。GetClipList()函数用于检索剪裁器的内部剪裁数据。
一旦被连接到表面,Blt(),BltBatch()以及UpdateOverlay()函数所进行的blt操作将根据DirctDrawCliper接口中包含的数据被自动地剪裁。注意,Blt Fast()函数在此被忽略。BltFast()函数不支持剪裁。
第八节 附加DirectDraw接口
DirctDraw还提供了另外个我们没有讨论的接口,它们是:
●DDVideoPortContainer
●DirectDrawColorControl
●DirectDrawVideoport
这些接口由DirectX5介绍,它们提供低水平视频端口控制。这些接口还向DirctDraw表面提供流活动视频的方法。尽管利用这些接口可以为DirctDraw应用程序增加视频支持,但除非高水平视频APIs不能满足需要,否则最好不用这一方法。
第九节 DirectDraw结构
我们已讨论过DirctDraw接口及其成员函数了,接下来再看看DirctDraw定义的结构。DirctDraw总共定义了8全结构:
●DDBLTFX
●DDCAPS
●DDOVERLAYFX
●DDPIXELFORMAT
●DDSURFACEDESC
●DDSCAPS
●DDBLTBATCH
●DDCOLORKEY
我们已经见过其中的一些结构了,比如在讨论DirctDrawSurface SetColorKey()函数时我们就接触过DDCOLORKEY结构。在此,我们不详细讨论每一个结构的细节,但必须指出,DirctDraw quirk被忘记时会导致失败。
以上所列结构中的前5个有一个称作dwSize的字段。该字段用于存储结构的大小,设定该字段的工作由你来做。另外,该字段如果没有被正确设定的话,那么任何一个将这5个结构作为变量的DirctDraw函数都会失效。
以DDSURFACEDESC结构为例,使用结构的代码如下:
DDSURFACEDESC surfdesc; surfdesc.dwSize=sizeof(surfdesc); surf->GetSurfaceDesc(&surfdesc);
首先声明结构,然后用sizeof()关键字设定dwSize字段。最后结构传递给DirctDrawSurface GetSurfaceDesc()函数。忘记设定dwSize字段将导致这段代码失效。
到底为什么DirctDraw坚持要求给出它所定义的结构的大小?原因在于这5个包含dwSize字段的结构将来有可能会改变。DirctDraw会检查结构的大小,以便确定正在使用的版本。DirctDraw坚持要求给出一个有效的大小值,是为了让开发者提供有效的结构大小。这样做是有好处的,因为DirctDraw的新版本可以正确运用旧版本的DirctDraw程序。
在使用结构之前,最好将结构初始化为零。这样,前面的代码就变成:
DDSURFACEDESC surfdesc; ZeroMemory (&surfdesc,sizeof(surfdesc)); surfdesc.dwSize=sizeof(surfdesc); surf->GetSurfaceDesc(&surfdesc);
ZeroMemory()函数是一个Win32函数,它将作为第一个参数的存储器设定为零.ZeroMemory()函数的第二个参数表明有多少存储器应被初始化。这一做法的好处是,通过GetSurfaceDesc()函数调用可以知道结构的哪些字段被更新了。如果没有对结构进行初始化,就有可能将结构字段中不可预测的值当作DirectDraw的设定值。
第十节 窗口应用程序
DirctDraw应用程序主要有两种型式:窗口的和全屏的。窗口DirctDraw应用程序看起来就像一个常规的Windows程序。我们很快将讨论到全屏应用程序。
窗口应用程序包括窗口边界、标题框以及菜单,这些都是传统的Windows应用程序中常见的部分。由于窗口应用程序同其他窗口一起出现在桌面上,因此它们被迫使用Windows当前所使用的分辨率和比特深度。
窗口程序有一个主表面,但只在进行真实页面翻转时才显现。而且,主表面并不代表窗口的客户区域(该区域在窗口边界内)。主表面还代表整个桌面。这就是说,你的程序必须追踪窗口的位置和大小,以便在窗口内正确显示可见的输出。换言之,利用窗口化的应用程序中可以在整个桌面上进行绘图。
如果不允许页面翻转,那么图像就必须从离屏缓冲区blt到主表面上。这就增加了图像撕裂的可能性,因为blt比页面翻转速度慢。为了避免图像撕裂,blt操作可以与监视的刷新率保持同步。
如果与窗口客户区域同样大小的离屏缓冲区被创建到显示RAM中,窗口应用程序就可以很好地运行。这样,窗口的内容可利用离屏表面合成。然后离屏表面可以通过硬件加速很快地到主表面上。
由于显示存储器的缺乏而不得不将离屏缓冲区创建到系统RAM中时,会严重影响性能。不幸的是,这种情况常常发生,尤其是在只有2MB显示卡的时候,这是因为人们总希望为自己Windows的桌面设置高分辨的显示模式。例如,采用1024×768×16显示模式的主表面自己就要占用2MB的RAM。在一个2MB显示卡上,几乎没有显示RAM留给离屏表面。
窗口应用程序的另一个问题是剪裁。一个性能良好的应用程序必须有一个连接到主表面的剪裁对象。这是有损性能的,原因在于为了检查剪裁器的剪裁清单内容,blt操作只能在一个小的矩形部分内进行。而且,不能使用优化的BltFast()函数。Bltting必须用较慢的,(而且更笨重的)Blt()函数。
最后要讲的是,窗口应用程序不允许全调色板控制。由于Windows保留了20个调色板项,所以在256种颜色只有236种颜色可被设定。被Windows保留的颜色只用系统调色板的前10个项和后10个项。因此在给图像上色时,只能使用中间236个调色板项。
第十一节全屏应用程序
包含对显示设备进行专有存取的DirctDraw应用程序就是全屏应用程序。这种应用程序可以任意选取显示卡所支持的显示模式,并享有全调色板控制。另外,全屏应用程序还可进行页面翻转。因此同窗口应用程序相比,全屏应用程序速度更快,灵活性更好。
典型的全屏应用程序首先检查所支持的显示模式,并激活其中一个。然后创建具有一个或更多后备缓冲区的可翻转主表面,剩下的显示RAM用于创建离屏表面。当显示RAM耗尽时,就启用系统RAM。屏幕被在后备缓冲区中合成的第一个场景更新,然后进行页面翻转。即使主表面占用了所有的可用的显示RAM,全屏应用程序还可输出执行其窗口化的副本,这是因为全屏应用程序可进行真实页面翻转。
由于全屏应用程序不一定非得使用Windows的当前显示模式,所以显示RAM的可用与否不存在多少问题。如果只检测到2MB的显示RAM,就可使用低分辨率显示模式来保留内存。如果检测到4MB的显示RAM,应用程序就可使用要求的显示模式并仍有保持良好性能。
全调色板控制也是个有利因素。这样可以使用所有256个调色板项而无需根据Windows保留的20中颜色重新分配位图。
第十二节混合应用程序
混合应用程序既可以在全屏方式下运行也可在窗口方式下运行。混合应用程序内部非常复杂,但却可以提供良好的性能。用户可在窗口方式下运行,如果太慢,则可切换到全屏方式下运行。
编写混合应用程序最好的方法是编写一个定制库,该库包括那些与应用程序使用全屏方式还是窗口方式无关的函数。象编程
Windows实现了一种仿OOP(面向对象编程)环境。Windows下的消息系统负责在多任务环境中分解信息。从应用程序的角度来看,消息是关于发生的事件的通知。用户可以通过按下或移动鼠标来产生这些事件。