M8SDK教程-游戏开发心得(二):DirectDraw基础(5月29日更新)

来源:互联网 发布:apache麒麟 编辑:程序博客网 时间:2024/06/11 01:28

M8SDK教程-游戏开发心得(二):DirectDraw基础(5月29日更新)

引用:
M8SDK教程-游戏开发心得(一): 游戏程序框架
http://bbs.meizu.com/thread-957024-1-4.html
2009-05-18
WM_ACTIVATE已处理,感谢linuxlt的提醒.

M8SDK教程-游戏开发心得(二): DirectDraw基础
http://bbs.meizu.com/thread-968949-1-1.html
2009-05-25

M8SDK教程-游戏开发心得(三): DDraw进阶教程-简单贴图,Alpha半透明效果和Sprite动画
http://bbs.meizu.com/thread-981601-1-1.html
2009-12-04 屏幕闪的问题,请看第三篇教程80楼

题外话:
非常高兴上一篇教程能给大家带来帮助.同时非常感谢指出不足的朋友(没有处理WM_ACTIVATE消息),我后来已经处理了这个问题.第二个教程其实想快点写出来的,但是由于最近非常忙,所以写的比较慢,请大家谅解.本期的目的是为大家简单介绍DirectDraw(以下简称DDraw),因为对于DDraw我本人也只是略懂,所以只能将我所知道的告诉大家了.因为使用图形加速有许多性能方面的考虑,所以这一章我会写一些理论性的东西来讲一些我了解的细节,以利于大家对自己的程序进行更好的优化. DDraw的接口和函数细节请大家查MSDN,如果大家在使用中有问题,欢迎回帖讨论,我会尽可能的去解决大家使用DDraw的问题.在开始这章之前我建议不熟悉Windows GDI的朋友先去简单去了解一下Windows GDI的使用,因为GDI是Windows绘图的基础,了解GDI也会有助于学习DirectDraw.

正式开始:
在上一个教程中,我们大致介绍了一个M8游戏的基本程序框架,同时进行了最简单的绘图.通过显示帧数,我们发现绘图方面存在瓶颈,所以我们在这一章将通过硬件加速解决这个问题.

大家知道,目前的游戏根据画面大致分为2D和3D游戏,二者在M8中的实现硬件加速的技术并不相同. M8所使用的三星6410支持DirectDraw,Direct 3D Mobile, OpenGL ES 1.1/2.0等图形加速接口. 如果打算在M8中制作2D游戏,一般可以选择DDraw. OpenGL ES其实也可以在M8里用做2D加速(在0905的SDK中可以看到相应的类库封装,但是还没验证是否可用),这个问题如果有机会我们将在以后的教程讨论(这就要看我们的教程是不是真正受到大家欢迎啦,哈哈). 本章我们重点介绍DDraw.

DDraw的背景
DDraw是DirectX的2D部分,通过DDraw,可以为开发者提供一个比GDI层次更高、功能更强、操作更有效、速度更快的应用程序图象引擎,与此同时,其保持了设备无关的优良特性. DDraw同时有一个优点,虽然我们使用了DDraw,但是我们仍然可以把DDraw和我们熟悉的GDI函数结合起来使用. 在Win32中, 8.0以上版本的DirectX,DDraw都已经不再更新,所以DDraw在Win32里已经不再作为主流的2D图形. 但是在M8(对应WinCE 6.0)中,由于图形技术比PC还要慢一拍,所以DDraw依然非常重要.

为什么使用DDraw
如果大家已经有WINCE或者WM的开发经验,可能会用过GAPI,GAPI是微软尚未在WinCE中实现DirectX功能的替代选择,所以很多很老的Windows Mobile游戏使用了GAP作为图形接口,但是到了M8目前所用的WINCE6.0, DirectX已经比较完善了,由于DirectX在PC上就基本上已经是游戏的标准库,如果使用DirectX,开发人员可以得到更多的资源和文档支持.而且目前来看,M8本身并未实现GAPI的硬件加速,所以对于2D游戏,DDraw是我们的优先选择之一.

为了理解DDraw硬件加速的方法以及了解如何正确使用DDraw,下面简单介绍一些概念:

2009-05-29
我们使用DDraw绘图与Windows绘图的区别
这里其实有一点东西要讲,结果我给漏掉了.算是回答KORY的问题.

