利用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下编写,但是是参考的MSDN中DirectX 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.h和ddraw.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. 调用Surface的Release成员函数释放相应资源
ii. 调用DiretDraw的Release成员函数释放相应资源
4. 直接操作显存
a) 使用BackBuffer Surface的Lock成员函数,会取得一个Surface,其中的指针lpSurface就是指向后台缓存表面。对这个指针的内容进行修改,并进行Flip就会显示在屏幕上。
b) 该缓存与屏幕的对应关系与显示模式有关。当使用8bit色彩时,颜色由调色板决定;而使用16bit和24bit时,与调色板无关,直接用2byte或3byte代表颜色值。
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与直接处理显存。
浙公网安备 33010602011771号