织梦CMS - 轻松建站从此开始!

罗索

DirectDraw高彩模式编程入门

落鹤生 发布于 2010-05-05 15:34 点击:次 
本来是应该从256色模式下的编程谈起的,但现在大家写游戏都喜欢用高彩模式,那我就顺因大势所趋,直接讲讲高彩模式下的编程吧。
TAG:

 目 录
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的初始化代码

  1. #define COPYRIGHT "Fan YiPeng" 
  2. #define HOMEPAGE "http://bbs.rosoo.net" 
  3.  
  4. #define TITLE "EXAMPLE" 
  5. #define NAME "DDRAW EXAMPLE" 
  6.  
  7. #include <windows.h> 
  8. #include <ddraw.h> 
  9.  
  10. #define RELEASE(x) if (x != NULL) { x -> Release(); x = NULL; } 
  11.  
  12. HWND hWndCopy; 
  13. BOOL bActive; 
  14.  
  15. LPDIRECTDRAW lpDD = NULL; // DirectDraw object 
  16. LPDIRECTDRAWSURFACE lpDDSPrimary = NULL; // DirectDraw primary surface 
  17. LPDIRECTDRAWSURFACE lpDDSBack = NULL; // DirectDraw back surface 
  18.  
  19. int ScreenW = 640, ScreenH = 480, BitDepth = 16; // 屏幕模式参数 
  20. BOOL HighColor555Flag; // 555模式标志 
  21.  
  22. // 初始化DDraw 
  23.   BOOL InitDDraw(void
  24.   { 
  25.     DDSURFACEDESC ddsd; 
  26.     DDSCAPS ddscaps; 
  27.     DDPIXELFORMAT ddpf; 
  28.     HRESULT ddrval; 
  29.  
  30.     // 创建DirectDraw对象 
  31.     ddrval = DirectDrawCreate(NULL, &lpDD, NULL); 
  32.     if(ddrval != DD_OK) 
  33.     { 
  34.       return FALSE; 
  35.     } 
  36.  
  37.     // 设置为独占模式 
  38.     ddrval = lpDD->SetCooperativeLevel(hWndCopy, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN); 
  39.  
  40.     if(ddrval != DD_OK) 
  41.     { 
  42.       return FALSE; 
  43.      } 
  44.  
  45.     // 设置显示模式 
  46.     ddrval = lpDD->SetDisplayMode(ScreenW, ScreenH, BitDepth); 
  47.     if(ddrval != DD_OK) 
  48.     { 
  49.       return FALSE; 
  50.     } 
  51.  
  52.     // 创建带一个back buffer的primary surface 
  53.     ddsd.dwSize = sizeof(ddsd); 
  54.     ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; 
  55.      ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX; 
  56.     ddsd.dwBackBufferCount = 1; 
  57.  
  58.     ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL); 
  59.  
  60.      if(ddrval != DD_OK) 
  61.     { 
  62.       // Create the primary surface failed 
  63.       return FALSE; 
  64.     } 
  65.  
  66.     ddscaps.dwCaps = DDSCAPS_BACKBUFFER; 
  67.     ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack); 
  68.     if(ddrval != DD_OK) 
  69.     { 
  70.       // Create the back surface failed 
  71.       return FALSE; 
  72.     } 
  73.  
  74.     // 取得像素格式 
  75.      ddpf.dwSize = sizeof(ddpf); 
  76.     ddrval = lpDDSPrimary->GetPixelFormat(&ddpf); 
  77.     if(ddrval != DD_OK) 
  78.     { 
  79.       return FALSE; 
  80.     } 
  81.  
  82.     WORD GBitMask = (WORD)ddpf.dwGBitMask; 
  83.  
  84.     // 分析像素格式 
  85.     if(GBitMask == 0x03E0) 
  86.     { 
  87.       HighColor555Flag = TRUE; 
  88.     } 
  89.     else 
  90.     if(GBitMask == 0x07E0) 
  91.     { 
  92.       HighColor555Flag = FALSE; 
  93.     } 
  94.     else 
  95.     { 
  96.       // The Video Card doesn't support 5:5:5 or 5:6:5 HighColor 
  97.       return FALSE; 
  98.     } 
  99.   
  100.      return TRUE; 
  101.   } 
  102.   
  103.   // 恢复丢失的DDraw对象 
  104.   HRESULT RestoreDDraw(void
  105.   { 
  106.     HRESULT ddrval; 
  107.  
  108.      if(lpDDSPrimary) 
  109.     { 
  110.       ddrval = lpDDSPrimary->Restore(); 
  111.       if(ddrval==DD_OK) 
  112.       { 
  113.         if(lpDDSBack) 
  114.         { 
  115.           ddrval = lpDDSBack->Restore(); 
  116.         } 
  117.       } 
  118.     } 
  119.     return ddrval; 
  120.   } 
  121.  
  122.   // 释放DDraw对象 
  123.   void ReleaseObjects(void
  124.   { 
  125.     RELEASE(lpDDSBack); 
  126.      RELEASE(lpDDSPrimary); 
  127.     RELEASE(lpDD); 
  128.   } 
  129.  
  130.   // 产生初始化失败消息窗口 
  131.   void InitFail(LPSTR msg) 
  132.   { 
  133.      ReleaseObjects(); 
  134.     MessageBox(hWndCopy, msg, "ERROR", MB_OK); 
  135.     DestroyWindow(hWndCopy); 
  136.   } 
  137.  
  138.   // 检查程序是否已经在运行 
  139.   BOOL AlreadyRun(void
  140.   { 
  141.     HWND FirsthWnd, FirstChildhWnd; 
  142.  
  143.     if((FirsthWnd = FindWindow(NULL, TITLE)) != NULL) 
  144.     { 
  145.        FirstChildhWnd = GetLastActivePopup(FirsthWnd); 
  146.        BringWindowToTop(FirsthWnd); 
  147.  
  148.       if(FirsthWnd != FirstChildhWnd) 
  149.       { 
  150.         BringWindowToTop(FirstChildhWnd); 
  151.       } 
  152.  
  153.       ShowWindow(FirsthWnd, SW_SHOWNORMAL); 
  154.        return TRUE; 
  155.     } 
  156.  
  157.    return FALSE; 
  158.   } 
  159.  
  160.   // Windows消息处理 
  161.   long FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
  162.   { 
  163.     switch(message) 
  164.     { 
  165.       case WM_ACTIVATEAPP: 
  166.       bActive = wParam; 
  167.       break
  168.  
  169.     case WM_CREATE: 
  170.     break
  171.  
  172.     case WM_SETCURSOR: 
  173.        SetCursor(NULL); 
  174.       return TRUE; 
  175.  
  176.     case WM_KEYDOWN: 
  177.       switch(wParam) 
  178.       { 
  179.         case VK_ESCAPE: // ESC键退出 
  180.         DestroyWindow(hWnd); 
  181.         return 0; 
  182.       } 
  183.        break
  184.  
  185.     case WM_DESTROY: 
  186.       ReleaseObjects(); 
  187.        PostQuitMessage(0); 
  188.       break
  189.     } 
  190.  
  191.     return DefWindowProc(hWnd, message, wParam, lParam); 
  192.   } 
  193.  
  194.   // 初始化 
  195.   BOOL doInit(HINSTANCE hInstance, int nCmdShow) 
  196.   { 
  197.     HWND hwnd; 
  198.     WNDCLASS wc; 
  199.  
  200.     // set up and register window class 
  201.      wc.style = CS_HREDRAW | CS_VREDRAW; 
  202.     wc.lpfnWndProc = WindowProc; 
  203.     wc.cbClsExtra = 0; 
  204.     wc.cbWndExtra = 0; 
  205.     wc.hInstance = hInstance; 
  206.     wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION); 
  207.     wc.hCursor = LoadCursor(NULL, IDC_ARROW); 
  208.     wc.hbrBackground = (HBRUSH )GetStockObject(BLACK_BRUSH); 
  209.     wc.lpszMenuName = NAME; 
  210.     wc.lpszClassName = NAME; 
  211.     RegisterClass(&wc); 
  212.  
  213.     // create a window 
  214.     hwnd = CreateWindowEx(WS_EX_TOPMOST, 
  215.                NAME, 
  216.                TITLE, 
  217.                WS_POPUP, 
  218.                0, 
  219.                0, 
  220.                 GetSystemMetrics(SM_CXSCREEN), 
  221.                 GetSystemMetrics(SM_CYSCREEN), 
  222.                NULL, 
  223.                NULL, 
  224.                hInstance, 
  225.                NULL); 
  226.  
  227.      if(!hwnd) return FALSE; 
  228.  
  229.     ShowWindow(hwnd, nCmdShow); 
  230.      UpdateWindow(hwnd); 
  231.  
  232.     hWndCopy = hwnd; 
  233.  
  234.      if(!InitDDraw()) // Init DDraw Object 
  235.     { 
  236.       InitFail("Init DirectDraw Fail"); 
  237.     } 
  238.     return TRUE; 
  239.    } 
  240.  
  241.   // WinMain函数 
  242.   int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 
  243.   { 
  244.     MSG msg; 
  245.     HRESULT ddrval; 
  246.  
  247.     if(!doInit(hInstance,nCmdShow)) 
  248.      return FALSE; 
  249.  
  250.     // main loop 
  251.     while(TRUE) 
  252.     { 
  253.       if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) 
  254.       { 
  255.         // 消息队列处理 
  256.         if (!GetMessage(&msg, NULL, 0, 0)) return msg.wParam; 
  257.         TranslateMessage(&msg); 
  258.          DispatchMessage(&msg); 
  259.       } 
  260.       else 
  261.       { 
  262.         if(bActive) 
  263.         { 
  264.           // 以后在这里加入实际的图形代码 
  265.            // ... 
  266.  
  267.           // Flip surface(页面翻转) 
  268.           while(TRUE) 
  269.           { 
  270.             ddrval = lpDDSPrimary->Flip(NULL, 0); 
  271.             if(ddrval == DD_OK) 
  272.             { 
  273.               break
  274.             } 
  275.             if(ddrval == DDERR_SURFACELOST) 
  276.             { 
  277.               ddrval = RestoreDDraw(); 
  278.                if(ddrval != DD_OK) 
  279.               { 
  280.                 break
  281.               } 
  282.             } 
  283.             if(ddrval != DDERR_WASSTILLDRAWING) 
  284.             { 
  285.               break
  286.              } 
  287.           } 
  288.        } 
  289.        else 
  290.        { 
  291.          // 当程序未被激活时等待 
  292.          WaitMessage(); 
  293.         } 
  294.       } 
  295.     } 
  296.   } 

  “你不仔细解释,我怎么看得懂?”没办法,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都提供吧”……)

  1.   DDSURFACEDESC ddsd; 
  2.   LPWORD lpSurface; 
  3.   long Pitch; 
  4.  
  5.   ddsd.dwSize = sizeof(ddsd); 
  6.  
  7.   // 锁定Back Surface,DDraw中的Surface必须先锁定,然后才能访问 
  8.    while((ddrval=lpDDSBack->Lock(NULL, &ddsd, 0, NULL))==DDERR_WASSTILLDRAWING); 
  9.   if(ddrval == DD_OK) 
  10.   { 
  11.     // 起始地址指针,注意其类型是指向WORD的指针 
  12.     lpSurface = (LPWORD)ddsd.lpSurface; 
  13.  
  14.     // 我们关心的Pitch,其值是字节(BYTE)数 
  15.     Pitch = ddsd.lPitch; 
  16.     Pitch >>= 1; // 为了方便后面的计算,把它换算成字(WORD)数 
  17.  
  18.     // 作了以上处理以后,像素坐标到地址的换算公式为 
  19.      // (LPWORD)addr = lpSurface + y*Pitch + x; 
  20.  
  21.     // 画满屏的黑点 
  22.     { 
  23.        int x, y; 
  24.  
  25.        for(y=0; y<ScreenH; y++) 
  26.        for(x=0; x<ScreenW; x++) 
  27.        { 
  28.           *(lpSurface + y*Pitch + x)=0; 
  29.        } 
  30.     } 
  31.  
  32.     // 在第10行画一条白线 
  33.     { 
  34.        int x, y; 
  35.  
  36.        y=10; 
  37.        for(x=0; x<ScreenW; x++) 
  38.        { 
  39.           *(lpSurface + y*Pitch + x)=0xFFFF; // 0x7FFF (555) 
  40.          // 0xFFFF=11111 111111 11111 0x7FFF=0 11111 11111 11111 
  41.        } 
  42.     } 
  43.  
  44.     // 在[100,200]处画一个红点 
  45.     *(lpSurface + 200*Pitch + 100)=0xF800; // 0x7C00 (555) 
  46.     // 0xF800=11111 000000 00000 0x7FFF=0 11111 00000 00000 
  47.  
  48.     // 在[200,300]处画一个绿点 
  49.      *(lpSurface + 300*Pitch + 200)=0x07E0; // 0x03E0 (555) 
  50.     // 0x07E0=00000 111111 00000 0x03E0=0 00000 11111 00000 
  51.  
  52.     // 在[300,400]处画一个蓝点 
  53.     *(lpSurface + 400*Pitch + 300)=0x001F; // 0x001F (555) 
  54.     // 0x001F=00000 000000 11111 0x001F=0 00000 00000 11111 
  55.  
  56.     // 对Back Surface解锁,Surface访问后必须要解锁 
  57.      lpDDSBack->Unlock(NULL); 
  58.   } 
(Fan YiPeng)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201005/9337.html]
本文出处:CSDN博客 作者:Fan YiPeng
顶一下
(1)
100%
踩一下
(0)
0%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容