我们平时在Windows中绘图时遵循这样的流程: 当Windows需要你的窗口重绘的时候,会给窗口发一个WM_PAINT消息,我们只需要在响应这个消息的函数写下绘图代码就可以了.在M8 SDK中的CMzWnd类中,OnPaint函数就是WM_PAINT消息的响应函数.PaintWin函数应该是优化过的绘图函数(依据绘图的效果来看,很可能已经实现了双缓冲),所以在一般应用中我们在PaintWin这个函数里写绘图代码就可以了.如果我们需要频繁主动的刷新屏幕,我们就要不断的用Invalidate或者InvalidateRect函数使窗口(或某一部分区域)失效,这样Windows就会发给我们WM_PAINT消息了.
但是在游戏中,一般都不会使用这种方式. 因为WM_PAINT产生的优先级是很低的,而且消息队列2个WM_PAINT消息是会被Windows合并掉的.所以这种方式在我们的游戏应用中是不可靠的.一般在游戏程序中我们都不依赖Windows的WM_PAINT消息,而是采用主动绘图的方式.也就是间隔一段时间我们就必须强制去绘图(请再看一下第一章教程中的游戏框架的实现,正是基于这种思想).DDraw是直接依靠硬件加速方式实现的,所以绘图根本不受窗口的限制,我们可以直接绘图到屏幕的任何位置(这一点也有点不好,因为我们必须在窗口失去焦点时停止绘图让M8处理电话等其他事件,否则即使接到电话你看到的仍然是游戏屏幕在不断的刷...).

Surface与双缓冲
我们在使用GDI绘图的时候可能有这样的经历: 我们通常在绘图的时候会得到一个当前窗口的DC(设备句柄),这个DC描述了最终的图形设备,我们使用这个DC去绘图就可以把图形绘到图形设备上. 如果绘图过程很复杂我们通常会发现这样绘图速度会很慢,典型的现象是看到屏幕闪烁.我们通常采用的优化方法是创建一个内存DC,将图形先绘制到这个DC上面,之后通过BitBlt这个函数通过位拷贝的方式将内存DC的数据拷贝到图形设备上去,由于内存拷贝的速度很快,所以我们很大程度改进了绘图效率(这种技术叫做双缓冲).

DDraw中Surface的作用跟我们刚才说的例子一个道理. 对于DDraw来说,要绘图,首先要创建一个Primary Surface,这代表当前的屏幕,要实现优化,我们需要再创建一个Background Surface(这就相当于前面说的内存DC),绘图的时候,先将图形绘制到Background Surface,然后将Background Surface的内容复制到Primary Surface,这个就是DDraw实现的双缓冲.与GDI 的双缓冲不同的是,DDraw的双缓冲是使用硬件加速实现的,所以速度会更快.

全屏独占模式与窗口模式,Flip与Blt
DDraw在全屏模式和窗口模式的性能并不相同. 还以刚才提到的双缓冲为例,刚才我们讲过,在最终绘图的时候,我们需要将Background Surface中的数据复制到Primary Surface上去,在窗口模式中,为实现这个功能我们只能使用Surface对象的Blt这个函数,这个函数就是直接进行位拷贝(当然,虽然仍然是拷贝操作,但是由于有硬件优化,这个拷贝操作比GDI的BitBlt还是要快很多的).而在全屏模式中,我们则可以使用Flip这个函数,这个函数则要快的多,而且这个函数是一个完全的异步操作(这一点很重要哦),如果你的Surface是创建在显存中,它的实际操作的是直接交换2个Surface中的显存指针,这样连位拷贝都省了,效率自然最高. 经过我在M8里的简单测试,全屏模式大概比窗口模式要快十几帧左右.这就是为什么大多数游戏都要做成全屏模式了.
(补充: 忘了一点,通过Overlay可以在窗口模式下进行Flip,这样应该也可以实现和全屏类似的性能,这一点我没有试过,待验证)

更好的解决方案: 三缓冲
在大家已经理解双缓冲的基础上,我们来介绍三缓冲.顾名思义,三缓冲只不过是再多一层缓冲罢了.那么,我们为什么需要3缓冲呢?

上面的一张图来自MSDN,清楚的描述了3缓冲的Surface结构,A,B,C表示Surface中封装的显存/内存指针.可以看到,每经过一次Flip,三个Surface中的内存指针就会发生调换. 刚才我们讲过了Flip是个异步操作,即我们在调用了这个函数是可以直接返回的.这时就是Flip大显神通的时候了.

