DirectDraw 编程指南

译者注:由于最近在学习DirectDraw的东西, 学了快两个星期了吧, 还是收获不少。今天周未闲来无事,实在无聊,总不能老想女朋友吧,呵呵,还是翻译点文章来打发时间。下面所有的DD表示DirectDraw。由于本人英语水平有限,可能翻译得不够人意,望体谅。亲爱的读者们,如果你发现有任何错误,请邮件指正:[email protected]


    原文地址:http://www.gamedev.net/reference/articles/article608.asp

 

目录

DirectDraw概述
1. DX API 集
2. 什么是DirectDraw
3. 与WinG关系
4. DD页面-如何处理显存
5. 页面交换生成动画
6. DD与COM

DirectDraw 程序设计
1. 编译与链接环境
2. DD初始化
3. 创建主页面并用于页面交换
4. 加载图片到显存
5. 加载调色板
6. 关键色
7. 场景组织
8. 释放清理工作

一些细节
1. DD的调试
2. 运行窗口模式
3. 页面丢失
4. DD深入学习

DirectDraw概述

本节将对DD进行概括化的介绍,解释学习DD的最基本概念。

1. DX API 集

微软DX API集是由以下部件组成:
   DirectDraw  直接操作显存
   DirectSound 直接操作声音硬件
   DirectPlay  对多人网络联机游戏的支持
   DirectInput 对游戏输入设备如操作杆的持
这些部件都被设计为供程序员直接用来操作硬件的APIs. 该文档核心讲解DD技术,让你能写出快速稳定的程序。

2. 什么是DirectDraw

DD实质是一个显存管理者。 最重要的功能就是允许程序员直接在显存中存储与操作位图。它能充分利用硬件加速器来区域拷贝显存中的位图。在有硬件加速的情况下, 从显存区拷贝到显存中要比从系统内存拷贝到显存中快许多。 特别是当前64位的显卡提供的64位的数据处理显存,效果更为明显。这种硬件支持的区域拷贝操作是独立于CPU的,因此它并不占用CPU运行时。别外,DD还支持显卡的其它硬件加速功能,例如点精灵与Z缓冲。DD1.0在windows95下已得到支持,到夏天将会在windows NT得到支持。

3. 与WinG的关系

  到现在为此, 在windows平台下,程序员可以用WinG或者CreateDIBSection技术来生成快速高效的动画。这种方法让程序员在系统内存直接处理位图,因此能用最高效的方式来绘制位图。WinG在处理位图的时候总是比GDI更好,因为GDI总是不可能以高效的方式满足游戏开发员的需要。
  当用位图的方式组装好WinG场景,它是从系统内存拷贝到显存中,从而显示出来。该技术并没有DD快,因为WinG是从内存到显存的拷贝,而DD是从显存到显存的拷贝,很明显显存到显存的拷贝要快于内存到显存的拷贝。由于显存是有一定限制的,而DD与WinG能共存,因此在许多复杂的游戏与程序当中它们将共存。

4. DD页面-如何处理显存

  利用DD时, 终极目的是将尽可能多的位图放入显存,因为所有显存对你来说都是可利用的。你可以用它来储存位图,也可以用来当主页面或离屏页面。在DD中,显存与一种叫着页面的东西有着直接联系(译注:可以理解为页面是显存的组成部分,即指向显存的指针)。当你载入一幅位图到显存中用来描绘一个sprite时,你需要的步骤如下:首先你要创建一个页面,该页面就是显存的一部份,然后载入一幅位图到该页面,也就是说你已经高效的将位图拷贝到了显存当中。此时该位图已经存在于显存当中供你随时使用。
  当前屏幕上显示的内容在显存中也是以页面的形式表示的,我们把它叫着主页面。主页面占用显存大小与当前显示模式一样大。如果显示模式是640×480大小的分辨率256色(每像素8bits),那么主页面占用的显存为大小为:307,200 bytes。而通常还需要创建至少一个与主页面大小相同的离屏页面,用于页面交换。也就是说在没有加载任何位图的情况下, 你就需要614,400 bytes的显存用于初始化工作。
  因此,用DD开发出快速的游戏最重要的硬件条件就是需要许多显存。当你显存不够用的时候,你的DD页面就会占用系统内存,而此刻在内存中的页面将不能利用硬件加速的功能。当前的主流显示都至小有1M的显存,而1M几乎不能让DD的优势发挥出来。一个2M显存的显卡可能是用于一个简单程序正常运行的最低要求。
  DD提供了运行时可利用的显存大小的查询函数,因此应用程序可以充分利用显存。

