DirectDraw高彩模式编程入门

 目 录
  1 图形编程基本常识
  2 DirectDraw的简单概念
  3 DirectDraw的初始化代码
  4 高彩下的画点函数


--------------------------------------------------------------------------------

  本来是应该从256色模式下的编程谈起的,但现在大家写游戏都喜欢用高彩模式,那我就顺因大势所趋,直接讲讲高彩模式下的编程吧。

1、图形编程基本常识
  如果你有DOS时代在13H模式下的编程经验,你就肯定知道,要想在屏幕上显示图象则必须要同显示缓冲区打交道。而且在具体开始编程时,还必须知道显示缓冲区的地址和表示格式。对于初学者来说,可以简单地把显示缓冲区看作一块内存,往这块内存里填数据就能在屏幕上的对应位置显示出该数据表示的像素来。
  为了便于理解,就先拿DOS下最简单的13H模式来举个例子。在这个模式下,显示缓冲区的起始地址是固定的(a000:0000H),而且整个显示缓冲区地址是连续线性排列的,每个字节用来表示一个像素。比如你要在屏幕(以屏幕 左上角为坐标原点,像素为单位)坐标[2,3]处画一个颜色号为138的像素,已知屏幕宽度是320,用公式Addr=y*320+x可以计算出地址,得到Addr=3*320+2=962(03c2H),因此在a000:03c2H处写一个字节138(8aH)就能达到目的了。

2、DirectDraw的简单概念
  在Windows系统下,由于是处于保护模式状态,显示缓冲区的起始地址并不是固定的,我们将通过DirectDraw来得到显示缓冲区的起始地址。为了更容易地讲清楚这个问题,我们先来侃侃DirectDraw的一些基本概念和结构。
  微软是按COM来设计DirectX的,每个DirectDraw对象代表一个显示设备,它提供了一些子对象和相应的各种界面来完成具体功能。
  DirectDrawSurface对象可以看成是一块线性的显存,每个表面(Surface)对象提供了对应起始地址的指针,我们的图像数据就是往表面上进行读写的。表面又分了几种,直接在屏幕上见到的那个是主表面(Primary Surface),还有不能直接看到的离屏表面(Off-screen Surface)及覆盖表面(Overlay Surface),如果显存不够,这种不能直接看到的Surface可以在内存中创建,不过由于不能利用硬件的加速能力,性能要稍微受些影响。
  DirectDrawPalette对象是为256色准备的,对高彩模式编程时完全用不上,因此在这里就暂且不去管它了。
  另外,由于窗口裁剪非常容易编程实现,那个用来对窗口进行裁剪时使用的DirectDrawClipper对象,在这里也不去多谈它了。知道了DirectDrawSurface对象以后,下面该说说DirectDraw的一般工作流程和双缓冲显示原理了。

创建一个DirectDraw对象
设置协作模式
设置显示模式
创建主表面和一个后台表面
往后台表面写入内容
通过Flip(页面翻转)交换主表面和后台表面,从而显示出写入后台表面的内容
  在这个工作流程中,最后两步完成了显示一帧的工作,以每秒n次的频率反复进行。
  这种创建两个表面,在隐藏的表面中先画好内容,再通过Flip显示出来的方法称为双缓冲。如果不这样做,直接画到可见的主表面上面,而省去了后台表面及Flip过程,你会发现屏幕上会有明显的闪烁,这就是为什么要采用双缓冲的原因。
  这么简单说说,可能是无法把DirectDraw编程讲得很清楚的,还是来看看实际的DirectDraw的初始化代码吧。