一般我们的绘图过程是这样的: 将图形绘到Bufferbuffer Surface上去,然后调用Primary的Flip函数.在每次绘图的时候我们都同样执行这样的步骤. 如图所示,我们在第一次Flip之后BackBuffer内的显存/内存指针已经被改变了,所以我们在Flip函数返回之后我们不用考虑Flip操作是否已经完成,我们已经可以用BackBuffer进行下一次绘图了.这样其实等于DDraw自动提供我们2个BackBuffer交替使用,同时省去了上一次Flip的等待时间.三缓冲比双缓冲虽然要多消耗一些资源,但是依据微软的说法,速度能再快20%左右,是一种比较好的方案.

下面,在正式看代码之前,我们简单介绍一下DDraw的编程接口,DDraw的编程接口是通过COM的方法暴露,所以我们主要关心的是以下几个主要的COM接口:

IDirectDraw 这个是DDraw的基本功能接口,其他的接口基本要从这个接口创建,同时我们使用这个接口来进行一些DDraw的最基本功能操作,这个接口要使用DirectDrawCreate这个全局函数来创建
IDirectDrawSurface 这个是最重要的了,就是是我们刚才所提到的Surface,我们大多数的绘图操作都必须依赖它来进行,这个接口由IDirectDraw 的方法来创建
IDirectDrawClipper 这是个裁剪器,当我们使用窗口模式时,这个接口比较有用,可以防止我们将图形绘到窗口外面,这个接口由IDirectDraw 的方法来创建

好的,下面我们仍然基于上一次教程的例子,加上DDraw的代码,这次我们主要目的是理解DDraw的基本概念和使用方法,所以我们仍然以绘文字为主,至于位图操作我们会在下一次讲.


InitDraw函数是我们新增加的,用来初始化DDraw的相关对象.可以看到,我们首先使用DirectDrawCreate函数创建了IDirectDraw接口,之后使用这个接口设定全屏模式并创建其他的接口.创建Surface时的步骤比较重要.我们通过制定DDSCAPS_FLIP和BackBufferCount就在创建Primary Surface的同时创建了2个支持Flip的BackBuffer Surface. DDraw会自动将他们形成一个链,不用我们去操心他们的具体关系.之后我们通过遍历Primary Surface上相关联的Surface来找到第一个BackBuffer Surface的指针,以后我们绘图用的就是这个Surface.这个回调函数的代码如下:



下面我们来看具体绘图时的操作:

刚才讲到,DDraw是兼容GDI的,所以我们在使用DDraw的同时仍然可以用GDI函数.可以通过GetDC得到Surface的DC,通过ReleaseDC释放DC.由于目前M8无法对DDraw的全屏程序进行截屏,所以后面我自己写了代码进行截屏,大家可以加到自己的游戏中,可能有用.下面就是最重要的Flip,可以看到,虽然我们有2个BackBuffer Surface,但是我们根本不用指定从哪个BackBuffer Flip到Primary Surface,DDraw的链式结构会为我们搞定一切.


最后就是在程序退出时的清理动作了.由于DirectX是基于COM的,所以我们只需要把所有的DirectX对象Release即可了.

最终的运行效果如下,和上次一样,很土鳖,呵呵,不过效率提升明显哦,你可以试着把帧数限制去掉,看看我们的程序究竟能跑多快.


通过这次的介绍,不知道大家对DDraw是否已经有大概的了解了呢.因为篇幅有限,没有做过多细节的描述,关于DDraw的应用细节,最好的老师还是MSDN: http://msdn.microsoft.com/en-us/library/aa913258.aspx,这里有文档,有范例,大家在具体开发过程中可能需要经常参考.


今天的代码不太多,但是我费了很多篇幅去讲一些我理解的概念,不知道这样是不是更有助于理解呢.抱歉的是,以我现有的时间和能力,大概就能写成这样了,呵呵.哪里不对的地方,欢迎拍砖.

最后,我们来看看通过本次的学习,我们了解到了什么:
1. 了解了DDraw加速的原理
2. 了解了DDraw的初始化,简单使用和对象的最后释放
3. 了解了三缓冲的优点和使用方法
4. 通过看这次的代码,可以了解如何在DDraw全屏模式下自己截图

OK,我们已经有了图形引擎,但是丰富的游戏画面只靠这几行代码是做不出来的.请关注我们的下一个教程: DirectDraw进阶: 位图显示,半透明和精灵动画......

本次教程源代码: GameSample1_step2.rar (26.68 KB)