5.页面交换生成动画

  从一个页面的位图转变到另一个页面位图叫变换场景。利用硬件能力从可视的页面转变到别一个离屏页面,让新的页面场景可视。这就叫着页面交换,并且速度超快。快是由于不需要内存拷贝。只是简单的在显卡站注册,并把它作为向显示器发射显存上哪儿可以扫描并显示的信号。DD屏蔽了这些繁琐的硬件操作细节,而提供了一个简单的API Flip()取代它的工作。用页面交换生成动画的步骤如下:

  1.在离屏页面上组织好场景
  2.交换离屏页面,使它显示出来
  3.被交换的页面又变成了离屏页面
  4.重复步骤1
 
  交换帧率非常快,但受限于显示器的刷新率,因为一次页面翻转只会发生在垂直信号折回的时候。也就是说如果你的显示器设置的分辨率是72Hz,那你可以想象实现的帧速率高达72帧每秒。等待垂直折回信号是非常有意义的,因为它意为DD交面交换的时候你将看不到撕裂现象。撕裂现象是指,当你移入一个精灵到屏幕的时候发生了屏幕刷新。当精灵移到新的位置时候,该精灵就会出现撕裂感觉。例如:一张图片的上半部份在一个位置,而下半部份向左或向右发生了轻微的移动,给人的感觉就像图片从中间被破坏或撕裂。
  图像处理程序员开发出了许多聪明的技术来弥补精灵缺点, 这些技术也被利用在DD程序开发当中。但是学习这些技术并不能帮助你学习与理解DD。在此我的焦点是交换页面产生动画。页面交换可能是产生动画最简单的办法,与此同事它也是证明DD工作原理最好的方法。

6. DD与COM

  DX是以COM对象的方式执行的。与其从DLLs中简单的导出单凋的API相比,在DLLs中用COM对象有许多优点,包括版本与多态。DD开发的程序员都知道用COM对象并不比用其它的API难, 特别是对于懂C++或面向对向的Pascal程序员。
  你不需要初始化COM,或调用QueryInterface 来得到接口等。 DD的头文件里声明了各种用于DX对象的类。你只需要调用各种创建方法去实便化这些类。然后就是大概简单的学习一下各类的成员方法。

DD程序设计

  本节将呈现一些代码去展示一下DD的用法。这些示例将是功能完整的functioal, 但是它将整个过程的关键点展现得淋漓精致。

1.编译与链接环境

  首先,你需要DX SDK。当前,你可在MSDN II或更高版本中你将找到,IDE为MS VisualC++. 该SDK提供了一个详细的帮助文档,和优秀的例子程序。
  要编译与链接DD程序,你需要ddraw.dll, ddraw.h, 还需要用implib.exe工具从ddraw.dll中导出一份库。由于DX只属于win32技术,所以你需要一个编译器生成win32的应用程序,Borland C++4.52 与 Borland C++ 5.0提供了一个很好的DX开发平台。
  为了应用DX,你必须在你系统中安装DX的驱动程序。由于DX是一个通用的游戏开发技术,所以你会发现你的系统中已经预DX。请查看windows/system 目录下面,是否已有一份ddraw.dll。(记住,这项技术在Windows NT 3.X versions上不能得到支持)

2.DD初始化

  学习DD的第一件事是如何初始化它。请查看下面源码及其中每一步详细注释。
// 全局变量(ugh)
LPDIRECTDRAW lpDD; // DirectDraw object defined in DDRAW.H

/*
 * Function to initialize DirectDraw
 * Demonstrates:
 *   1) 创建DD对象
 *   2) 设置协作等级
 *   3) 设置显示模式
 *
 */
