利用DirectX实现Windows下直接写屏

                                                                        刘国安,galiu@21cn.com

 

记得当年在DOS下,因为计算机速度很慢,做显示时往往要跳过标准图形库,直接写显存。到了Windows以后,觉得一切都好起来了,图形的显示尤其方便了。

可是在有些情况下,比如图像编程、高速采集的数据显示,对图形的显示要求又提高了,而在Windows下对硬件的控制远没有DOS下那么直接,通过API函数中对GDI的控制,远远达不到系统的要求。经过一段时间的搜索,发现Microsoft提供的游戏编程引擎——DirectX可以实现在Windows下的直接写屏,高效、可靠,而且支持目前的大多数硬件平台。

1.       关于DirectX SDK版本

DirectX现在已经发展到了9.0,但是从8.0开始,对DirectDraw的支持开始淡化,而转而开始强调D3D。虽然在库函数和头文件中仍然支持DirectDraw,但在help中已经找不到太多相关的消息了。个人认为还是5.0 / 7.1中的信息比较全。本文中所有的例子均在DirectX 8.0 SDK下编写,但是是参考的MSDNDirectX 5.0的相关帮助;另外在DirectX 9.0 SDK下也测试通过。目前在Microsoft网站上可以下载的版本是9.0,也可以找到8.0 SDK,但5.0或是7.1 SDK却是很难再找到了。

2.       DirectX中的主要概念

a)         Surface:一般翻译成“表面”,指的是一些图形缓冲区,一般开在系统内存中或是显存中。显示的内容就放在这些Surface中,并可以进行绘图、拷贝等处理。Surface一般又分为三种:

                                       i.              Primary Surface,主表面,指目前显示的表面。

                                     ii.              BackBuffer Surface,后台缓冲表面,与主表面可以互相切换(通过flip)

                                    iii.              Offscreen Surface,离屏缓冲区,可以拷贝到另外两个表面。

 

 

b)        Bltting:指的是将图形进行复制。比如将Offscreen Surface的图像复制到Backbuffer Surface

 

 

c)        Color Key:在本文中未用到,指的是一个结构,可以将某范围内的颜色定义为一个Color Key,在Bltting时不参与拷贝。

3.       简单的DirectDraw程序

为了方便编程,这里没有用MFC的架构。其实用MFC也可以实现DirectX编程的,具体可以在网上查到这方面的资料。本文仅以SDK框架为例。

在附录1中可以找到本文相应的代码。

需要加以注意的地方:

a)         该程序中共有5个函数,调用顺序为:Winmain -> InitWindow -> InitDDraw -> WinProc -> FreeDDraw

                                       i.              WinMain:所有Win32应用程序的入口函数,它也是应用程序关闭时的出口,一个应用程序的全生命周期就是在它的控制之下。所以,确切的说,其它四个函数是被包括在WinMain之内的。消息循环也是在这个函数中启动。

                                     ii.              InitWindow:初始化和创建一个与程序的HINSTANCE(实例句柄)相关联的主窗口,这个窗口的HWND(窗口句柄)在初始化DirectDraw环境时需要用到。

                                    iii.              InitDDraw:初始化和创建DirectDraw对象,并执行一定的功能。它里面包括了创建DirectDraw对象,创建页面,设置显示模式,创建主页面,输出文字。

                                   iv.              WinProc:是应用程序感知外来动作和产生反应的神经中枢,相当于人的大脑。这是程序中最主要的部件之一,它和在WinMain中所启动的消息循环是一起工作的。

                                     v.              FreeDDraw:释放DirectDraw的各种对象,以使其不再占用内存空间。

b)        编译时要把ddraw.hddraw.lib (DirectX SDK相应目录下可以找到,如果还没装就先装一下)拷贝到当前目录下,并在VC中设置Link ddraw.lib。当然也可以通过环境设置设成包含ddraw.h的路径,但我觉得这样比较省力气。

 

c)        InitDDraw的过程:

                                       i.              首先调用DirectDrawCreate来创建一个DirectDraw对象。它是DirectDraw接口的核心,必须被创建。

                                     ii.              SetCooperativeLevel设置程序的控制级别。

                                    iii.              SetDisplayMode设置显示方式。本例中采用1024*768*32bit

                                   iv.              CreateSurface创建主表面

                                     v.              GetAttachedSurface取得主表面相应的后备缓冲面

                                   vi.              GetDC取得Device Context,从而调用TextOutput显示字符

d)        FreeDraw过程

                                       i.              调用SurfaceRelease成员函数释放相应资源

                                     ii.              调用DiretDrawRelease成员函数释放相应资源

4.       直接操作显存

a)         使用BackBuffer SurfaceLock成员函数,会取得一个Surface,其中的指针lpSurface就是指向后台缓存表面。对这个指针的内容进行修改,并进行Flip就会显示在屏幕上。

b)        该缓存与屏幕的对应关系与显示模式有关。当使用8bit色彩时,颜色由调色板决定;而使用16bit24bit时,与调色板无关,直接用2byte3byte代表颜色值。

                                       i.              16bit的颜色表示:#define RGB16(r,g,b) (((r)&0xf8)*256+((g)&0xfc)*8+(b)/8)

                                     ii.              24bit的颜色表示:可以直接使用RGB宏。

                                    iii.              8bit时调色板的使用:

void ChangePalette()
{
        PALETTEENTRY gray[256];
        LPDIRECTDRAWPALETTE DDPalette=NULL;
        int i;
        lpDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, gray, &DDPalette, NULL);
        for(i=0; i<256; i++)
        {
                    gray[i].peRed = i;
                    gray[i].peGreen = i;
                    gray[i].peBlue = i;
        }
        DDPalette->SetEntries(0, 0, 256, gray);
        lpDDSPrimary->SetPalette(DDPalette);
        lpDDSBackBuffer->SetPalette(DDPalette);
}

调色板可以想成是一个颜色索引。这样在程序中就可以用8bit的数字来引用颜色。

c)        该缓存是一个线性缓存,以16bit为例,每2byte代表一个pixel的颜色,依次排开。这样对每个点进行操作就很容易:

void SetPoint(int x, int y, unsigned int iColor)
{
    unsigned long iDist;
    iDist = SCR_WIDTH*y+x;
    *(target+iDist) = iColor;
}

d)        有了直接操作内存画点的程序,扩展成画线及画圆就很简单。

e)         注意在对显存直接操作前要先Lock,操作后要记得UnLock

f)         附录2附上直接调用显存画图的程序,供参考。下面的图为程序运行的截图。

 

 

5.       参考资料

a)         多谢DirectX工作室的老王,在开始编程时是参考他的手册。

b)        本文部分图形来自“放飞技术网”。

c)        一本很久以前买的老书,里面有一章提到DirectX中的Lock与直接处理显存。