DirectX窗口模式一

来源:互联网 发布:淘宝客佣金代扣款 编辑:程序博客网 时间:2024/05/17 03:07

DIRECTX中独占模式与窗口模式的切换(一)
介绍
让你的游戏能够在独占(全屏)模式与窗口模式下运行应该很简单,但想要让它合理且优雅的运行就要多做些工作了.在这篇文章中,我将用业界十分常用的C++语言来讲解这方面的技术,如果你想,可以用类把这个例子封装起来以便于使用.
我假设你已熟悉独占模式下的DirectDraw的设置与使用,这里我不再赘述,让我们开始吧!
设计
 DirectDraw 窗口模式下的初始化有好多与独占模式不同.最好的方法是在你程序的开始就创建DirectDraw对象,第二步再创建所有表面,设置协调层级和显示模式, 初始化你需要的变量,等等.独占模式与窗口模式的不同都体现在这第二步中,所以,你的函数可以写成这样:

void CreateDirectDraw();
void DestroyDirectDraw();
和 
void CreateSurfaces(bool bExclusive, int nWidth, int nHeight, int nBPP);
void DestroySurfaces();
第 一部分(CreateDirectDraw and DestroyDirectDraw)分别创建和销毁DirectDraw对象.你自己应该可以完成 的.第二部分(CreateSurfaces and DestroySurfaces)解决所有除去创建和销毁DirectDraw对象以外的事.看看 参数 bExclusive,它表明创建一个独占模式的表面或窗口模式的表面以及相关的各个对象. 参数 width, height,bpp用以描述显 示模式(当bExclusive为true时)
我们需要稍微改变游戏循环以正确处理窗口模式.为了正常改变窗口模式我们增加了一个函数:
void SwitchMode(bool bExclusive, int nWidth, int nHeight, int nBPP);
继续来看如何实现这此些函数!

CreateSurfaces 
我们把这个函数分成两部分,分别实现独占模式和窗口模式下的DirectDraw的初始化,如下:
if( bExclusive )
{
    // exclusive code

    // save the mode
    g_bExclusive = bExclusive;
}
else
{
    // windowed code

    // save the mode
    g_bExclusive = bExclusive;
}
你还需要创建一个全局变量g_bExclusive用以标志当前的模式,在游戏循环中要用到它的.请明确g_bExclusive的重要性,我们借此追踪模式
你可以把你以前的代码放到exclusive部分,用参数nWidth, nHeight, 和 nBPP设置显示模式等.然后让我们一起来完成windowed部分的代码(我用了几个函数来实现,都将它们分成了两部分,就像你在这个函数里看到的一样)
就和我前面提到的一样,当这个函数被调用时, DirectDraw已经创建,所以初始化DirectDraw的下一步是通过lpDD->SetCooperativeLevel()来设置协调层级. 把主窗口的句柄和DDSCL_NORMAL作为参数传给它:
lpDD->SetCooperativeLevel(hMainWnd, DDSCL_NORMAL);
如果你想使用多线程,把标识DDSCL_MULTITHREADED与DDSCL_NORMA相与传给它就是了,但注意:在窗口模式下不能设置显示模式!所以下一步是创建主表面与后缓冲区.
在 窗口模式下你需要一个完全不同的”缓冲系统”,你不能在创建主表面时连接一个后缓冲区然后调用flip()函数来翻转,为什么?因为你在窗口模式下不能独 享显卡,而翻转是交换主表面和一个与其相连的后缓冲区的地址的过程,显然,你不能在窗口模式下这么做,因为此时你和其它应用程序共享主表面.
要使用窗口模式,应该用下面这个DDSURFACEDESC2结构去创建主表面:

DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));

ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

这些语句创建的主表面将使用现在的屏幕格式,而且你不能修改(因为是窗口模式),也请注意我没有使用DDSD_BACKBUFFERCOUNT这个标识,这只能在独占模式下使用
然后再创建后缓冲区,代码如下:

DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));

ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 6 4 0;  // whatever you want
ddsd.dwHeight = 4 8 0; // whatever you want

注意这里也没有使用DDSCAPS_BACKBUFFER标识,因为这也是特别为独占模式应用程序准备的
请记住,在DirectDraw中主表面往往表示整个屏幕.为了防止你在窗口模式下在整个屏幕里作图,你可以给主表面连接一个裁剪器,并将其与主窗口相连(这很简单!)
LPDIRECTDRAWCLIPPER lpddClipper;
lpDD->CreateClipper(...lpddClipper...);
lpddClipper->SetHWnd(...hMainWnd...);
lpddsPrimary->SetClipper(...lpddClipper...);
简便起见,我省略了这些函数的其它参数
好了,这就是CreateSurfaces函数,接下来我们要看看如何清除对象了!
DestroySurfaces
你结束程序所用的清理代码也将有所不同.原来你只需要释放DirectDraw对象和主表面,现在你还需释放后缓冲区和裁剪器.同样,用if 语句将处理独占模式的代码与处理窗口模式的代码分开:
if( bExclusive )
{
    // exclusive code
}
else
{
    // windowed code
}
把你的独占模式的代码放入exclusive  code部分,然后我们来添加窗口处理代码.
把下面的代码加入到windowed code部分:

if( lpddBack )
{
    // release the back buffer
    lpddBack->Release();
    lpddBack = NULL;
}

if( lpddPrimary )
{
    // release the clipper (indirectly)
    lpddPrimary->SetClipper(NULL);
    lpddClipper = NULL;

    // release the primary surface
    lpddPrimary->Release();
    lpddPrimary = NULL;
}

当你加入了上述代码后,让我们一起进入游戏循环!
由于论坛容量有限,分两部分贴出

当你加入了上述代码后,让我们一起进入游戏循环!