bool DirectDrawInit(HWND hwnd)
{
    HRESULT ddrval;

   /*
    * 创建DD对象.
    *
    * 该函数负责初始化COM,并构造DD对象
    */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
    {
        return(false);
    }

   /* 协作等级决字我们对屏幕的控制权。它必须为DDSCL_EXCLUSIVE与DDSCL_NORMAL中的一个
    *
    *
    * DDSCL_EXCLUSIVE允许我们改变显示方式,并且需要DDSCL_FULLSCREEN联合使用,
    * 它告诉DD将以全屏模式显示。这是首选模式,因为它允许我们操作整个屏幕而不用关心GDI。
    *
    * DDSCL_NORMAL 用来告诉DD应用程序将以窗口模式显示.
    */
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    if( ddrval != DD_OK )
    {
        lpDD->Release();
        return(false);
    }

   /*
    * 设置显示模式为 640x480x8
    * 这是允许的,因为上面已设置为独占模式
    */
    ddrval = lpDD->SetDisplayMode( 640, 480, 8);
    if( ddrval != DD_OK )
    {
        lpDD->Release();
        return(false);
    }

    return(true);
}
  我们现在已经初始化了DD,并设置了显示模式。

3.创建主页面并用于页面交换

  下一步我们要创建一个主页面与一个后缓冲。这在DD中叫着复杂页面。后缓冲附在主页面上。在程序运行结束的时候,你只需要通过主页面指针调用Release,与此同时后缓冲也得到了释放。

// 全局变量(ugh)
LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw primary surface
LPDIRECTDRAWSURFACE lpDDSBack; // DirectDraw back surface

/*
 * 创建一个可交换主页面,附带一个缓冲区
 */
bool CreatePrimarySurface()
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;
    HRESULT ddrval;

    //创建一个主页面附带一个后台缓冲
    memset( &ddsd, 0, sizeof(ddsd) );
    ddsd.dwSize = sizeof( ddsd );

    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 1;

    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
        lpDD->Release();
        return(false);
    }

    //  得到后台缓冲的指针
    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);
    if( ddrval != DD_OK )
    {
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }

    return true;
}
   我们现在创建好了双缓冲的页面交换环境。

4.载入图片到显存

  下一步我们将加载游戏当中所需的位图。理想化地,我们有足够的显存来存放所有的位图。如果没有足够显存,CreateSurface将会为你在内存中创建页面。所有的工作都一样, 只是拷贝页面的时候显得更慢一点。
/*
 * 创建一个离屏页并从磁盘上加载一幅位图到该页面。
 * szBitmap参数指向位图的文件名
 */
IDirectDrawSurface * DDLoadBitmap(IDirectDraw *pdd, LPCSTR szBitmap)
{
    HBITMAP hbm;
    BITMAP bm;
    IDirectDrawSurface *pdds;

    // LoadImage在windows 95上有一些附加功能,它使你可以从磁盘加载位图。
    hbm = (HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP, 0, 0,
    LR_LOADFROMFILE|LR_CREATEDIBSECTION);

    if (hbm == NULL)
        return NULL;

    GetObject(hbm, sizeof(bm), &bm); // 获取BITMAP

   /*
    * 创建一个离屏页面存放位图
    */

    pdds = CreateOffScreenSurface(pdd, bm.bmWidth, bm.bmHeight);
    if (pdds) {
        DDCopyBitmap(pdds, hbm, bm.bmWidth, bm.bmHeight);
    }

    DeleteObject(hbm);

    return pdds;
}

/*
 * 创建一个指定大小的页面,如果有足够显存该页面将在显存当中,否则在系统内存中
 */
IDirectDrawSurface * CreateOffScreenSurface(IDirectDraw *pdd, int dx, int dy)
{
    DDSURFACEDESC ddsd;
    IDirectDrawSurface *pdds;

    //
    // 为位图创建一个位图页面
    //
    ZeroMemory(&ddsd, sizeof(ddsd));
    ddsd.dwSize = sizeof(ddsd);
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = dx;
    ddsd.dwHeight = dy;

    if (pdd->CreateSurface(&ddsd, &pdds, NULL) != DD_OK)
    {
        return NULL;
    } else {
        return pdds;
    }
}

/* 该函数将位图信息载入到页面中。注意我们获得页面的一个GDI风格的设备上下文,
 * 用它我们可以把位图BitBlt到页面当中
 */