3、DirectDraw的初始化代码

  #define COPYRIGHT "Fan YiPeng"
  #define HOMEPAGE "http://gamevision.yeah.net"

  #define TITLE "EXAMPLE"
  #define NAME "DDRAW EXAMPLE"

  #include <windows.h>
  #include <ddraw.h>

  // -------------------------------------

  #define RELEASE(x) if (x != NULL) { x -> Release(); x = NULL; }

  // -------------------------------------

  HWND hWndCopy;
  BOOL bActive;

  LPDIRECTDRAW lpDD = NULL; // DirectDraw object
  LPDIRECTDRAWSURFACE lpDDSPrimary = NULL; // DirectDraw primary surface
  LPDIRECTDRAWSURFACE lpDDSBack = NULL; // DirectDraw back surface

  int ScreenW = 640, ScreenH = 480, BitDepth = 16; // 屏幕模式参数
  BOOL HighColor555Flag; // 555模式标志

  // 初始化DDraw
  BOOL InitDDraw(void)
  {
    DDSURFACEDESC ddsd;
    DDSCAPS ddscaps;
    DDPIXELFORMAT ddpf;
    HRESULT ddrval;

    // 创建DirectDraw对象
    ddrval = DirectDrawCreate(NULL, &lpDD, NULL);
    if(ddrval != DD_OK)
    {
      return FALSE;
    }

    // 设置为独占模式
    ddrval = lpDD->SetCooperativeLevel(hWndCopy, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);

    if(ddrval != DD_OK)
    {
      return FALSE;
     }

    // 设置显示模式
    ddrval = lpDD->SetDisplayMode(ScreenW, ScreenH, BitDepth);
    if(ddrval != DD_OK)
    {
      return FALSE;
    }

    // 创建带一个back buffer的primary surface
    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)
    {
      // Create the primary surface failed
      return FALSE;
    }

    ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
    ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);
    if(ddrval != DD_OK)
    {
      // Create the back surface failed
      return FALSE;
    }

    // 取得像素格式
    ddpf.dwSize = sizeof(ddpf);
    ddrval = lpDDSPrimary->GetPixelFormat(&ddpf);
    if(ddrval != DD_OK)
    {
      return FALSE;
    }

    WORD GBitMask = (WORD)ddpf.dwGBitMask;

    // 分析像素格式
    if(GBitMask == 0x03E0)
    {
      HighColor555Flag = TRUE;
    }
    else
    if(GBitMask == 0x07E0)
    {
      HighColor555Flag = FALSE;
    }
    else
    {
      // The Video Card doesn't support 5:5:5 or 5:6:5 HighColor
      return FALSE;
    }
 
    return TRUE;
  }
 
  // 恢复丢失的DDraw对象
  HRESULT RestoreDDraw(void)
  {
    HRESULT ddrval;

    if(lpDDSPrimary)
    {
      ddrval = lpDDSPrimary->Restore();
      if(ddrval==DD_OK)
      {
        if(lpDDSBack)
        {
          ddrval = lpDDSBack->Restore();
        }
      }
    }
    return ddrval;
  }

  // 释放DDraw对象
  void ReleaseObjects(void)
  {
    RELEASE(lpDDSBack);
    RELEASE(lpDDSPrimary);
    RELEASE(lpDD);
  }

  // 产生初始化失败消息窗口
  void InitFail(LPSTR msg)
  {
    ReleaseObjects();
    MessageBox(hWndCopy, msg, "ERROR", MB_OK);
    DestroyWindow(hWndCopy);
  }

  // 检查程序是否已经在运行
  BOOL AlreadyRun(void)
  {
    HWND FirsthWnd, FirstChildhWnd;

    if((FirsthWnd = FindWindow(NULL, TITLE)) != NULL)
    {
      FirstChildhWnd = GetLastActivePopup(FirsthWnd);
      BringWindowToTop(FirsthWnd);

      if(FirsthWnd != FirstChildhWnd)
      {
        BringWindowToTop(FirstChildhWnd);
      }

      ShowWindow(FirsthWnd, SW_SHOWNORMAL);
      return TRUE;
    }

   return FALSE;
  }

  // Windows消息处理
  long FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
    switch(message)
    {
      case WM_ACTIVATEAPP:
      bActive = wParam;
      break;

    case WM_CREATE:
    break;

    case WM_SETCURSOR:
      SetCursor(NULL);
      return TRUE;

    case WM_KEYDOWN:
      switch(wParam)
      {
        case VK_ESCAPE: // ESC键退出
        DestroyWindow(hWnd);
        return 0;
      }
      break;

    case WM_DESTROY:
      ReleaseObjects();
      PostQuitMessage(0);
      break;
    }

    return DefWindowProc(hWnd, message, wParam, lParam);
  }

  // 初始化
  BOOL doInit(HINSTANCE hInstance, int nCmdShow)
  {
    HWND hwnd;
    WNDCLASS wc;

    // set up and register window class
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH )GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NAME;
    wc.lpszClassName = NAME;
    RegisterClass(&wc);

    // create a window
    hwnd = CreateWindowEx(WS_EX_TOPMOST,
               NAME,
               TITLE,
               WS_POPUP,
               0,
               0,
               GetSystemMetrics(SM_CXSCREEN),
               GetSystemMetrics(SM_CYSCREEN),
               NULL,
               NULL,
               hInstance,
               NULL);

    if(!hwnd) return FALSE;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    hWndCopy = hwnd;

    if(!InitDDraw()) // Init DDraw Object
    {
      InitFail("Init DirectDraw Fail");
    }
    return TRUE;
   }

  // WinMain函数
  int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  {
    MSG msg;
    HRESULT ddrval;

    if(!doInit(hInstance,nCmdShow))
    return FALSE;

    // main loop
    while(TRUE)
    {
      if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
      {
        // 消息队列处理
        if (!GetMessage(&msg, NULL, 0, 0)) return msg.wParam;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
      else
      {
        if(bActive)
        {
          // 以后在这里加入实际的图形代码
          // ...

          // Flip surface(页面翻转)
          while(TRUE)
          {
            ddrval = lpDDSPrimary->Flip(NULL, 0);
            if(ddrval == DD_OK)
            {
              break;
            }
            if(ddrval == DDERR_SURFACELOST)
            {
              ddrval = RestoreDDraw();
              if(ddrval != DD_OK)
              {
                break;
              }
            }
            if(ddrval != DDERR_WASSTILLDRAWING)
            {
              break;
             }
          }
       }
       else
       {
         // 当程序未被激活时等待
         WaitMessage();
        }
      }
    }
  }

  “你不仔细解释,我怎么看得懂?”没办法,Windows下的DirectDraw编程概念和结构对初学者来说确实是不大好理解,我也没办法三言两语就阐述得很清楚,如果详细展开讲,也许你又觉得我的话又臭又长,倒不如就讲成现在这样,反正不会做饭并不代表你不会吃饭。你可以直接把这段代码拿去用,以后实践多了,慢慢就理解了。如果你要想对这个问题做进一步的深入了解,可以去买本书来看看(好象现在没看到讲高彩模式的书卖?)或者啃啃Windows API、DirectX SDK的帮助文档,再不然就干脆提出你的问题,然后耐心地等待我在下一篇文字中作答(呵呵,我很懒哦)。另外,在上面的例子中,有几个地方要特别提醒一下。一个是代码最开始定义的“ScreenW = 640,ScreenH = 480”这两个变量给出了屏幕的大小,你可以根据需要进行更改。另一个是标明“在这里加入实际代码”的那个地方,我们马上将根据需要在那里加入实际的图形代码。

4、高彩模式下的画点函数
  阿基米德有句名言,“给我一个支点,我就能举起整个地球。”
  图形程序员则常说,“给我一个画点函数,我就能创造一切。”
  是的,只要掌握了画点的方法,你就能很快投入到其它更复杂的图形世界中去。已经做好学会它的思想准备了吗?那就开始吧……
  通过前面对图形编程基本常识的介绍,我们已经知道了画点就是往显存里填数据。现在我们该开始讨论一些具体的细节了。
  首先我们必须要了解的是显存的结构,由于我们是通过DirectDraw来同显存打交道,因此实际上就是要了解表面对象的结构。下图描绘了表面的简单结构:


图1 表面的简单结构

  可以很明显地注意到,表面对象有两个宽度,一个是WIDTH,一个是PITCH。WIDTH就是创建表面时所给出的那个宽度,而PITCH是表面的实际宽度,是按字节算的。在许多显卡上,PITCH和WIDTH是相等的,比如在640x480的高彩模式下,PITCH为1280。而在某些显卡上,PITCH比WIDTH要大。比如在640x480的256色模式下,当WIDTH是640时,PITCH为1024而不是640,这些显卡这样做是为了更好地进行数据对齐来提高性能或达到其它目的。所以,我们在实际编程时,为了保证程序的兼容性,必须按PITCH处理。
  在高彩模式下,我们用一个字(WORD)来表示一个像素。但由于显卡的差异,通常会有555和565两种情况。555是指颜色表示的RGB分量各占5位,如图:

N R R R R R G G G G G B B B B B

图2 555的RGB分量

  565是指颜色表示的RB分量各占5位,G分量占6位,如图:

R R R R R G G G G G G B B B B B

图3 565的RGB分量

  这种555和565的差异为我们编程带来了些麻烦。一般可以采用以下两种方法中的一种来解决这个问题:

提供两套函数,一套针对555,一套针对565,然后根据显卡的具体情况调用对应的那套函数。

只提供一套函数,比如针对555,然后根据显卡的具体情况,如果是555的则不做任何特别处理,否则如果是565的,则在最后Flip表面之前,做一次替换,把全部像素转换成565格式即可。
  前一种方法执行速度比较快,但由于要写两套代码,工作量也比较大。
  后一种方法的代码量要少一些,但在处于最坏情况下时,每次刷新都要做一次替换,速度方面稍微受点影响。
  因为我的显卡是565的,为了自己方便,下面就以565为例,给出一段具体的画点例程。如果你的显卡是555的,就请你在读懂这段代码的基础上稍微做些改动就行了。(周围突然传来群众愤怒的吼声,“不许偷懒”,于是我默然屈服了,“那就555和565都提供吧”……)

  {
    DDSURFACEDESC ddsd;
    LPWORD lpSurface;
    long Pitch;

    ddsd.dwSize = sizeof(ddsd);

    // 锁定Back Surface,DDraw中的Surface必须先锁定,然后才能访问
    while((ddrval=lpDDSBack->Lock(NULL, &ddsd, 0, NULL))==DDERR_WASSTILLDRAWING);
    if(ddrval == DD_OK)
    {
      // 起始地址指针,注意其类型是指向WORD的指针
      lpSurface = (LPWORD)ddsd.lpSurface;

      // 我们关心的Pitch,其值是字节(BYTE)数
      Pitch = ddsd.lPitch;
      Pitch >>= 1; // 为了方便后面的计算,把它换算成字(WORD)数

      // 作了以上处理以后,像素坐标到地址的换算公式为
      // (LPWORD)addr = lpSurface + y*Pitch + x;

      // 画满屏的黑点
      {
         int x, y;

         for(y=0; y<ScreenH; y++)
         for(x=0; x<ScreenW; x++)
         {
           *(lpSurface + y*Pitch + x)=0;
         }
      }

      // 在第10行画一条白线
      {
         int x, y;

         y=10;
         for(x=0; x<ScreenW; x++)
         {
           *(lpSurface + y*Pitch + x)=0xFFFF; // 0x7FFF (555)
           // 0xFFFF=11111 111111 11111 0x7FFF=0 11111 11111 11111
         }
      }

      // 在[100,200]处画一个红点
      *(lpSurface + 200*Pitch + 100)=0xF800; // 0x7C00 (555)
      // 0xF800=11111 000000 00000 0x7FFF=0 11111 00000 00000

      // 在[200,300]处画一个绿点
      *(lpSurface + 300*Pitch + 200)=0x07E0; // 0x03E0 (555)
      // 0x07E0=00000 111111 00000 0x03E0=0 00000 11111 00000

      // 在[300,400]处画一个蓝点
      *(lpSurface + 400*Pitch + 300)=0x001F; // 0x001F (555)
      // 0x001F=00000 000000 11111 0x001F=0 00000 00000 11111

      // 对Back Surface解锁,Surface访问后必须要解锁
      lpDDSBack->Unlock(NULL);
    }
  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值