使用IDirectDrawSurface::Lock( )就能让我们随心所欲,因为此函数可以允许我们直接修改页面。 Lock( )函数的用法如下:
HRESULT Lock( LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent );
第一个参数为一个指向某个RECT的指针,它指定将被锁定的页面区域。如果该参数为 NULL,整个页面将被锁定。 第二个参数为一个 DDSURFACEDESC结构的地址,将被填充页面的相关信息。 第三个参数,即dwFlags,还是象以前一样给它DDLOCK_WAIT。 第四个参数规定要为NULL。 现在举一个例子来说明怎样使用Lock( ),我们的目标是使lpDDSBack半透明地浮现在lpDDSBuffer上。先看看完整的锁屏部分(注意,这一节只讨论24和32位色下如何操作):
DDSURFACEDESC ddsd, ddsd2; //DirectDraw页面描述 ZeroMemory(&ddsd, sizeof(ddsd)); //ddsd用前要清空 ddsd.dwSize = sizeof(ddsd); //DirectDraw中的对象都要这样 ZeroMemory(&ddsd2, sizeof(ddsd2)); ddsd2.dwSize = sizeof(ddsd2); lpDDSBuffer->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL); //Lock! lpDDSBack->Lock(NULL, &ddsd2, DDLOCK_WAIT, NULL);
BYTE *Bitmap = (BYTE*)ddsd.lpSurface; //Lock后页面的信息被存在这里,请注意 //这个指针可能每次Lock( )后都不同! BYTE *Bitmap2 = (BYTE*)ddsd2.lpSurface;
锁完页面后,Bitmap数组的存储格式是这样的:如为32位色则页面上坐标为(x,y)的点的R/G/B值分别存在Bitmap的y*ddsd.lPitch+x*4,y*ddsd.lPitch+x*4+1,y*ddsd.lPitch+x*4+2处;如为24位色则页面上坐标为(x,y)的点的R/G/B值分别存在Bitmap的y*ddsd.lPitch+x*3,y*ddsd.lPitch+x*3+1,y*ddsd.lPitch+x*3+2处。所以,现在我们就可以发挥想象,做出想要的一切效果了,比如说动态光照(将一目标页面按光照表改变亮度即可)!下面是接下来的代码(32位色时):
int pos; for (int y=0;y<480; y++) { pos=y*ddsd.lPitch; for (int x=0; x<640; x++) { Bitmap[pos] =(Bitmap[pos]+Bitmap2[pos])>>1; //改R pos++; Bitmap[pos] =(Bitmap[pos]+Bitmap2[pos])>>1; //改G pos++; Bitmap[pos] =(Bitmap[pos]+Bitmap2[pos])>>1; //改B pos+=2;//到下一个R处 } } lpDDSBack->Unlock(&ddsd2); //Unlock! lpDDSBuffer->Unlock(&ddsd);
由于使用Lock后DirectDraw要锁定页面,在没有使用Unlock( )前我们是无法用其他办法如Blt来修改页面的。所以用完Lock( )要赶快象上面的程序那样Unlock( )。Unlock的方法很简单,lpDDSXXX->Unlock(LPDDSURFACEDESC lpDDSurfaceDesc)即可。
7.2 程序的提速 上面的程序看起来好象很简单,但运行速度很可能会很慢,即使你直接用汇编重写也不会快多少。原因是读显存非常慢,写显存的速度也比写内存慢。解决这个问题的方法是: (1)把除了主页面外的所有页面放在内存中。(初始化页面时将ddsd.ddsCaps.dwCaps中的 DDSCAPS_OFFSCREENPLAIN后再或( | )一项"DDSCAPS_SYSTEMMEMORY")这样做的另一个好处是你Lock( )一次后就永远得到了页面指针,而且然后一Unlock( )就又可以使用Blt了,你就拥有了两种改变页面的手段。 (2)将后台缓冲改为一个普通的离屏页面。 (3)将Flip( )改用BltFast( )函数实现。当然你也可以直接用memcpy( )拷贝,这样做有时候会快一些。注意要一行一行地拷贝,比如说640x480x24位色全屏幕拷贝是这样的:
BYTE *pSrc=(BYTE *)ddsd_src.lpSurface; BYTE *pDest=(BYTE *)ddsd_dest.lpSurface;
for (int y=0;y<480;y++) { memcpy(pDest, pSrc, 1920); //若为32位色则为2560 pSrc+=ddsd_src.lPitch; pDest+=ddsd_dest.lPitch; }
看起来好像要重写很多东西,其实改动的部分并不多。这样改了之后整个程序的速度就会快很多,但还不能很好地满足全屏幕特效的要求,因为全屏幕特效实在很耗时间,只有用MMX指令重写速度才能比较快。所以在下面一章中,我们将介绍内嵌汇编和MMX指令。
7.3 排除错误的技巧 每个人都会犯错误,在编程中也如此,写完程序后第一次运行就直接通过的情况实在是不多的,偶尔出现一两次都是值得高兴的事。有错当然要改,但很多时候最难的并不是改正错误,而是找到错误,有时候写程序的时间还不如找错误的时间长。为了帮助大家节省一点时间,下面就讲一讲我的一点找错误的经验。 首先当然要说说常见的错误有哪些,最经常出现的是:漏分号、多分号、漏各种括号、多各种括号、"=="写成了"="(上面的错误看上去很弱智,不过你没犯过吗?)、数组(指针)越界(最常见的错误之一!)、变量越界、指针使用前未赋初值。有一点要注意的是Visual C++显示出的出错的那一行有可能不是真正出错的位置! 常用的找错办法就是先确认你刚刚改动了哪些语句,然后用/*和*/把可能出错的语句屏障掉,如果运行后还不通过就再扩大范围。即使有一段程序你觉得不可能有什么问题或以前工作正常也要试试将它屏障,有时就是在似乎最不可能出错的地方出了问题。 还有一种大家都经常用的找错办法就是把一些变量的值显示在屏幕上,或是把程序运行的详细过程存入文件中,出什么问题一目了然。如果再像QuakeIII一样用一个"控制台"显示出来就很酷了。 象其它编译器一样,Visual C++提供了变量观察(Watch)、单步执行(Step)等常规调试手段,当然你首先需要把工程设为Debug模式,在编译---放置可运行设置中可更改。但可惜的是在DirectDraw程序中这些方法不怎么好用。可能有的人会问我为什么不把程序改为在窗口模式下运行,但在窗口模式下你能Lock吗?你能随意改动显存吗?如果把这些语句屏障掉那可就太麻烦了。不过有两条调试语句可以在DirectDraw程序中调用,第一条就是assert。该语句的使用方法是assert(条件),你可以把它放到需要的地方,当条件不满足时就会显示一个对话框,说明在哪个程序哪一行出现了条件不满足,然后你可以选择继续或是忽略。第二条语句是OutputDebugString(要输出的字符串),可以在屏幕下方编译窗口的调试那一栏显示这个字符串。
7.4 C++程序优化技巧 首先请注意一点:再好的语句上的优化,比如说使用汇编语言,也比不上算法上的优化所带来的巨大效益。算法的设计是国内有些编程人员的不足之处,所以我觉得对这方面不太熟悉的人都应该买本讲数据结构与算法的书来看看,不要抄了别人的A*算法就觉得自己的程序很先进了。在9.4节讲述了几种常用的算法,如果你感兴趣可以看看。 下面就转入正题,讲一讲一般的优化技巧吧: (1) 使用内联函数。
(2)展开循环。
for (i = 0; i < 100; i++) { do_stuff(i); }
可以展开成: for (i = 0; i < 100; ) { do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; do_stuff(i); i++; }
(3)运算强度减弱。 x = w % 8; y = x * 33; for (i = 0; i < MAX; i++) { h = 14 * i; cout< }
上面的程序这样改动可以大大加快速度: x = w & 7; y = (x << 5) + x; //<<比+的运算优先级低! for (i = h = 0; i < MAX; i++) { cout< h += 14; }
(4)查表。这种方法挺有用,特别是在DirectDraw程序中。比如说我们定义了一个函数f(x)可以返回x*x*x,其中x的范围是0~1,精度为0.001,那么我们可以建立一个数组a[1000],a[t*1000]存储的是预先计算好的t*t*t的值,以后调用函数就可以用查找数组代替了。
|
|
|