HRESULT DDCopyBitmap(IDirectDrawSurface *pdds, HBITMAP hbm, int dx, int dy)
{
    HDC hdcImage;
    HDC hdc;
    HRESULT hr;
    HBITMAP hbmOld;

    //
    // 选择一个位图到DC中
    //
    hdcImage = CreateCompatibleDC(NULL);
    hbmOld = (HBITMAP)SelectObject(hdcImage, hbm);

    if ((hr = pdds->GetDC(&hdc)) == DD_OK)
    {
        BitBlt(hdc, 0, 0, dx, dy, hdcImage, 0, 0, SRCCOPY);
        pdds->ReleaseDC(hdc);
    }

    SelectObject(hdcImage, hbmOld);
    DeleteDC(hdcImage);

    return hr;
}

  到现在,我们有了已载入位图的页面。我们将在稍后用它来交换到屏幕上。

5.加载调色板

  我们需要为主页面设置调色板。意味着所有为游戏创建的位图都必须使用相同的调色板。这儿有一个函数为指定位图创建一个色板。
/*
 * 在磁盘上为指定位图创建一个DD调色板,
 * 参数szBitmap是位图的文字
 */
IDirectDrawPalette * DDLoadPalette(IDirectDraw *pdd, LPCSTR szBitmap)
{
    IDirectDrawPalette* ddpal;
    int i;
    int n;
    int fh;
    PALETTEENTRY ape[256];

    if (szBitmap && (fh = _lopen(szBitmap, OF_READ)) != -1)
    {
        BITMAPFILEHEADER bf;
        BITMAPINFOHEADER bi;

        _lread(fh, &bf, sizeof(bf));
        _lread(fh, &bi, sizeof(bi));
        _lread(fh, ape, sizeof(ape));
        _lclose(fh);

        if (bi.biSize != sizeof(BITMAPINFOHEADER))
            n = 0;
        else if (bi.biBitCount > 8)
            n = 0;
        else if (bi.biClrUsed == 0)
            n = 1 << bi.biBitCount;
        else
            n = bi.biClrUsed;

      //
      // 一个DIB颜色表存储的方式是BGR,并非RGB,因些需要交换回来
      //
        for(i=0; i<n; i++ )
        {
            BYTE r = ape[i].peRed;
            ape[i].peRed = ape[i].peBlue;
            ape[i].peBlue = r;
        }
    }
    if (pdd->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL) != DD_OK)
    {
        return NULL;
    } else {
        return ddpal;
    }
}

   下一步,我们为主页面设置调色板:
lpDDPal = DDLoadPalette(lpDD, szBitmap); // Call the function above to load the palette

if (lpDDPal)
    lpDDSPrimary->SetPalette(lpDDPal);  // this sets the palette for the primary surface

6. 关键色

   DD利用关键色能达到透明的效果。你可以让你位图的一部份透明,当你将一个精灵交换到后页面的时候这是项很有用的技术。关键色是指定调色板中一个范围内的颜色,而页面交换时源或目的位图中这些颜色将不于拷贝。具有代表性的就是你可以在源位图中设定调色板中第0或第255个颜色为透明色,然后构造这样一幅位图:在你需要透明的地方用调色板中的这种颜色。
// 为位图设置关键色.
//
// Whatever color is at entry 255 in the palette will be the
// transparent color for blits. This color is black unless
// you used the DDPCAPS_ALLOW256 flag when the palette was
// created

DDCOLORKEY ddck;
ddck.dwColorSpaceLowValue = 0xff;
ddck.dwColorSpaceHighValue = 0xff;

// lpDDSSomeBitmapSurface is a DirectDraw surface with a
// bitmap loaded into it. This needs to be done for each
// surface containing a bitmap.
lpDDSSomeBitmapSurface->SetColorKey( DDCKEY_SRCBLT, &ddck );

7.场景组织

  现在我们已开始我们的游戏, 模拟或其它基于图像的程序。最简单的方式就是拷贝一幅位图到后页面,然后放一些精灵(其实也是位图)到后页面适当的位置,然后交换页面。如此重复上面步骤,调整精灵到新的位置。

// 该方法在空闲时间里应当重复被调用,放在你的PeekMessage()(消息循环)里吧。


