第五章 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?68。一些显示卡支持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种颜色之一。
当你使用调色板显示模式时,必须保证在应用程序中的图像也使用同一调色板。如果没有做到庖坏悖允镜囊恍┗蛉客枷裰薪鱿执砦蟮难丈5魃逡不岽绰榉常绕涫怯靡桓龅魃謇聪允敬罅客枷竦氖焙颉5魃逡灿幸恍┯攀啤U缜懊嫣岬降模魃逶市碓谝桓鲇邢奚实某『鲜褂米疃嗟纳省5魃逡苍市淼魃宥?调色板动画是动画通过改变调色板项目,而不是改变像素值来执行的技术,这就使得一个屏幕上的很多像素可以瞬时改变颜色。对于一些有限的应用程序,诸如分配的、重复的动画,调色板动画很有用处。
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-UNSU