Device的Reset

来源:互联网 发布:java小游戏程序代码 编辑:程序博客网 时间:2024/06/06 07:26
最近一直在头疼如果窗口大小改变了,如何将后台缓存的大小也随之改变的问题,还有窗口与全屏之间的转换问题,这两个小小的问题困扰了我三天三夜,终于都给我破解了,当时那个开心呀!我怎么破解的?嗯……是研究了微软的DXUT源代码才办到的,话说那个DXUT不是一般的难理解,它里面往往一个函数千回百转,想找到它的核心代码还真心不容易,没有一定基础最好不要去研究,否则你会苦恼不已。不过里面的知识还真博大精深,毕竟是DirectX的BOSS微软老大写的实例,研究透了,DirectX也算入门了,不过最重要的还是它的编程思想以及它的框架构建。好了,废话不多说,该进入主题了。

首先,先来看下问题,如果窗口大小改变了,如何将后台缓存的大小随着改变?

第一步:先来解决如何判断窗口大小改变了,学过Windows编程的人都知道,在处理消息的回调函数中可以进行判断,没错,就是那个像MsgProc()的函数,在里面,可以处理Windows发送来的消息,可以在里面判断按下了哪个键,窗口是否创建,是否被关闭之类的消息。其中有一个WM_SIZE的消息,看到它的名字就可以知道它是干嘛用的啦,不过要注意的是,什么时候Windows会发错WM_SIZE的消息呢?

(1) 当窗口刚建立的时候,

(2) 当窗口的大小发生改变的时候,

(3) 当你调用了SetWindowPos()的时候,

(4) 当窗口最大化或最小化的时候。

都会发出WM_SIZE的信息,剩下的就看里怎么处理,当这里我还有一点想提的就是,那个SetWindowPos() 函数,它可以将窗口调整为指定的大小,但是你千万不要将它放入WM_SIZE的处理里面,不然它就进入一个死循环,因为你不断的调用SetWindowPos()会不断的发出WM_SIZE的消息。

好了,判断窗口大小的是否改变的问题解决了,但还不是我想说的主题。接下来就是最棘手的问题了,准备好了没?那我就当你准备好了!

第二步:如何改变后台缓存的大小?你或许会想将之前的Device设备全部释放掉,然后再创建一遍就行啦,这个方案是可行的,但是恭喜你,你浪费宝贵的资源与时间,还有浪费了微软老大辛辛苦苦想出来的Reset(),OnResetDevice()API函数,所以这个方案是不可用的,如果你还坚持你的想法是正确的话!那么好好想想,如果你的游戏要加载很多纹理,很多模型,你认为释放掉了再来创建,这样子得浪费多少的时间,如果有玩家玩你的游戏,肯定会破口而出,不就随便改下窗口嘛,用得着这么久吗!!!

所以我们得另寻方案,你也许会想到用Device的Reset(),没错,这个方案会比上面那个好几百倍,但你会用这个函数吗?那么先来看看这个API函数要求传入的参数。

HRESULT Reset(D3DPRESENT_PARAMETERS *pD3dpresent_Parameters)。

你会发现,它只需要一个D3DPRESENT_PARAMETERS结构体的指针,那么好办,我们填充这个结构体就行啦,你也许会像以下那样子填充:

D3DPRESENT_PARAMETERS d3dpp;

ZeroMemory(&d3dpp,sizeof(D3DPRESENT_PARAMETERS));

d3dpp.BackBufferFormat =D3DFMT_A8B8G8R8

d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

d3dpp.BackBufferCount = 1;

d3dpp.BackBufferWidth = width;

d3dpp.BackBufferHeight = height;

d3dpp.hDeviceWindow = hwMain;

d3dpp.Windowed = true;

d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT

然后你很高兴的会想到,当窗口改变的时候,就调用这个Reset(),将上面的结构体的指针传入做参数,你激动的想看看效果如何,然后点了运行,但你会发现会跳出一个错误,前提是你像下面那样使用:

hr = pDevice->Reset(&d3dpp);

assert(SUCCEEDED(hr));

判断一个函数是否运行成功是非常重要的,你也选择可以抛出一个异常而不用断言,但无论怎样,那个函数都不会调用成功。你很自然的就想到,肯定是上面的结构体填充错误了,但是错误在哪呢?我们只填充了我们想要的那部分,不要的全取默认值0了,错就错在了有些不该是0的,你却默认取了0。如果你加上下面两行:

d3dpp.EnableAutoDepthStencil = 1;

d3dpp.AutoDepthStencilFormat = D3DFMT_D24X8;

你再运行,如果你其他地方没有错误而且没有用到需要在内存池创建的接口话,只是简简单单的调用pDevice的Clear、BeginSence()、EndSence()、Present(),当你改变了窗口,这个函数调用成功了,没有跳出错误信息了。

现在我们来想想是为什么?首先看下上面那两行说的是什么,意思一看就懂,开启自动深度模板缓存,并将格式设置为D3DFMT_D24X8,那么什么叫深度模板缓存,其实是两个概念。

(1) 深度缓存就是那个Z-Buffer,我们知道一个点有(X,Y,Z),如果设置了Z坐标值,要开启深度缓存才有效,不然Z坐标的值作废了,如果开启了深度缓存,那么每个像素都有一个深度值,就是Z值,如果分辨率是800*600的话,就说明有800*600个深度值,然后通过深度缓存算法,将最靠近视窗的像素显示出来,被遮盖住的像素将不能显示。所以就有了3D世界。

(2) 模板缓存与深度缓存其实是一起的,看下上面的格式,D3DFMT_D24X8意思就是说24位分配给深度缓存,8位分配给模板缓存,一共32为,也就是它们两个共睡一张床,深度缓存占了3/4,模板缓存占了1/4。模板缓存是用来实现一些效果的,它可以决定那些东西不显示,哪些东西显示,镜面效果还有阴影效果也是用它来完成的。

到这里,终于松一口气了,那是不是说明问题就解决了呢?也许你注意到了上面我说的那句话“没有用到需要在内存池创建的接口”,什么是内存池?龙书有讲,去翻一下,简单的说就是放资源的地方,例如将文字Font放进显存,将点精灵sprite放进显存!

这时候问题就来了,如果你创建了Font、sprite之类的接口,当你改变了窗口,你什么也不管就调用pDevice的Reset(),你会发现,错误又跳出来了,因为你pDevice重设了显示模式,那些接口失效了,不过也很容易解决,在Reset之前先将它们OnLostDevice()掉,然后在pDevice->Reset()后再OnResetDevice()就可以了。

不过这里还有一个小技巧,将下面的改为0更好

d3dpp.BackBufferWidth =0;

d3dpp.BackBufferHeight = 0;

不用担心,它不会真的是0,Direct3D会为我们自动设置为正确的值,不要注意的是,当你创建一个为800*600的窗口时,它真正显示图形的区域并没有这么多,而是784*562,剩下的一部分留给了窗口边框。如果将上面的设置为0,你再跟踪一下D3DPRESENT_PARAMETERS d3dpp的值,就会发现d3dpp.BackBufferWidth 和d3dpp.BackBufferHeight 的值其实不是800*600,而是784*562,不过这个只是在窗口模式才有效。如果是全屏模式,那可不是这么简单了,想了解的话看一下我另一篇文章。

原创粉丝点击