void updateFrame( void )
{
    RECT rcRect;
    HRESULT ddrval;
    int xpos, ypos;

    // Blit the stuff for the next frame
    SetRect(&rcRect, 0, 0, 640, 480);

    // blit the background bitmap. This is a 640x480 bitmap,
    // so it will fill the screen (remember, we set the video
    // mode to 640x480x8.
    // The parameter lpDDSOne is a hypothetical surface with this
    // background bitmap loaded. LpDDSBack is our backbuffer.
    ddrval = lpDDSBack->BltFast( 0, 0, lpDDSOne, &rcRect,
    DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

    if( ddrval != DD_OK )
    {
        return;
    }
    SetRect(&rcRect, 0, 0, 32, 32); // our fictitious sprite bitmap is 32x32 in size

    // xpos and ypos represent some appropriate location for the sprite.
    // lpDDSMySprite is just a surface with the bitmap for our sprite.
    ddrval = lpDDSBack->BltFast( xpos, ypos, lpDDSMySprite,
    &rcRect, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT );
    if( ddrval != DD_OK )
    {
        return;
    }

    // Flip the surfaces
    ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT );
} /* updateFrame */

   这儿有一些注意事项。在BltFast(), Flip()方法的最后一个都有DDXX_WAIT标志。由于这些方法是同步的,所以这是必要的。也就是说,只要它们成功调用(即使没有完成)或是由于错误导致没有成功调用都会立即得到返回值。最常见的错误返回就是指示当前页面被上次操作影响而繁忙。因此你可以设置DDXX_WAIT标志,告诉DD尝试去操作页面,或者至到发生其它的什么错误。
  大家注意,第一个BltFast(),也就是填充后缓冲时用到的那个方法,它用了DDBLTFAST_NOCOLORKEY标志。目的在忽略关键色,在某些显卡上这样可以提高执行效率。 这样一来,该位图上任何透明区域都会被拷贝,因此没有任何透明。这样做是可行的,因为背景位图并不是精灵图像。
  当调用Flip()的时候,在显卡上分配的页面将会交换。意味着曾经的主页面现在变为后缓冲,并且如此循环。而对于你, 你只需要利用后缓冲的指针,并在后缓冲上绘制场景,然后调用Flip(),你并不用担心去追踪页面的前后关系。

8.释放清理工作

  要释放DX,最重要的就是针对你创建的任何一个DX对象,调用它的Release()成员方法。你可以像下面一样做:
/*
 * 针对我们创建的每一个对象调用Release释放之
 */
void finiObjects( void )
{
    if( lpDD != NULL )
    {
        if( lpDDSPrimary != NULL )
        {
            lpDDSPrimary->Release();
            lpDDSPrimary = NULL;
        }

        if( lpDDSOne != NULL )
        {
            lpDDSOne->Release();
            lpDDSOne = NULL;
        }

        if( lpDDPal != NULL )
        {
            lpDDPal->Release();
            lpDDPal = NULL;
        }
    }
    lpDD->Release();
    lpDD = NULL;
} /* finiObjects */

  当你对实际存在的DD对象(实例化了的LPDIRECTDRAW)调用Release方法后,它的引用计数将变为0,与此同时该对象将被销毁,屏幕返回最开始保留的状态。

一些细节

1.DD的调试

  当一个DD应用程序以全屏模式运行的时候,GDI是不可用的。也就是说你不能在你程序中设置断点,并指望看到在断点处看到debugger. 如果支持双显示器,或远程调试,就可以解决刚才全屏调式的问题。但是最简单的方法是依照硬件环境,让你的应用程序运行于窗口模式下。

2.运行窗口模式

  如果你想用窗口方式运行,你必须用不同的方式初始化。由于你想与GDI共存,所以你不能改变显示模式。你需要想到的最主要的就是:当你创建一个主页面的时候,你就得到了一个指向显存中当前可见区域的指针,而此刻它也正被GDI所使用。如果你想的话,你可以在屏幕的任意地方乱画东西。因此你要注意你所画的东西应该在窗口的范围内。DD中有裁剪器, 有了它你可以轻松的按照你的方式裁剪。

  下面这个方法初始化DD为运行窗口模式, 并没有用到页交换。
bool IsWindowed = false;