游戏循环
你也许认为在游戏循环中唯一真正的不同是当你在窗口模式下时把后缓冲区的内容贴到主表面上而不是用翻转(fliping).好,我们就从那里开始.将你的渲染函数也用if 语句分成两部分:
if( g_bExclusive )
{
    // exclusive code
}
else
{
    // windowed code
}
g_bExclusive表示什么?这是我们用来追踪当前模式的全局变量!
把你原来独占模式下的渲染代码移植到exclusive code部分,然后在windowed code部分增加一个Blt函数,主表面作为目的表面,后缓冲区作为源表面,如下:

lpddPrimary->Blt(NULL, lpddBack, NULL, DDBLT_WAIT, NULL);
简 直和随手拿来一样简单!等等,这语句对整个主表面作图,不只是窗口!我们怎样才能用DirectDraw把整个后缓冲区复制到窗口的窗户区呢?好,这样, 我们先得到窗口的客户区,然后把客户区矩形的两个点坐标转换成相对于屏幕左上角的坐标(屏幕坐标),再把这个矩形作为Blt的第一个参数,这里是部分代 码:
    // calculate the client rect in screen coordinates
    RECT rect;
    ZeroMemory(&rect, sizeof( rect ));

    // get the client area
    GetClientRect(hMainWnd, &rect);

    // copy the rect's data into two points
    POINT p1;
    POINT p2;

    p1.x = rect.left;
    p1.y = rect.top;
    p2.x = rect.right;
    p2.y = rect.bottom;

    // convert it to screen coordinates (like DirectDraw uses)
    ClientToScreen(hMainWnd, &p1);
    ClientToScreen(hMainWnd, &p2);

    // copy the two points' data back into the rect
    rect.left   = p1.x;
    rect.top    = p1.y;
    rect.right  = p2.x;
    rect.bottom = p2.y;

    // blit the back buffer to our window's position
    g_lpPrimary->Blt(&rect, g_lpBack, NULL, DDBLT_WAIT, NULL);


在 游戏循环中你还有一些要做的是:你不能你像独占模式下一样占着系统不放.当WINDOWS其它应用程序需要资源时,你必需让给它们.因为这是由用户选择决 定的,不仅仅取决于操作系统.一个好的窗口应用程序允许用户在窗口间切换,在同一时刻使用多个应用程序.这就是WINDOWS名字的由来吧!
那 么,我们怎么做呢?好, 是什么消耗了大部分计算机资源?当然是游戏循环.所以当用户切换出去时我们应该使游戏暂停,当用户切换回来时再自动开始游戏(在 像大型多人在线之类的游戏中,这不太可行,你必须想其它办法 提示:你至少可以暂停渲染或只是降低一点速度).不管怎样,想办法暂停游戏循环,我们添加一 个变量来追踪游戏状态:是否在运行!

bool bRunGame;
然后,我们在主消息循环(WndProc)中处理一个特定的消息: WM_ACTIVATE.每当主窗口获得或失去焦点时,我们就收到一个WM_ACTIVATE消息.wParam参数表明是获得焦点或失去焦点.设置bRunGame标识如下:

if( LOWORD( wParam ) == WA_INACTIVE )
{
    // the user is now working with another app
    bRunGame = false;
}
else
{
    // the user has now switched back to our app
    bRunGame = true;
}
如何使用这个刚设置的变量呢?每次继续游戏循环前检查此变量,并用以下代码代替你程序中的主消息循环(一般在WinMain函数中);我们看到游戏循环已嵌入到消息循环中(在最后部分):
MSG msg;
ZeroMemory(&msg, sizeof(msg));

for( ;; )
{
    if( PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE) )
    {
        // retrieve a message
        GetMessage(&msg, NULL, NULL, NULL);
        
        if( msg.message == WM_QUIT )
            break;  // only way out of the for( ;; ) loop

        // dispatch the message to our WndProc
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        if( bRunGame )
        {
            // game code here
        }
    }
}

return msg.wParam;

然后把你的游戏代码放入到新增的if  语句中(标有// game code here处),这样就完成了游戏循环部分.哈!现在到了看看如何在运行中改变模式的时候了,相信我,这将是本文中最容易的部分!
在运行时切换模式

啊!真的终于到了切换模式的时候了吗?
好的,我们将使用一个简单的函数来切换模式,它可以在窗口模式和独占模式间自由切换,并设置显示模式(在独占模式下),看:
void ChangeDisplayMode(bool bExclusive, int nWidth, int nHeight, int nBPP);
有点简单,不是吗?不,让我来向你解释一下.当你要从独占模式切换到窗口模式时,你这样调用函数:

ChangeDisplayMode(false, 0, 0, 0);  // windowed

当你想从窗口模式切换独占模式,或在独占模式下改变分辨率与色彩深度,你这样调用:

ChangeDisplayMode(true, 6 4 0, 4 8 0, 16);  // 6 4 0x4 8 0x16 exclusive
ChangeDisplayMode(true, 8 0 0, 6 0 0, 32);  // 8 0 0x6 0 0x32 exclusive

不错吧!让我们来完善它吧,下面是完整的函数:

ChangeDisplayMode(bool bExclusive, int nWidth, int nHeight, int nBPP)
{
    // destroy any existing surfaces and clippers.
    DestroySurfaces();

    // create new surfaces and change the 
    // cooperative level and display mode
    CreateSurfaces(bExclusive, int nWidth, int nHeight, int nBPP);
}

这就是模式切换!
在DirectX窗口应用程序中有许多技巧来提升性能,也有不少方法使其易于使用.我们的目标是达到达到两者合一.我将在下篇文章中来阐述这方面的问题!
祝好运!

原创粉丝点击