// function to initialize DirectDraw in windowed mode
bool DDGameInitWindowed(THIS_ HWND hwnd)
{
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;
    HRESULT ddrval;

    IsWindowed = true;

   /*
    * create the main DirectDraw object
    */
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK )
    {
        return(false);
    }

    // using DDSCL_NORMAL means we will coexist with GDI
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_NORMAL );
    if( ddrval != DD_OK )
    {
        lpDD->Release();
        return(false);
    }

    memset( &ddsd, 0, sizeof(ddsd) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    // The primary surface is not a page flipping surface this time
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
        lpDD->Release();
        return(false);
    }

    // Create a clipper to ensure that our drawing stays inside our window
    ddrval = lpDD->CreateClipper( 0, &lpClipper, NULL );
    if( ddrval != DD_OK )
    {
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }

    // setting it to our hwnd gives the clipper the coordinates from our window
    ddrval = lpClipper->SetHWnd( 0, hwnd );
    if( ddrval != DD_OK )
    {
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }

    // attach the clipper to the primary surface
    ddrval = lpDDSPrimary->SetClipper( lpClipper );
    if( ddrval != DD_OK )
    {
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }

    memset( &ddsd, 0, sizeof(ddsd) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = 640;
    ddsd.dwHeight = 480;

    // create the backbuffer separately
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSBack, NULL );
    if( ddrval != DD_OK )
    {
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }
    return(true);
}

   与前面代码初始化DD第一个不同之处在于SetCooperativeLevel()用的是ddscl_normal这个标志。也就是说我们并没有采用独占模式的显示方式,因此并没有移除GDI功能。当我们创建主页面的时候, 我们并没有创建复杂页面, 因为我没有没准备页面交换。

   第二个不同之处在于我们为窗口创建了一个DD裁剪器,并把它与主页面建立联系。DD裁剪器是一个用来维护一系列用于裁剪的矩形的对象。我们用窗口句柄传递给裁剪器,暗示裁剪器利用窗口的大小作为裁剪区域。然后我们把它与我们主页面建立联系,告诉主页面区域拷贝的时候将按照窗口区域进行裁剪。
   我们创建了一个独立的后台缓冲, 这次我们必须指定其大小。
  
   现在我们继续走完窗口模式的过程。最后一个不同之处在于我们并没有调用Flip(),取而代之的是区域拷贝。你可以创建一个你自己的Flip,就像下面一样:
bool MyFlip()
{
    HRESULT ddrval;
    RECT rcRectSrc;
    RECT rcRectDest;
    POINT p;

    // if we're windowed do the blit, else just Flip
    if (IsWindowed)
    {
        // first we need to figure out where on the primary surface our window lives
        p.x = 0; p.y = 0;
        ClientToScreen(ddWnd, &p);
        GetClientRect(ddWnd, &rcRectDest);
        OffsetRect(&rcRectDest, p.x, p.y);
        SetRect(&rcRectSrc, 0, 0, 640, 480);
        ddrval = lpDDSPrimary->Blt( &rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
    } else {
        ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT);
    }

    return (ddrval == DD_OK);
}

3. 页面丢失

  上面的代码有忽略了一个非常重要的情况:当用户改变一个全屏模式的DD应用程序到一个其它的应用程序时会发生什么。首先,你的代码需要处理WM_ACTIVATEAPP消息,并设定一个标志预示你程序为非活动斗状态。在你的消息循环中检测该标志,如果为非活动状态时候,不要试着去更新你的显示内容。
  但是最主要的问题是你切换到另处的程序时,你已经离开了DD环境,并充许GDI变得可用。GDI使DD变得失去控制能力,因此它会重写被DD页面所占用的显存。也就是说你页面上的位图信息被擦出,需要重新载入。幸运的是,当你调用方法操作页面的时候,例如Flip()或BltFast(),DD将会返回DDERR_SURFACELOST的错误让你知道是否已发生页面丢失。因此, 你的代码当中要检测这个错误,如果错误发生要重新载入位图信息。

ddrval = lpDDSPrimary->Blt( &rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
}

ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT);

if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
}
:
:

void restoreAll()
{
    // for each surface your app has created you should do the following:
    ddrval = lpMyDDSurface->Restore(); // this reattaches the video memory to the surface
    if( ddrval == DD_OK )
    {
        lpTempDDS ->DDReLoadBitmap(); // this will be the same as the function above
                                         // that was originally used to load the bitmap
                                         // with the exception that the surface is already
                                         // created and ready to go
    }
}

4. DD深入学习

   我推荐的最好的资料是DX SDK中的示例代码。 那些示例中值得一读,因为里面包含了许多本教程所忽略的细节。我找到了一本关于DX API高级展示的书。 MSJ 曾发表了几篇介绍DD与DS的优秀文章,但都是很基础的东西。

你可能感兴趣的:(DirectDraw 编程指南)