2023年12月22日发(作者:)

directx中文手册

1、什么是DirectX ?

微软的DirectX软件开发工具包(SDK)提供了一套优秀的应用程序编程接口(APIs),这个编程接口可以提供给你开发高质量、实时的应用程序所需要的各种资源。DirectX技术的出现将极大的有助于发展下一代多媒体应用程序和电脑游戏。

总的说来,使用DirectX的主要有两个好处:1、为软件开发者提供硬件无关性;2、为硬件开发提供策略。

1、为软件开发者提供硬件无关性

微软开发DirectX,其最主要的目的之一是促进在Windows操作系统上的游戏和多媒体应用程序的发展。在DirectX出现以前,主要的游戏开发平台是MS-DOS,游戏开发者们为了使他们的程序能够适应各种各样的硬件设备而绞尽脑汁。自从有了DirectX,游戏开发者们便可以获益于Windows平台的设备无关性,而又不失去直接访问硬件的特性。DirectX主要的目的就是提供象MS-DOS一样简洁的访问硬件的能力,来实现并且提高基于MS-DOS平台应用软件的运行效果,并且为个人电脑硬件的革新扫除障碍。

另一方面,微软公司开发DirectX是为了在当前或今后的计算机操作系统上提供给基于Windows平台的应用程序以高表现力、实时的访问硬件的能力。DirectX在硬件设备和应用程序之间提供了一套完整一致的接口,以减小在安装和配置时的复杂程度,并且可以最大限度的利用硬件的优秀特性。通过使用DirectX所提供的接口,软件开发者可以尽情的利用硬件所可能带来的高性能,而不用烦恼于那些复杂而又多变的硬件执行细节。

一个高表现力的基于Windows平台的游戏将得益于以下几种技术:

专为提高图形运算及快速反应能力而设计的加速卡(Accelerator cards)

即插即用以及其它Windows软硬件

内建于Windows的通信服务, 包括DirectPlay

2、为硬件开发提供策略

DirectX的另外一个重要的目的是给硬件厂商提供开发策略,他们可以从高性能程序的开发者和独立的硬件供应商(independent hardware vendors IHVs)那里得到反馈。所以,在DirectX 程序员参考书中有时可能会提供那些还不存在的硬件加速设备的技术细节。在很多时候,软件可以模拟这些特性,在另外一些情况下,软件根据硬件的指标判断出其特性,并且可以忽略那些硬件并不支持的性能。

已经和将要实现的显示设备的特性包括:

覆盖(Overlays),由于它的被支持,在图形设备接口中,窗口中的换页(page flipping)将成为可用。换页是用来在整个屏幕上显示画面的双缓冲(double-buffer)方案。

精灵引擎(Sprite engines),使精灵(不规则图形)覆盖更容易。

插补延展(Stretching with interpolation),它可以更有效的保存显示内存,因为它可以使小幅画面延展到整个屏幕。

Alpha 融合(Alpha blending),它可以在硬件像素层(hardware-pixel level)上混合颜色。

带有透视修正(perspective-correct)贴图的3D加速器,它允许你在3D表面上贴图。比如,你可以在用3D软件制作的城堡的走廊里贴上砖块的位图来显示出透视效果。

为3D图象进行的位操作将 Z 方向考虑在内。

1

标准2兆显示内存,这在3D游戏中是最小需求。

压缩标准,这将允许你在显存中储存更多的信息。这个标准不论是在软件还是硬件中执行都会相当快。它将被用在贴图中并且包含透明压缩。

将要被包括的声音设备的新特性包括:

硬件及其外设可以提供空间环绕立体声效果。

声卡上内置音频内存。

音频-视频一体卡可共享其上的内存。

另外,视频回放将得益于今后的与DirectX相兼容硬件加速设备。今后的DirectX版本中的一个特性是将支持硬件加速YUV视频解码。

2、DirectX的组件

DirectX SDK为基于Windows平台的应用程序提供了以下几个组件。

DirectDraw :通过直接访问显示硬件来提供高级的图象处理能力。

DirectSound :它提供了软硬件的低延迟声音混频(low_latency sound mixing)和回放(Playback),硬件加速,以及直接访问音频设备的能力。

DirectPlay :它明确的提供了通用环境连接能力(generalized communication capabilities),来简化你应用程序之间的通讯服务。

Direct3D :它为主流的桌上型计算机和Internet用户提供实时的、交互的3D技术。

DirectInput :它简化你的应用程序访问鼠标、键盘和操纵杆设备的能力。

DirectSetup :一套简单的API向你提供安装DirectX部件的功能。

AutoPlay :它也是Win95操作系统的一个特性,当你在光驱内放上光盘,指定的应用程序会自动执行。

3、关于DirectDraw

DirectDraw是DirectX SDK大家族中的一员,也是其中最主要的一个部件。DirectDraw允许程序员直接的操作显存、硬件位图映射以及硬件覆盖和换页技术。它在提供这些功能的同时,也使其与现在的基于Microsoft Windows的应用程序和设备驱动程序相兼容。

DirectDraw是一个软件接口,它在提供直接访问显示设备的同时,与Windows图形设备接口(GDI)相兼容。DirectDraw不是一个高层的图形程序编程接口,它为游戏和Windows子系统软件(例如:3D图形包和数字视频编码)提供了一种与设备无关的途径,以获得访问特定的显示设备的某些高级特性的能力。

DirectDraw适用于种类众多的的显示设备,从简单的SVGA显示器到提供裁剪、缩放、和支持非RGB颜色格式的高级硬件实现设备。设计这样的接口是为了让你的应用程序能够列举低层硬件的能力,并且对那些支持的硬件加速特性加以利用。那些在硬件设备中不能实现的特性,DirectX将仿真出来。

DirectDraw提供了以下几个优点,这些好处在以前只有那些专为特定显示设备所写的软件才能利用。

支持双缓冲和换页图形

访问、控制显示卡的位图映射

支持3D z-buffers (z缓存)

支持z方向(z-ordering)硬件辅助覆盖。

访问图形缩放硬件

2

仿真访问标准的和增强的显示设备内存空间

DirectDraw的任务是用与设备无关的途径来提供依赖于设备的访问显示内存的方法。本质上,DirectDraw管理显示内存。你的应用程序只需要懂得那些一般的关于硬件与设备有关的知识,比如RGB和YUV色彩格式和两条光栅线之间的pitch(宽距) 。在需要利用位转换或操作调色板寄存器时,你不需要为调用过程中的细节而烦恼。使用DirectDraw,你可以方便的操作显示内存,充分的利用不同类型的显示设备的位转换和颜色压缩能力,而不需要依赖于某一个特定的硬件。

DirectDraw给运行于Windows 95和Windows NT 4.0或更高版本的计算机提供了一个高性能的的游戏图象引擎。

4、为什么要使用DirectDraw?

无庸置疑,人们之所以利用DirectDraw来开发各种游戏以及多媒体应用软件,是因为DirectDraw可以为他们的应用程序带来许多强大的功能提升。

DirectDraw可以充分评估视频硬件的能力,只要可能,它就会对其某一特性加以利用。例如,如果你的显卡支持硬件Blit,DirectDraw就会将位图映射这一操作分派给显卡来完成,极大的提升运行速度。此外,当某硬件不支持某项特性时,DirectDraw还提供了硬件仿真层(HEL)以完成这项操作。

DirectDraw的硬件抽象层(HAL)提供了一个统一的接口,通过它,开发者可以直接的操作显示存储器和视频存储器,从系统硬件中获取最大的表现能力。

DirectDraw运行于Windows95操作系统之上,从系统所提供的32位内存寻址和平面内存模型中获益。DirectDraw将视频和系统存储器视为整块的空间,而不是碎片的集合。如果你曾使用过区段偏移寻址,你将很快就喜爱上这种“平面”内存模型。

对于全屏模式的应用程序,DirectDraw使得多后台缓存的换页操作变得极为容易。

支持窗口和全屏模式应用程序的裁减。

支持3-D z缓存。

支持带z轴方向的硬件辅助覆盖。

可访问图象缩放硬件。

可同时访问标准的和增强的显示设备内存区。

其它的特性,包括动态改变调色板、独占访问硬件、和分辨率切换等等。

合理和有效的利用DirectDraw的这些特性将使开发者很容易就能够写出比基于标准的Windows GDI应用程序,甚至MS-DOS应用程序还要优秀的作品。

5、DirectX 5.0的新特性

DirectX5.0版本比3.0版拥有更多的创新和新特性(注意:并不存在4.0版)。尽管这样,你用以前版本写的DirectX应用程序同样可以不加修改的运行于DirectX5.0环境中。以下将介绍的是DirectX 5.0中DirectDraw接口的新特性。

首先,DirectDraw支持Windows98和Windows NT 5.0的多显示器系统。例如,在多显示器环境下,同一台电脑使用两套不同的显示设备和显示器,用户可以将不同的画面显示在两个显示屏幕上,甚至将一个窗口从一个显示器拖拽到另一个显示器上。DirectDraw5.0支持这样一个系统,允许应用程序在这样的环境中直接的访问和操纵硬件加速特性。

其次,DirectDraw已经使视频端口(Video-port)具有新的性能,它允许应用程序直接控制数据流从一个硬件视频端口流向一个位于显存中的DirectDraw页面上。这样,使得视频图象的输入和输出对 3

DirectDraw来说都变得极为便利和快速。

除此之外,DirectDraw的HEL(硬件仿真层)现在可以充分利用Pentium MMX处理器所带来的多媒体性能的提升。当你第一次创建页面的时候,DirectDraw会测试你的电脑芯片是否支持MMX。而在一台非Pentium MMX级别电脑上,这个测试会在调试程序的时候给出一个良性的异常信息,而这个异常并不会影响到你的应用程序执行的稳定性。

DirectDraw接口现在还支持离屏页面的宽度大于主页面的宽度。你可以创建这些页面而不用顾及它的大小,而在以前的版本中,你经常会为了不使页面宽度大于主页面而将其分割开来。

DirectDraw接口现在已经能够支持高级图形端口(AGP)构架。在装备了AGP显卡的系统中,DirectDraw程序开发者将从AGP显卡的大容量和高传输速率中获益。

6、部件对象模型(COM)

DirectX是基于COM的一套软件编程接口。

部件对象模型(Component Object Model, COM)是OLE 的基础。COM 为OLE提供了编程模型和二进制标准。COM定义并实现了软部件(如应用程序、数据对象、控件及服务)机制,并把它们统称为“对象”。每个软部件对象由数据以及访问数据的函数组成,访问软部件对象数据的函数的集合称为“接口”。

从这里可以看出,COM 的设计与C++类非常相似,即一个软部件对象具有一个内部数据结构和一组外部接口函数,允许通过接口函数对数据进行访问。因此Microsoft公司把根据COM执行的对象统称为Windows对象。但Windows对象与C++中的对象有明显的差别,Windows对象中没有公共数据,也没有成员函数,因此不能直接访问数据,也就是说数据是全封装的。

对象的提供者或服务器必须指明一个或多个接口的定义,每个接口都是相互关联的一组函数,执行对象的一个特性。每个对象的一个用户(或称为“客户”)必须拥有一个接口指针才能访问对象。当客户有了这个指针后,就可以使用这个对象而无需知道对象的含义,即使客户运行在不同的进程、不同的机器、不同的操作系统、由不同的软件开发而使用不同的语言,或者版本不同。

DirectX SDK接口被创建在COM编程层次表中很基础的一层。每一个代表设备的对象的接口,比如IDirectDraw、IDirectSound、和IDirectPlay,是直接从IUnknown OLE接口中派生下来的。这些基本对象的创建被操作于在该对象的动态连接库中,要比用Win32中专门用来创建COM对象的CoCreateInstance函数要好得多。

特别的是,DirectX SDK对象模型为每一个设备提供一个主要的对象。其它的设备对象是由这个主要对象派生下来的。例如,DirectDraw对象代表显示设备。你可以用它来创建代表显存的主页面(DirectDrawSurface)对象,和代表硬件调色板的调色板(DirectDrawPalette)对象;相似的,DirectSound对象代表声卡,并且创建DirectSoundbuffer对象来代表声卡上的声音数据。

7、自我检测

本章所讲的内容都是关于DirectX的较为抽象的概念。其实,在你真正开始自己编制程序,进入这一领域之前,有些概念现在还是很难接受的。但是,当你在有了一定的实践经验,在回过头看这些文字,你会发现它们其实还是很好理解的。实践出真知,掌握DirectDraw知识的最佳途径只有一个,那就是“编程”。在下一章里,我们将正式开始DirectDraw编程的学习。

通过这一章的学习,你应该对以下内容有所了解:

DirectX是做什么用的?

4

DirectX包括哪些组件,以及它们各自的作用。

DirectDraw的好处是什么,我们为什么要使用它。

COM的一般解释。

一个DirectDraw入门程序

1、一个小测验 2、牛刀小试 3、分析代码 1)程序结构 2)定义和创建DirectDraw对象 3)设置控制级和显示模式 4)创建主页面 5)输出文字 6)释放对象 7)主窗口类型 4、小结

1、一个小测验

在正式进入本章主题之前,先对读者进行一次小小的入学考试,不用紧张,其实几道题都非常的简单。

1、根据你对左边这三个函数的直观感觉,选出它们的正确对应关系。

WinMain() a、初始化窗口

InitWindow() b、处理Windows消息

WinProc() c、应用程序入口

2、找出与其它三个没有共同点的一个。

a、HINSTANCE b、HWND

c、HBITMAP d、HELLO

3、HWND之于窗口,相当于什么之于苹果。

a、苹果皮 b、苹果核

c、苹果把儿 d、整个苹果

4、如果要创建一个最普通的窗口,应该用以下的哪一个标志。

a、WS_OVERLAPPEDWINDOW b、WS_STRANGE

c、WS_BEAUTIFUL d、WS_UGLY

如果你有一道题做错了,说明你对Win32编程还不是十分了解,那么你需要事先预习一下。请跳转到本教程的“Win32编程基础知识”一章,学习一下Win32编程的基础知识。(这四道题的答案分别是:cab、d、c、a。其实它们真的是非常简单,只要你仔细阅读一下题目,就是猜也能猜出来。)

如果这些题对你来说不成问题,祝贺你,你可以继续本章的内容了。

2、牛刀小试

只要是介绍编程的书,似乎有一个不成文的规定,即第一个例子由“Hello World”开始,本教程也不例外。那么,如果你早已迫不及待想初尝DirectDraw程序编译成功后的“0 error(s), 0 warning(s)”的喜悦,就让我们开始吧!

在下面的例子中,我们将利用Visual C++5.0来生成一个简单的DirectDraw应用程序。程序的创建将不使用方便的MFC(Microsoft Foundation Class Library,微软的C++基础类库)向导,而是使用最原始的Win32 应用程序开发环境。熟悉VC++的读者可能会问,为什么舍先进的MFC工具不用,而去 5

使用最原始的方法呢?这是因为,MFC主要是用于基于窗口和文档的应用软件的编程,它集成了大量的数据和方法,将许多烦琐的任务,如:应用程序初始化、文档处理、磁盘IO封装起来,虽然这样可以给你的编程带来了极大的便利,但是在你编制基于图形显示和多媒体的应用程序的时候,这却会给你带来极大的麻烦。首先,你无法触及系统的内核,如:你需要自己来处理每一个消息循环时,而MFC并没有为你留出这样一个接口;而且,MFC为你事先建好的类,它们的许多功能对你来说是没用和低效率的,使用它们只会给你的程序带来冗余和不便。

总之,MFC为你隐藏了太多技术细节,而DirectDraw编程需要系统对于开发者具有一定的透明度。

所以,在大多数情况下,我们用最基本的Win32应用程序开发环境来开发我们的DirectDraw应用程序,本教程中几乎所有的例程都是使用Win32开发环境。当然,这并不是说用MFC就不能编制基于DirectDraw的应用程序了,它也是可以的,这将在本教程的“用MFC创建DirectDraw应用程序”一章中做介绍。

使用Win32开发环境表明,你必须从WinMain()开始编程,自己写每一个消息的处理程序,这的确是一项很繁重的工作。但是当你理解和熟悉了这一套方法时,你会发现它其实是相当直观和容易的。

打开Visual C++ 5.0。

选择File菜单的New,在出现的对话框中,选择Projects栏目(新建工程),并点取其下的Win32

Application项,表示使用Win32环境创建应用程序。先在Locatin(路径)中填入“c:”,然后在Project

Name(项目名称)中填入“Hello”,其它按照缺省设置,使对话框如图所示。单击OK按钮。

此时,一个基于Win32的工程已经创建完毕,但是它还没有包括任何文件。你需要新建一个C++文件增加到工程中。

再次选择File菜单的New,在出现的对话框中,选择Files栏目(新建文件),并点取其下的C++ Source

File项,表示新建一个C++源文件。在右边的File栏中输入“Hello”,最后确定让Add to project检查框打上勾,使整个对话框如图所示。单击OK按钮。

在文件中输入以下源程序代码,当然,你最好的做法是将以下的代码复制到你的文件中去,确保能用。

//*******************************************************************

// 工程:hello

// 文件:

// 内容:创建第一个DirectDraw应用程序,

//*******************************************************************

#include

#include

#include

LPDIRECTDRAW lpDD; // DirectDraw对象

LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw主页面

char szMsg1[] = "Hello World, I am DirectDraw boy !";

char szMsg2[] = "按 ESC 退出";

6

//函数声明

LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );

BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );

BOOL InitDDraw( void );

void FreeDDraw( void );

//*******************************************************************

//函数:WinMain()

//功能:Win32应用程序入口函数。进行初始化工作,处理消息循环

//*******************************************************************

int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

//初始化主窗口

if ( !InitWindow( hInstance, nCmdShow ) )

return FALSE;

//初始化DirectDraw环境,并实现DirectDraw功能

if ( !InitDDraw())

{

MessageBox(GetActiveWindow(), "初始化DirectDraw过程中出错!", "Error", MB_OK );

FreeDDraw();

DestroyWindow(GetActiveWindow());

return FALSE;

}

//进入消息循环

while (GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return ;

}

//******************************************************************

//函数:InitWindow()

7

//功能:创建主窗口。

//******************************************************************

static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )

{

HWND hwnd; //窗口句柄

WNDCLASS wc; //窗口类结构

//填充窗口类结构

= 0;

dProc = WinProc;

xtra = 0;

xtra = 0;

nce = hInstance;

= LoadIcon( hInstance, IDI_APPLICATION );

r = LoadCursor( NULL, IDC_ARROW );

kground = GetStockObject(BLACK_BRUSH);

nuName = NULL;

assName = "dxHello";

//注册窗口类

RegisterClass( &wc );

//创建主窗口

hwnd = CreateWindowEx(

0,

"dxHello",

"",

WS_POPUP,

0, 0,

GetSystemMetrics( SM_CXSCREEN ),

GetSystemMetrics( SM_CYSCREEN ),

NULL,

NULL,

hInstance,

NULL );

if( !hwnd ) return FALSE;

//显示并更新窗口

ShowWindow( hwnd, nCmdShow );

UpdateWindow( hwnd );

return TRUE;

8

}

//******************************************************************

//函数:WinProc()

//功能:处理主窗口消息

//******************************************************************

LRESULT CALLBACK WinProc( HWND hWnd, UINT message,

WPARAM wParam, LPARAM lParam )

{

switch( message )

{

case WM_KEYDOWN://击键消息

switch( wParam )

{

case VK_ESCAPE:

PostMessage(hWnd, WM_CLOSE, 0, 0);

break;

}

break;

case WM_DESTROY://退出消息

FreeDDraw();

PostQuitMessage( 0 );

break;

}

//调用缺省消息处理过程

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

}

//******************************************************************

//函数:InitDDraw()

//功能:初始化DirectDraw环境并实现其功能。包括:创建DirectDraw对象,

// 设置显示模式,创建主页面,输出文字。

//******************************************************************

BOOL InitDDraw(void)

{

DDSURFACEDESC ddsd; //页面描述

HDC hdc; //设备环境句柄

//创建DirectCraw对象

if ( DirectDrawCreate( NULL, &lpDD, NULL ) != DD_OK ) return FALSE;

9

// 取得独占和全屏模式

if ( lpDD->SetCooperativeLevel( GetActiveWindow(),

DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)

return FALSE;

//设置显示模式

if ( lpDD->SetDisplayMode( 640, 480, 8 ) != DD_OK) return FALSE;

//填充主页面信息

= sizeof( ddsd );

s = DDSD_CAPS;

= DDSCAPS_PRIMARYSURFACE;

//创建主页面对象

if ( lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ) != DD_OK)

return FALSE;

//输出文字

if ( lpDDSPrimary->GetDC(&hdc) != DD_OK) return FALSE;

SetBkColor( hdc, RGB( 0, 0, 255 ) );

SetTextColor( hdc, RGB( 255, 255, 0 ) );

TextOut( hdc, 220, 200, szMsg1, lstrlen(szMsg1));

TextOut( hdc, 280, 240, szMsg2, lstrlen(szMsg2));

lpDDSPrimary->ReleaseDC(hdc);

return TRUE;

}

//******************************************************************

//函数:FreeDDraw()

//功能:释放所有的DirectDraw对象。

//******************************************************************

void FreeDDraw( void )

{

if( lpDD != NULL )

{

if( lpDDSPrimary != NULL )

{

lpDDSPrimary->Release();

lpDDSPrimary = NULL;

10

}

lpDD->Release();

lpDD = NULL;

}

}

为了简化代码,这第一个入门程序没有头文件。在进行编译之前,还得进行最后的设置。选择Project菜单的Settings…,出现工程设置对话框。选择Link栏,在Object/Library modules中添入“”。使对话框如图所示。

请注意:这一步骤是将DirectDraw的静态连接库文件连接到工程中,否则,程序虽然可以正常编译,但是在连接时会产生一个“unresolved external symbol”(没有定义的外部符号)的错误。在以后所有的DirectDraw程序中,都必须将与你所用到的DirectDraw组件相应的静态连接库添入到这个设置中。

至此,一个最基本的DirectDraw应用程序已创建完毕,你现在不必去深究这些代码的含义,在下面及以后的章节中我们会对它们进行详细的分析。这虽不是一个最简单的DirectDraw应用程序,但它确实是一个能够实现最基本的输出功能的DirectDraw程序。

按F7编译成功后,按Ctrl+F5,执行该程序,显示器将切换到640*480*256色模式,黑屏后,屏幕中央会打印出蓝底黄字“Hello World, I am DirectDraw boy !”,除了输出这些字符外,这个程序什么也不做。按ESC可退出程序。程序运行结果如下图

这就是DirectDraw?有的人也许会对DirectDraw感到很失望,因为它并没有为我们表现出神奇的功能啊?但有的人却会对此感到异常兴奋,他们觉得一扇充满诱惑的房间的大门正向他们打开。这就象透过天窗,有些人只会看到黑暗的夜空,有些人却能看见满天的星星一样。第一个例子,为了使程序不至于太长而让那些初学者望而生畏,所以只能一再简化(尽管这样,整个程序还是占用了相当大的篇幅)。在后续章节的例子中,你们会看到程序会一个比一个更精彩。

3、分析代码

下面,让我们来逐一分析一下这个程序。

1)程序结构

分析程序应该是一个由外而内,逐步求精的过程。首先从大的方面来看,这个程序一共用到了五个函数,如果按照正常顺序,排除程序中出错的可能,它们的调用顺序依次是这样的。

WinMain ----> InitWindow ----> InitDDraw ----> WinProc ----> FreeDDraw

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

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

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

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

11

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

上面这段话如果使你感到迷惑,就象是在你小学的时候有人给你讲什么是微积分,那么你仍需要事先预习一下Win32编程。请跳转到本教程的“Win32编程基础知识”一章,复习一下Win32编程的基础知识。

如果你对Win32编程和Windows的消息机制有一定的了解的话,以上概念是比较容易理解的。

2)定义和创建DirectDraw对象

分析完程序的总结构,再让我们从最开头看起。

#include

这是把DirectDraw的头文件包含到文件里来。这一步在以后所有的例程中都是必不可少的。

LPDIRECTDRAW lpDD; // DirectDraw 对象

LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw 主页面

接着定义了三个全局指针变量,它们都是指向对象的指针。第一个是DirectDraw对象,表示显示硬件,它包括了显示器和显卡还有显存,用它来代表整个显示系统。第二个是DirectDrawSurface对象,表示页面,你可以在大脑中把它想象成一张矩形的白纸,你可以在上面绘制图象。现在你暂且不用去深究它们的含义,看下去就是了,在后面的课程中,我还会更加详细的介绍。

LPDIRECTDRAW和LPDIRECTDRAWSURFACE是在ddraw.h头文件里预定义的指向DirecctDraw和DirectDrawSurface对象的长型指针,所以前面加了LP,代表Long Point。因为这些DirectDraw对象指针变量经常用到,所以给它起名为lpDD和lpDDSPrimary,DD是DirectDraw的缩写。

给一个变量命名时,在变量名前加上该变量的类型标志,在Windows编程中是一个默认约定,称为匈牙利表示法,该名称来源于微软的一个匈牙利籍资深程序员,因为他惯用此表示法,后来便成了规范。如lpDD表示一个长型指针变量,dwHeight代表一个DWORD型变量。这样的好处就在于你可以一眼就辨认出某变量的类型,而不用去追溯它的定义。

这里所说的“对象”(Object),并不完全等同于C++中对象的概念,尽管它们使用的是同一个英文单词Object。这里的对象指的是COM,COM是Component Object Model 的缩写,代表“部件对象模型”,它在DirectX中贯穿始终,无处不是它的身影。

在DirectX SDK中,大多数APIs(应用程序编程接口)由对象和基于COM的接口组成。COM是致力于可重复利用接口资源的面向对象系统的基础,并且是OLE编程的核心模型。它也是一个接口规范,通过它可以设计出许多接口。它是建立在操作系统层次的对象模型。

COM与C++类也有许多相同之处。对一个C++程序员来说,COM接口就象是一个抽象基础类。这就是说,它定义了一套关键符号(signatures)和语法(semantics),但不是执行语句,并且没有与接口相关联的状态数据。在C++抽象类中,所有的方法被定义成纯虚(pure virtual)函数,它们并没有实际的代码。在这一点上,COM和基础类是一致的。

COM对象与C++对象的另一个相似点是:一个函数的第一个引用是接口或类的名称,在C++中叫做this引用。因为COM对象和C++对象是完全二进制兼容的,编译器把COM接口当作C++抽象类来看,而且采取同样的语法。这样就可以减少代码的复杂程度。例如,this引用在C++中被当作可识元素,并且被暗中的操作,COM中也是如此。

COM 是DirectX的基础,虽然它和C++中的类不近相同,但你完全可以把它当成C++的类来看待,在实际编程中,它们的语法和接口也是完全一样的。所以,对于C++程序员,进行DirectX编程并不 12

需要你去学习新的编程方法,继续沿用你所熟知的C++,DirectX也能为你所用。

只要是你要用到DirectDraw接口的特性,都必须创建一个DirectDraw对象,它是DirectDraw接口的核心。它是这样被创建的:

if ( DirectDrawCreate( NULL, &lpDD, NULL ) != DD_OK ) return FALSE;

DirectDrawCreate()函数是在ddraw.h中定义的,关于这个函数的详细解释,请参看本教程的“DirectDraw参考手册”一章,它的原型如下:

HRESULT DirectDrawCreate(GUID FAR * lpGUID, LPDIRECTDRAW FAR * lplpDD, IUnknown FAR *

pUnkOuter);

第一个参数是lpGUID:指向DirectDraw接口的全局唯一标志符(GUID:Global unique identify)的指针。在这里,我们给它NULL,表示我们将使用当前的DirectDraw接口。

第二个参数是lplpDD:这个参数是用来接受初始化成功的DirectDraw对象的地址。在这里,我们给它&lpdd。

第三个参数是pUnkOuter:千万不要追问这个参数是干嘛使的,如果你不想惹麻烦,就给它NULL吧。Microsoft的说明书上是这么写的“考虑到与将来的COM集合特性保持兼容,当前,不管怎样,如果这个参数不是NULL ,DirectDrawCreate将返回一个错误”。

所有的DirectDraw函数的返回值都是HRESULT类型,它是一个4字节(32位)的值,用来代表某个错误或警告。DirectDraw头文件中已经预定义了所有可能的返回值常量,仅函数返回成功的值是用

“DD_OK”表示,所有的错误值标志开头都为“DDERR”,如:

DDERR_DIRECTDRAWALREADYCREATED

DDERR_GENERIC

DDERR_OUTOFMEMORY

Windows编程中,有一项让中国软件开发者大挠其头的就是函数名、常量名和数据结构名称中往往有长串的字符。这时,就要考验读者的英文断句水平了。如:DDERR_OVERLAYCOLORKEYONLYONEACTIVE,应该断为:DDERR-OVERLAY-COLORKEY-ONLY-ONE-ACTIVE。这对于老外来说也许并不成什么问题,但对于我们,有时候要看懂一个语句,简直就象在做一道英文题。除非你对一个名称有十足的把握,否则就应该尽量使用CP规则(原是离散数学里的一条规则,现引申为Copy-Paste,即:复制-粘贴)。而且,给读者一点忠告,在起函数、变量或常量名称时,也应该沿用老外的这条原则,以尽量清楚的表达意义为宗旨,不要担心它是否太长(当然,对于那些重复利用率很高的函数、变量或常量,还应该尽量使用缩写),否则,总是以i、n为变量,程序的可读性会变得极差,而在编程的初学者当中,这是很常见的。

3)设置控制程度和显示模式

DirectDrawCreate函数调用成功后,lpDD已经指向了一个DirectDraw对象,它是整个DirectDraw接口的最高层领导,以后的步骤都是在它的控制之下。

if ( lpDD->SetCooperativeLevel( GetActiveWindow(),

DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)

return FALSE;

这个语句用来设置应用程序对操作系统的控制程度。它的原型如下:

HRESULT SetCooperativeLevel( HWND hWnd, DWORD dwFlags )

13

第一个参数是hWnd,我们调用Win32的API函数GetActiveWindow获得应用程序主窗口的句柄,这将使DirectDraw对象与主窗口的消息挂上勾。

第二个参数是dwFlags,控制级的标志符。我们给它DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN,表示我们期望DirectDraw以独占和全屏方式工作。

这个函数有很多用法,而且它必须在创建DirectDraw对象之后,立即调用。你可以用它来设置应用程序是运行于全屏还是窗口模式,是独占还是共享模式。具体用法将在以后逐步介绍。可参阅“DirectDraw参考手册”。

if ( lpDD->SetDisplayMode( 640, 480, 8 ) != DD_OK) return FALSE;

显而易见,这是设置显示器的显示模式,它把显示模式设为640*480,8位色彩模式(即256色)。这是绝大多数显示器所能够支持的显示模式,所以我们不用担心它会出什么问题。在以后的例程中,我们将看到可以调用EnumDisplayModes()来列举出显示器所支持的所有显示模式。绝不要轻易尝试直接设置一个新的显示模式,而应从列举出的显示模式中选择,这也将在后续章节讲到。

要注意的是,只有当DirectDraw对象为独占访问的控制程度时才能改变显示器的显示模式,如果DirectDraw对象运行为窗口模式,调用该函数会返回一个错误。

4)创建主页面

然后的任务是要创建一个DirectDrawSurface对象。

DirectDrawSurface对象代表了一个页面。页面可以有很多种表现形式,它既可以是可见的(屏幕的一部分或全部),称之为主页面(Primary Surface);也可以是作换页用的不可见页面,称之后台缓存(Back

Buffer),在换页后,它成为可见;还有一种始终不可见的,称之为离屏页面(Off-screen Surface),用它来存储图象。其中,最重要的页面是主页面,每个DirectDraw应用程序都必须创建至少一个主页面,用它来代表屏幕上可见的区域,说白了,就是你的显示屏幕。

创建一个页面要分两步走,这里,我们以创建主页面为例,简要介绍一下这两个步骤,其它类型页面的创建也与之类似,在以后的例程中还会着重讲解。

在调用CreateSurface()函数创建一个页面之前,首先需要填充一个DDSURFACEDESC的结构,它是DirectDraw Surface Description的缩写,意思是DirectDraw的页面描述。该结构的详细资料请参看本教程“DirectDraw参考手册”一章。

//填充主页面信息

= sizeof( ddsd ); //结构的大小

s = DDSD_CAPS; //指定DDSURFACEDESC结构的ddsCaps成员为可用

= DDSCAPS_PRIMARYSURFACE; //指定要创建的是主页面

这就象是你到银行取款,必须事先填写一张取款单,你要在上面详细描述你的姓名,你的存折序号,以及你要取的钱数等等,然后把它递给银行工作人员。

页面描述填充完毕后,把它传递给CreateSurface()函数即可。

//创建主页面对象

if ( lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ) != DD_OK)

return FALSE;

CreateSurface()函数的第一个参数是被填充了页面信息的DDSURFACEDESC结构的地址,为&ddsd; 14

第二个参数是接收主页面指针的地址,此处为&lpDDSPrimary;第三个参数现在必须为NULL,为该函数所保留。

如果函数调用成功,lpDDSPrimary将代表一个合法的主页面对象。由于在前面已经设置了该程序的工作模式为独占和全屏,所以,此时主页面所代表的实际上是你的整个显示屏幕。在主页面上所绘制的图形将立即反映到你的显示屏幕上。

5)输出文字

下面开始在主页面上输出文字。

//输出文字

if ( lpDDSPrimary->GetDC(&hdc) != DD_OK) return FALSE; //获得设备环境句柄

SetBkColor( hdc, RGB( 0, 0, 255 ) ); //设置背景颜色

SetTextColor( hdc, RGB( 255, 255, 0 ) ); //设置文字颜色

TextOut( hdc, 220, 200, szMsg1, lstrlen(szMsg1)); //输出文字

TextOut( hdc, 280, 240, szMsg2, lstrlen(szMsg2));

lpDDSPrimary->ReleaseDC(hdc); //释放资源

如果你十分熟悉Windows的GDI(图形设备接口),你会发现上面这段程序居然和在窗口中输出文字一模一样。这是因为,DirectDraw页面和GDI的设备环境(DC)其实是兼容的。在调用主页面的GetDC()函数获得页面的设备环境句柄(HDC)之后,就可以使用Win32的API绘图函数来进行绘图操作了。同样,最后也必须调用主页面的ReleaseDC()函数来释放设备环境资源。

6)释放对象

在程序结束之前,DirectDraw还必须做一项扫尾工作,即:把已经创建的所有DirectDraw对象从内存中清除出去。这就是FreeDDraw()函数的作用。

void FreeDDraw( void )

{

if( lpDD != NULL ) //判断DirectDraw对象是否为空

{

if( lpDDSPrimary != NULL ) //判断主页面对象是否为空

{

lpDDSPrimary->Release(); //释放

lpDDSPrimary = NULL;

}

lpDD->Release(); //释放

lpDD = NULL;

}

}

每一个DirectDraw接口的对象都有Release()函数,以将其所引用的对象释放。这其实相当于C++中的delete方法。及时的将不用的对象释放掉是每一个优秀的程序员都应当养成的良好习惯。

15

6)主窗口的类型

以上关于DirectDraw接口的编程似乎与应用程序的主窗口没有一点联系,而且执行后,也看不到窗口的影子,是不是就可以不用创建程序的主窗口了呢?当然不是。

其实,程序执行后,漆黑的背景就是程序的主窗口。在注册窗口类时,已经给hbrBackground成员指定了黑色。

kground = GetStockObject(BLACK_BRUSH);

而且,在创建主窗口时,窗口的类型用的是WS_POPUP,表明创建的是一个没有标题栏,没有边框的窗口;而不是常用的WS_OVERLAPPEDWINDOW。

hwnd = CreateWindowEx(

……

WS_POPUP,

……

值得特别注意的是:在设置DirectDraw对象控制级的时候,使用了主窗口的句柄(HWND)。这就是说,DirectDraw将用主窗口来接收各种消息,这样,就可以利用主窗口来实现DirectDraw对用户操作的反馈。如,用户想用光标键来控制游戏图象的运动,光标键按下的消息被发往主窗口,然后在主窗口的消息处理过程中操作DirectDraw,使之对光标键产生反应。所以,在大多数情况下,创建一个主窗口是必不可少的。

if ( lpDD->SetCooperativeLevel( GetActiveWindow() /*主窗口句柄*/ ,

DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)

return FALSE;

4、小结

DirectX是一个功能强大而且使用复杂的工具,在这一章里,我们仅学习了一个最简单的利用DirectDraw接口的例子,对于庞大的DirectX来说,它只是冰山一角。要掌握DirectDraw编程是一个复杂的过程,需要从大量的实践中摸索出经验,任何读者想一蹴而就是不可能的。但是,好的开端意味着成功的一半,如果你能理解和掌握本章所学到的内容,继续下去,坚持不泄,你会发现你将一天比一天更加充实。

通过本章的学习,读者应能掌握:

在VC++5.0环境中使用Win32应用程序开发环境新建一个DirectDraw工程以及添加源文件。

用DirectDrawCreate()函数创建DirectDraw对象,并立即调用SetCooperativeLevel()设置其控制级,注意其第一个参数为应用程序主窗口句柄。

只有在独占的控制级下才能调用SetDisplayMode()以改变显示器的显示模式。

在调用CreateSurface ()创建DirectDrawSurface对象之前,必须先填充一个DDSURFACEDESC结构以描述页面的信息。

页面对象的种类,只有主页面代表了显示屏幕,为可见页面。

用GetDC()获得主页面的HDC,然后可以使用Windows API的GDI函数进行绘图或输出文字,最后必须调用ReleaseDC()以释放资源。

DirectDraw接口的对象必须被及时的用Release()函数释放。

16

DirectDraw基本图形概念

在上一章里,我们尝试了一个非常简单的DirectDraw应用程序,可以看到,DirectDraw编程对一个C或C++程序员来说并不是陌生和难以接近的。在这一章里,我们将介绍Directdraw编程中所经常用到的图形方面的知识,在深入进行DirectDraw编程之前,领悟这些最基本的图形概念是非常必要的。

1、像素和分辨率

2、RGB色彩

3、设备无关位图(DIB)

4、位深度(Bit depth)

5、抖动处理(Dithering)

6、调色板(Palette)

7、GDI与DirectDraw

8、位块传送(Blit)

9、翻页(Page flipping)

10、矩形(Rectangle)

11、精灵动画(Sprite animation)

12、关键色(Color Key)

13、补丁(Patching)

14、范围检查与碰撞检测

1、像素(Pixel)和分辨率(Resolution)

首先,让我们从电脑显示器屏幕上的基本单位棗像素说起。对用户来说,像素指的是显示器在某种分辨率状态下所能表达图象的最小单位。你可以把它简单的理解为一种类似于马赛克的东西。像素的大小与你当前的显示分辨率有关,现在所有的SVGA显示器都支持多种分辨率,比如通常所说的640x480、800x600等。许多人错误的将像素的大小与显示器的点间距(Dot Pinch)混淆了,其实它们指的不是同一个东西。当你用放大镜观察显示器屏幕时,你会发现图象实际是由许许多多红,绿,蓝整齐排列的小点组成,你可能会认为这就是像素,那么你就错了。这实际上是显示器光栅的一个扫描点,它是荧光屏后部的三束电子枪发射电子透过一层致密的网打到荧屏反面而发出的荧光,人们常说的0.28的彩显意即:屏幕上相临两个扫描点的平均间距为0.28毫米。一个像素是由若干个这样的扫描点组成的。专业的说,在某种分辨率状态下,显示器的水平(垂直)像素的个数,实际上等于一次水平(垂直)扫描期间,电子束的通短强弱状态能够发生变化的次数。一台彩显所能达到的最大分辨率受到这台彩显的尺寸和点距的限制。

显然,显示器型号越大,点间距越小,则它所能达到的分辨率就越高,那么,它所显示的图象就越清晰,表现得越细腻。

2、RGB色彩

现实生活中,我们所能分辨的所有颜色都能用三原色:红、绿、蓝合成出来,称为RGB色彩模式。用RGB方式合成颜色主要用于发光设备,如计算机显示器。通过分别调节红,绿,蓝三束电子枪的 17

强度,就可产生一个广泛的色彩范围。由于色彩的产生来源于RGB三种色彩光强度的叠加,所以,这种方法被称为彩色叠加。

值得一提但与我们的DirectDraw编程基本上没有什么关系的另一种色彩表示模式是CMYK色彩模式,代表青,粉,黄三种可减基色,加上黑色作对比,常用于打印,感光材料,胶片等。每种基本色从照射到打印页的白光中吸取某些色彩,由于CMYK方式通过从白光中吸取特定的色彩,所以被称为彩色削减。此外,还有HSI(也称HSL)色彩模式,它代表Hue , Saturation 和 Intensity 。Hue 是我们所认为的色调,如红橙黄绿青蓝紫等,Saturation描述该色调颜色的饱和度,Intensity 描述亮度。

RGB 颜色方式对于显示器来说是唯一适用的彩色混合方法,而CMYK则是适用于打印和印刷的彩色混合方法,HSI 则是一个很直观的定义彩色的方法。现在的许多图形软件中都同时具有这三种定义颜色方法的应用。但是,不论用那种方法给颜色取值,最终都必须转化为RGB方式才能为显示器所认识接收并显示出来。

在几乎所有的编程中,当然也包括DirectDraw,都是采用标准的24位RGB模式定义一个颜色,用三个从0到255的数值来分别代表R、G、B三个分量,每个分量都有256种亮度级别。当三种元素全被置为0,像素显示黑,通常表示为RGB(0,0,0);当全部置为255,像素显示为白,表示为RGB(255,255,255)。

3、设备无关位图(DIB)

位图图象(也称点阵图象)准确的说是什么呢?它们是数据元素的集合,这些数据元素决定在了在图片的某个具体位置是什么颜色。这就好比我们用许许多多的马赛克来拼图案一样,每个马赛克虽然只有一种颜色,但你看到的只是整个美丽的图案,而不是某一个马赛克。当图象显示在显示器屏幕上时,正如前面提到的那样,这一个个的马赛克就是像素。

Windows中,当然还包括DirectX,都使用设备无关位图DIB(Device-Independent Bitmap)作为其最基本的图象文件格式。

之所以称之为设备无关位图(DIB),其实是为了与设备相关位图(DDB:Device dependent bitmap)相区别。由于只有Windows 3.0以前的版本才广泛使用设备相关位图DDB,Windows 95和Windows NT及以后的版本使用的都是设备无关位图DIB,所以,“设备无关位图”这个名称在当前已没有其现实的含义。在大多数情况下,我们将其简称为位图(Bitmap)。

从本质上说,DIB是一个包含了图象的各种信息的文件,包括图象的尺寸,使用的颜色个数及其颜色值,还有每个像素的颜色数据等。此外,一个DIB中还包含了一些极少使用的参数,象文件压缩类型、重要颜色,还有图象的物理维数等。DIB文件通常具有“.bmp”扩展名,偶尔也会将“.dib”作为其扩展名。

Windows对DIB位图有规定的格式,在我们学习了位深度和调色板的概念之后,对DIB格式再做详细的解释。

与位图格式相对的是矢量格式,由于它是用形状和相互关系来描述图象,WMF文件就是一种典型的矢量图,它的特点是具有极大的灵活性,可以任意缩放不失真,且存储空间相对较小,但矢量图由于其描述图象的方式与位图完全不同,不适用于图象处理的领域。在以后的DirectDraw编程中,我们所指的图象特指位图图象。

4、位深度(Bit depth)

计算机中,一个字节(Byte)是由8个位(Bit)组成的。位深度指的是用来描述某状态值所使用的计 18

算机位的个数。在DirectDraw中,通常用位深度来代表位图中的颜色值所使用的位个数,从另一个意义上讲,位深度表示了位图中颜色的丰富程度。

1字节等于8位二进制数,所以一字节所能表达的十进制整数的范围是从0到255,即一字节能且最多只能反映出256(2的8次方)种不同的状态。在位图中,如果用其中每一种状态代表图象中某一个点的颜色,那么最多可以得到256种不同的颜色,这就是我们常见的8位(256色)的位图。同理,如果用1位二进制数来表示某一点的颜色,那么只能得两种(2的1次方)颜色,这就是一幅两位的黑白位图;如果用四位二进制数则产生16种颜色(2的4次方),这是一幅4位(16色)的位图,用24位产生16M种颜色(2的24次方)等,这就是一幅24位真彩位图。由于每幅图形文件都用确定的位数来代表颜色(1、2、4、8、16、24或32位),所以我们所见的图片颜色数都为2的n次方(n=位深度)。

显然,一幅图象的位深度越高,那么它所能表现的颜色也越多,色彩也就越丰富。通常,在一般情况下,8位(256色)的图片已可满足需要,但在一些要求高质量图象的场合,16或24位的图象是必须的。当然,颜色位深度的增加,也势必带来所需存储空间的膨胀,没有经过压缩的相同大小的24位图象所需的存储空间是8位图象的3倍,这也会带来文件读取和操作速度的降低,所以在需要高速度显示图象的场合,使用低位深度的图象是必要的。

5、抖动处理(Dithering)

在低位深度的图象中,由于颜色总数的限制,有些颜色无法显示出来,为了模拟出那些颜色以提高显示效果,广泛采用了一种称作抖动处理(dithering)的方法,也称半色调处理(Halftoning)。它是指用交替的点图案去模拟在图象中不能使用的颜色的过程。单色图象是最简单的格式,一般由黑色和白色组成,在一些单色图象如黑白照片和有深浅的图案中,会使用各种灰度,这种图象常被称为灰度图象(Grayscale Image)。由于人眼会把一个很细致的黑白相间的图案解释成灰色,所以灰度图象也可使用单色文件格式,数据仍然可以是黑和白。使用黑色或某一种单色的点获得连续的该色灰度的过程就是抖动处理。抖动处理被更多的用在那些低位数彩色图象文件中,与不采用这种处理相比,它具有更好的显示效果。

6、调色板(Palette)

显示器及显示卡与显示器的接口都采用模拟方式来处理色彩,因此,它们都具有几乎无限的色彩显示和传输能力,但主机和显示卡只能用数字方式来表示和处理色彩,在用数字方式表示色彩时,如果要获得更丰富,更细腻的色彩就需要增加色彩的位深度,这就需要更大容量的显示存储器,相应的也就需要更高的处理速度,同时分辨率的提高也对显示存储器的容量提出了很高的要求。

为了尽量降低对显存的需求,在Windows中可以使用一种间接的色彩表示方法,这就是调色板(Palette)表示法。它的含义是:用一个颜色索引(Color Index)来代表各个像素点的颜色,而不是直接用红,绿,蓝三基色的亮度值来确定每个像素点的颜色。色彩表(Color table)是一个包含了若干颜色索引和该索引所对应的真实颜色值的表,这个色彩表就是调色板。

这种方法就好比给学校里的每一个学生规定一个专用的学号,在很多场合,并不需要知道某个同学的实际姓名,只要知道他的学号即可,显而易见,使用学号无疑会给学校的学员管理工作带来了极大的便利。调色板就好比一张记载了学生学号及其姓名的名册,颜色索引值就是学生的学号,RGB颜色值就是学生的姓名。

使用调色板的好处就在于:索引值占用较少的数据位(1、2、4或8位),而真实颜色值占用较长的数 19

据位(24位,即3字节,分别代表红,绿,蓝三基色的颜色亮度值,从0到255)。由于使用了调色板,既提高了图象显示效率,又减少了对显存的需求。

调色板的颜色索引主要采用4或8两种位深度:4位位深度可有16种不同取值,对应于显示器的16色显示模式,这时一屏最多只能显示16种不同的颜色;8位位深度可有256种不同的取值,对应于显示器的256色显示模式,一屏最多只能显示256种颜色。于是,问题就开始出现了,当你在16色模式下显示一幅256色或更高位深度的的图象时,你将会看到本应色彩鲜艳的图片变得面目全飞,罪魁祸首就是调色板,由于它使得同时显示在屏幕上的不同颜色最多只能有16种(对应于16色模式下),于是Windows首先从图象中挑选了16种使用频率最高的颜色指定给调色板,对于所有其它的颜色,从调色板中挑选一个最接近的颜色显示出来。在256色模式下,要同时显示两幅不同调色板的256色位图时,也会发生这种图象失真的情况。

7、GDI与DirectDraw

在大多数的Windows编程中,开发者们使用的是Win32的函数以获得访问绘图页面的能力,例如,使用GetDC函数,可以获得设备环境(DC?/FONT>Device context)。在获得设备环境之后,你就可以开始进行对屏幕的绘图了。Win32的所有图形函数都是由Windows系统的一个独立完整的模块所提供,这就是图形设备接口(GDI?/FONT>Graphics device interface)。GDI为计算机用户和计算机硬件之间提供了一个抽象层,在此层的基础上,用户可以通过简单的调用Win32的图形函数进行图形显示。

GDI的一大缺憾就是,它不是为具有高表现力的多媒体软件和游戏而设计的,设计者们开发它的主要用途是运行商业应用软件诸如:Word字处理软件、Excel电子表格、Explorer浏览器等。GDI只提供了访问系统主存的能力,而不提供直接访问显存的能力,并不能从具有某些加速特性的显卡中获得其优良特性。简而言之,GDI对绝大多数的商业软件来说是相当完美的,但对于多媒体软件和游戏来说,它却是低速和低效的。

另一方面,DirectDraw可以提供给开发者代表了真实显示内存的绘图页面。这意味着,只要你使用了DirectDraw,你就可以直接操纵显卡上的内存,图形显示变得出奇的快速。而且这些页面代表了显存中连续的内存块,使得在页面中寻址和读写变得非常方便。

8、位块传送(Blit)

“Blit”是“Bit block transfer”的缩写,意为“位块传送”。顾名思义,Blit的作用是:将某一内存块的数据传送到另一内存块,前一内存块被称为“源”,后一内存块被称为“目标”。这里用的是“传送”一词,而不是“复制”,因为在Blit过程中,数据并不是被原封不动的转移,而是经过了一定的转换。

在许多书籍中或程序中经常可以见到的“Bitblt”、“Blt"或“Bltting”,其实也是同一个意思,前者读作['bitb'lit];后者读作[b'lit]。

在绝大多数情况下,Blit操作是针对位图图象的,因此,源数据代表的是“源位图”,目标数据代表的就是“目标位图”。图象程序开发者使用Blit的函数在内存中将某页面上的一幅位图经过一定的变换转移到另一个页面上。这种变换有很多种,每一种都有一个代码与之对应,我们称该代码为光栅操作代码(ROP?/FONT>Raster operation code)。

Blit操作被广泛的用于图形程序中,如在显示位图、移动、复制位图、位图合成、位图特效以及精灵的实现中都有blit的身影。GDI和DirectDraw中,都提供了blit的函数,如GDI中的BitBlt、StretchBlt、PatBlt等,DirectDraw中的IDirectDrawSurface3::Blt和IDirectDrawSurface3::BltFast等。

下面我们来分析一个典型的Blit的函数,GDI的BitBlt是Win32 API中一个重要的函数,关于它的详 20

细资料可以参阅VC中的帮助,位于:Platform,SDK,and DDK DocumentPlatform

SDKReferenceFunctionsWin32 Functions,它的原型如下:

BOOL BitBlt(

HDC hdcDest, //目标设备环境的句柄

int nXDest, //目标设备环境的矩形区域的左上角的x坐标

int nYDest, //目标设备环境的矩形区域的左上角的y坐标

int nWidth, //目标设备环境的矩形区域的宽度值

int nHeight, //目标设备环境的矩形区域的高度值

HDC hdcSrc, //源设备环境的句柄

int nXSrc, //源设备环境的矩形区域的左上角的x坐标

int nYSrc, //源设备环境的矩形区域的左上角的y坐标

DWORD dwRop //光栅操作符

);

dwRop参数是光栅操作代码(Rop),它是指源位图与目标位图以及图案刷的颜色值进行布尔运算的方式,以下列出了常用的光栅操作符。

光栅操作代码

BLACKNESS

DSTINVERT

MERGECOPY

MERGEPAINT

NOTSRCCOPY

含义

用黑色填充目标矩形区域。

将目标矩形图象进行反相。

将源矩形图象与指定的图案刷(Pattern)进行布尔“与”运算。

将源矩形图形经过反相后,与目标矩形图象进行布尔“或”运算。

将源矩形图象经过反相后,复制到目标矩形上。

NOTSRCERASE 先将源矩形图象与目标矩形图象进行布尔“或”运算,然后再将所得图象进行反相。

PATCOPY

PATINVERT

PATPAINT

将指定的图案刷复制到目标矩形上。

将指定的图案刷与目标矩形图象进行布尔“异或”运算。

先将源矩形图象进行反相,与指定的图案刷进行布尔“或”运算,再与目标矩形图象进行布尔“或”运算。

将源矩形图象与目标矩形图象进行布尔“与”运算。

将源矩形图象直接复制到目标矩形上。

SRCAND

SRCCOPY

21

SRCERASE

SRCINVERT

SRCPAINT

WHITENESS

将目标矩形图象进行反相,再与源矩形图象进行布尔“与”运算。

将源矩形图象与目标矩形图象进行布尔“异或”运算。

将源矩形图象与目标矩形图象进行布尔“或”运算。

用白色填充目标矩形区域。

表中,提到了三种的基本布尔运算,分别是:反相(NOT)、与(AND)、或(OR)。还提到了异或(XOR),学过数理逻辑的人都知道,异或运算其实也是可以由前三种组合出来的。可以看出,所有的光栅操作代码都是由三种基本布尔运算组合而成。实际上,一共有256种光栅操作代码,但最常用的就是以上这15种。若想使用这15种以外的任何一种时,可以查阅VC的帮助,但在实际中,它们是极少被用到的。

图案刷(英文是Pattern, 也可翻译成模式刷,直接的意思是布料上图案的花样)是Windows资源的一种,属于Brush,它其实是一个固定大小的位图(通常为8x8像素),用来平铺填充设备环境(DC)的某一区域。它的用法与普通的刷子是没有区别的,当把一个图案刷用SelectObject函数选定给某设备环境,再在该设备环境中进行填充操作,那么所填充的不是一个单纯的颜色(选用普通的刷子时的情况),而是连续平铺的图案。

GDI的blit函数还有PatBlt和StretchBlt。它们与BitBlt大同小异,BitBlt具有Patblt的所有功能,StretchBlt除了具有BitBlt的一切功能外,它还可以将位图放大或缩小,实现缩放功能。

DirectDraw最常用的blit函数是IDirectDrawSurface3::Blt,它的原型如下:

HRESULT IDirectDrawSurface3::Blt(

LPRECT lpDestRect, //目标矩形区

LPDIRECTDRAWSURFACE3 lpDDSrcSurface, //源页面

LPRECT lpSrcRect, //源矩形区

DWORD dwFlags, //标志符

LPDDBLTFX lpDDBltFx //光栅操作代码及特效

);

DirectDraw中的Blt函数比GDI中的BitBlt函数虽然在形式上极为相似,但在内容上却有了质的飞跃。首先,从运行速度上来比较,IDirectDrawSurface3::Blt可以利用到一切可以利用的硬件加速特性,而且该操作在缺省情况下是异步执行的,这意味着程序不需要等待blit结束才返回,而是在给系统发出了blit的指令后,立即返回,然后系统在后台进行blit操作,这使程序的运行变得极为高效、快速;其次,它还支持带源和目标关键色以及z缓存和alpha 通道的blit操作,这使得用该blit函数来完成各种效果变得极更为容易。

该函数的目标页面就是调用者本身,而且矩形区域用RECT结构表示。第四个参数dwFlays表示该blit操作的类型,最后一个参数lpDDBltFx是一个包含了光栅操作代码和其它特效的结构,其中的光栅操作代码成员与Win32的是兼容的。

DirectDraw中的blit函数还有IDirectDrawSurface3::BltFast和IDirectDrawSurface3:: BltBatch。前者完成一次显存中的blit操作,在没有硬件加速的情况下,其速度可以较Blt快一些,后者完成一组blit操作。

要得到更多的关于DirectDraw中的blit函数的资料,请参阅本教程的DirectDraw参考手册。

22

9、翻页(Page flipping)

在多媒体动画、动感游戏等软件中,翻页是一个相当关键的概念。与绘画师绘制动画片相比,计算机中的翻页技术与其具有相似之处。例如:绘画师在一叠相同的纸上绘画,画完一张再画下一张,在每一张上,绘画师使画面有略微的改动,于是,当你在这一叠纸上快速的翻页时,静止的图象便开始运动起来。

DirectDraw中的翻页与上面这个过程极为相似。首先,你得设置好一个翻页链结构,它由一组DirectDraw页面组成,每一个页面都可以被轮流翻页至显示屏幕。当前正好位于显示屏幕的页面,我们称之为主页面(primary surface),其后等待翻页至屏幕的页面,我们称之为后台缓存(Back buffers)。应用程序在后台缓存上进行绘图操作,然后“翻一页”,将此页面翻页成为主页面,原来的主页面成为后台缓存,翻页后,你所进行的修改可以立即显示在屏幕上。与此同时,你可以在下一个即将翻页成为主页面的后台缓存上进行绘图。将这个翻页过程一直持续下去,直到动画结束。

有了DirectDraw,整个翻页的任务并不是一件十分困难的工作。你既可以创建一个简单的双缓存翻页链结构(一个主页面和一个后台缓存),也可以创建一个使用起来更为灵活的多后台缓存翻页链结构。

由于DirectDraw更多的使用的是硬件的特性,使得翻页变得极为快速,这个过程在屏幕上不会产生丝毫的闪烁,其速度可以达到与显示器的刷新率一样的数量级。在后面的DirectDraw教程中,我们将具体介绍如何在DirectDraw程序中实现翻页。

10、矩形(Rectangle)

在Windows编程中,屏幕上的对象都是以一个封闭矩形的形状出现的。一个封闭的矩形可以用两个点来确定,左上角和右下角。绝大多数应用程序使用RECT结构来定义一个封闭矩形,用于blit操作或碰撞检测(hit detection)。RECT结构定义如下:

typedef struct tagRECT {

LONG left; //矩形左上角的X坐标

LONG top; //矩形左上角的Y坐标

LONG right; //矩形右下角的X坐标

LONG bottom; //矩形右下角的Y坐标

} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

应该注意的是:这个矩形区域是排除边线的,并不包括它的右边线和下边线。所以,这个矩形的宽度应该等于right-left,而不是right-left+1;同理,矩形的高度等于bottom-top。

11、精灵动画(Sprite animation)

精灵动画被广泛的用于多媒体及游戏软件中。从最基本的意义上说,精灵是一个可以在背景屏幕上四处移动的图象,通常,这个图象的形状是不规则的。它的实现方法可以简单的这样来描述:将精灵画在可见的背景页面上,然后将所画的上一个精灵从页面上抹去,再将精灵画在页面的另一个地方,依次类推。于是,对观察者来说,精灵在屏幕上就动起来了。

然而,在绝大多数情况下,我们所使用的精灵图象并不是规则的矩形,它们可能是多边形,也可能是一个支离破碎、完全不规则的图形。这就给我们实现精灵动画带来一个巨大的挑战,因为所有的blit函数所使用的都是规则的矩形区域,只有对于矩形区域,才能使blit操作更高效、流畅和便于调用。

于是,在GDI编程中,使用了一种称为屏蔽的方法,这也跟电影的特级合成用的方法差不多,先让一个人在一块蓝布前表演一些看上去很惊险的动作,然后再将这些影片与事先拍好的背景合成一体, 23

有蓝布的地方成了背景的画面,人就好象置身其中了。

要在计算机中实现这样的动画,得有两幅图:一幅是精灵图(Sprite),一幅是屏蔽图(Mask:也称掩码图)。精灵图中使要显示的部分颜色保持不变,其余全为黑色,屏蔽图中使精灵的部分都为黑色,其余全为白色。

有了这两幅图,经过两次blit运算,就可以把想要的精灵贴到背景上去了。第一次blit是让屏蔽图与背景进行布尔“与”(光栅操作代码为SRCAND)运算,第二次让精灵图与背景进行“异或”(光栅操作代码为SRCINVERT)运算。

伪代码如下:

BitBlt( hDCdest, 0, 0, width, height, hDCMask, 0, 0, SRCAND );

BitBlt( hDCdest, 0, 0, width, height, hDCSprite, 0, 0, SRCINVERT);

可以看出,在GDI中实现精灵动画是个比较复杂的过程,主要原因就是因为GDI的Blt函数不支持透明方式。在下面我们将看到,在DirectDraw中,由于其blit函数支持关键色,可以实现透明blit,所以,与GDI相比,在DirectDraw中实现精灵动画要变得简单得多。

12、关键色(Color Key)

DirectDraw的blit函数,支持带关键色(Color key)的透明blit操作。这就是说,如果在源页面上指定了一个颜色为关键色,那么在blit操作中,将视具有这种颜色的区域为透明,不会被传送到目标页面上。

所以,不管你的精灵看起来是否象一个矩形,你必须首先使它们可以放在一个合适大小的矩形区域内。然后在这个包含了精灵图象的矩形内,将不属于精灵图象的部分全部使用同一种颜色,或使其在一个颜色范围之内。这个颜色或颜色范围必须是精灵图象中所没有用到的,它就是关键色。

使用IDirectDrawSurface3::SetColorKey函数,将这个关键色设置给该页面。之后,调用IDirectDrawSurface3::Blt或IDirectDrawSurface3::BltFast函数,将该精灵blit到另一个页面上去,矩形区域中只有属于精灵的像素被映射了,其余带关键色的像素全被视为透明,对目标页面不会产生任何影响。这种在blit操作中,用于源页面表示透明区域的关键色称为源关键色(Source color key)。

除此之外,你还可以使用一种目标关键色,它所影响的只是目标页面。目标关键色定义了在blit操作中,目标页面上可以被覆盖的像素。举例来说,如果你要设计一个有一棵大树的背景,一个兔子在树后活动,当它经过树的时候,会被树遮住它的一部分或全部,以创造一种前后效果。那么,同样,你应该给背景页面上所有除了树的像素使用关键色,表示只有这些像素才能允许在blit中被使用。使用IDirectDrawSurface3::SetColorKey函数,将这个关键色设置给背景页面。然后调用IDirectDrawSurface3::Blt或IDirectDrawSurface3::BltFast函数并且指定了使用目标关键色,那么,当你把包含了兔子的页面blit到目标页面上大树所在的位置,大树总是完整的,兔子的某部分会被大树遮挡住,就象出现在树后一样,前后效果出现了。

13、补丁(Patching)

在精灵动画的过程中,当你把精灵画到一个新的位置之前,你必须将上一个位置的精灵从背景上抹掉。当然,最简单的做法是莫过于重新刷新整个背景,使其还原,然后再将精灵画上去。但是,这样做会使你失去很多的时间,而时间对于动画来说是非常宝贵的,而且最主要的一点就是,你的屏幕会因为大面积的刷新而闪烁。所以,你应该跟踪精灵所在的上一个矩形位置,然后在画下一个精灵之前,只 24

重画这一个区域。这种方法被形象的称为打“补丁”(patching)”。

要给精灵的上一个位置打上补丁,你必须在上一步绘制该精灵之前,把这个矩形区域的背景内容保存下来,在你要画下一个精灵之前,用这个补丁去还原这块区域。这个过程会进行得非常和谐,因为与刷新整个背景相比,它不会占用太多的时间。

打补丁的方法可以按以下几个步骤进行:

将保存的精灵的上一个位置的背景图象复制到背景上。

把将要绘制精灵的位置的背景图象保存下来。

将精灵blit到背景图象上。

重复步骤1

14、范围检查(Bounds Checking)与碰撞检测(Hit Detection)

范围检查和碰撞检测是编程中与精灵动画相关联的两个重要任务。

范围检查用来限制精灵的可能的活动范围,例如,在程序中,你可能想让某一个精灵限制在屏幕的某个矩形区域内活动,要完成这一步,你应该在精灵每移动一步之前检查精灵所在的位置,使这个位置的坐标保持在一个矩形范围之内(通常是一个RECT结构),并且阻止精灵移出这个矩形区域。DirectDraw并没有提供范围检查的服务函数,但是你可以用很简单的程序实现这样一个功能。

碰撞检测,或称为冲突检测,用来判断某一时刻是否有多个精灵处于同一位置。大多数的碰撞检测是检查某个精灵的包络矩形是否与另一个精灵的包络矩形有重合部分。因为有太多种不同的方法来实现不同种类的碰撞检测,所以DirectDraw也没有为用户提供碰撞检测的服务函数。用户可以根据自己的需要编制碰撞检测的程序。

DirectDraw架构

这一章介绍了DirectDraw与操作系统和系统硬件之间的关系。包含以下主题:

DirectDraw结构概览 DirectDraw对象类型 硬件抽象层(HAL) 软件仿真层(HEL)

DirectDraw架构概览

多媒体应用程序及游戏需要高表现力的图形引擎。Microsoft公司通过DirectDraw,为广大开发者提供了一个比GDI层次更高、功能更强、操作更有效、速度更快的应用程序图象引擎,与此同时,也努力使其保持了设备无关的优良特性。DirectDraw主要提供了完成以下任务的工具。

管理多页面

直接访问视频RAM 换页(Page flipping) 后台缓存(Back buffering) 管理调色板(Palette)

25

裁剪(Clipping) 视频端口(Video port)

除此之外,DirectDraw允许开发者在应用程序运行期测定显示硬件的特性,然后,充分利用主机硬件设备的加速特性为用户提供可能的最优的显示速度和效果。

与DirectX其它组件一样,只要可能,DirectDraw就会最高程度的利用硬件执行某特定功能,并且让那些该硬件还不支持的特性也能用软件仿真的方式加以实现。设备无关性通常是通过硬件抽象层(HAL:Hardware abstraction layer)实现的,要得到更多的关于HAL的资料,请参阅“硬件抽象层(HAL)”。

DirectDraw是通过基于COM的接口提供服务。在DirectX 5.0版本中,这些接口分别是:IDirectDraw2、IDirectDrawSurface3、IDirectDrawPalette、IDirectDrawClipper和IDirectDrawVideoPort。DirectX的这些组件是向下兼容的,它们仍然支持旧版本中的所有功能。要得到更多的关于COM的概念,以有助于理解和创建DirectX应用程序,请参阅“DirectX与部件对象模型(COM)”。

DirectDraw对象代表显示适配器,并且通过IDirectDraw或IDirectDraw2接口将其函数性暴露于开发者。在大多数情况下,开发者使用DirectDrawCreate函数创建一个DirectDraw对象,但也可以通过使用CoCreateInstance COM函数创建之。要得到更的资料,请参阅“用CoCreateInstance创建DirectDraw对象”。

在DirectDraw对象创建好之后,你可以通过使用IDirectDraw2::CreateSurface方法为该DirectDraw对象创建页面。页面代表了位于显示硬件上的内存,但是它既可以存在于视频RAM,也可以存在于系统RAM中。DirectDraw还扩展了对调色板、裁剪(主要用于基于窗口的应用程序)和视频端口(Video

port)的支持。

DirectDraw的对象类型

你可以将DirectDraw视为由若干个协同工作的对象所组成。DirectDraw所使用的对象有以下五个:

对象

DirectDraw

含义

DirectDraw对象是DirectDraw应用程序的核心。它是你在建立DirectDraw应用程序时所要创建的第一个对象,再用它来创建所有其它相关的对象。通过调用DirectDrawCreate函数可以创建一个DirectDraw对象。DirectDraw对象通过IDirectDraw和IDirectDraw2接口为开发者提供其函数性。要得到更多的资料,请参阅“DirectDraw对象”。

DirectDrawSurface对象,通常简称为“页面(Surface)”,代表了内存中的一块区域,它存储了可以显示在显示器上的图象数据。通过调用DirectDraw对象的IDirectDraw2::CreateSurface函数可以创建一个与该DirectDraw对象相关联的页面。DirectDrawSurface对象通过IDirectDrawSurface、IdirectDrawSurface2和IDirectDrawSurface3接口为开发者提供其函数性。要得到更多的资料,请参阅“页面”。

DirectDrawPalette对象,通常简称为“调色板(Palette)”,代表了一个可26

DirectDrawSurface

DirectDrawPalette

以为页面所使用的16或256色的调色板。它包含了一组RGB值的索引,用来描述页面上的像素所使用的颜色值。对于像素位深度大于8的页面,不需要使用调色板。通过调用IDirectDraw2::CreatePalette函数,可以创建一个DirectDrawPalette对象。DirectDrawPalette对象通过IDirectDrawPalette接口为开发者提供其函数性。要得到更多的资料,请参阅“调色板”。

DirectDrawClipper DirectDrawClipper对象,通常简称为“裁剪器(Clipper)”,帮助开发者使Blit(位块传送)操作限定在页面的某一区域内,或不超出页面的边界范围。通过调用IDirectDraw2::CreateClipper函数可以创建一个DirectDrawClipper对象。DirectDrawClipper对象通过IDirectDrawClipper接口为开发者提供其函数性。要得到更多的资料,请参阅“裁剪器”。

DirectDrawVideoPort对象代表了当前某些系统上的视频端口(Vedio

port)硬件。这个硬件允许直接的访问帧缓存,而不需要通过CPU或使用PCI总线。通过对DirectDRaw对象调用QueryInterface函数(指定IID_IDDVideoPortContainer标志符),可以创建一个DirectDrawVideoPort对象。DirectDrawVideoPort对象通过IDDVideoPortContainer和IDirectDrawVideoPort接口为开发者提供其函数性。要得到更多的资料,请参阅“视频端口”。

DirectDrawVideoPort

硬件抽象层(HAL:Hardware Abstraction Layer)

DirectDraw通过硬件抽象层(以后简称为:HAL)来提供设备无关的特性。HAL是由设备生产商提供的指定设备的接口,DirectDraw用来直接操作显示硬件。应用程序从来不会直接与HAL打交道,相反,而是与HAL所提供的下属函数打交道。

DirectDraw HAL可以以16位、32位或在Win95中两者兼而有之的形式执行。HAL在WinNT中通常以32位方式执行。HAL可以是显示设备驱动程序的一部分,或独立的DLL,通过驱动程序编写者定义的一个私有接口联系显示驱动。

DirectDraw HAL是由芯片制造商、板卡生产商或原始设备制造商(OEM)实现的。HAL只执行硬件有关代码而不进行仿真。如果硬件不能实现某个功能,HAL不会将其反映在自己的硬件特性中。

硬件仿真层(HEL:Hardware Emulation Layer)

当硬件抽象层(HAL)不支持某种特性时,DirectDraw会试图进行软件仿真。仿真的函数是由硬件仿真层(HEL)提供的。HEL与HAL一样,代表了DirectDraw的特性,并且应用程序从来不直接与HEL一起工作。结果是,DirectDraw对硬件的主要特性都提供了透明的支持,而不管这个特性是通过HAL硬件支持的还是通过HEL软件仿真的。

很显然,软件仿真不能与硬件所提供的特性等效。可以调用IDirectDraw2::GetCaps函数以查询硬件支持什么特性。在应用程序初始化的时候检查这些特性,你可以调整应用程序的参数以提供优化的性能。

在有些情况下,硬件特性与软件仿真的组合操作反而会比单纯使用软件仿真效率更低。例如,如果显示设备驱动程序支持DirectDraw,但不支持带缩放的Blit操作,在从视频RAM页面进行带缩放的Blit操作时,将导致明显的速度降低。这是因为有些视频RAM的速度要比系统RAM慢,迫使在访问视 27

频RAM页面的时候,CPU进入等待状态。如果你的应用程序使用硬件不支持的特性,某些时候,在系统RAM中创建页面更为合适,这样才能避免CPU访问视频RAM时的效率损失。

系统集成

下图展示了DirectDraw,图形设备接口(GDI),硬件抽象层(HAL)和硬件仿真层(HEL)四者之间的关系。

如上图所示,DirectDraw对象与GDI位于同一层次,都通过一个设备相关的抽象层来直接访问硬件设备。与GDI不同的是,DirectDraw会尽可能的利用硬件的加速特性。如果硬件不支持某特性,DirectDraw会使用HEL试图将该特性进行软件仿真。DirectDraw可以以设备环境(DC)的形式提供页面内存,使得开发者可以使用GDI的函数操作页面对象。

控制级(Cooperative Levels)

控制级描述了DirectDraw是怎样与显示设备相互作用的,它如何对系统事件产生反应。使用IDirectDraw2::SetCooperativeLevel函数可以设置DirectDraw的控制级。在很大程度上,开发者使用DirectDraw控制级来决定其应用程序是运行于全屏模式(具有独占的访问视频RAM的特性),还是运行于窗口模式。不管怎样,DirectDraw的控制级具有以下作用。

允许DirectDraw使用Mode X分辨率。要得到更多的信息,请参阅“Mode X 和Mode 13显示模式”。

阻止DirectDraw释放对显示设备的独占控制,或按Ctrl + Alt + Del以重新启动计算机。(仅用于独占模式)

允许DirectDraw对应用程序进行最小化或最大化控制,作为对系统事件的反应。

普通的控制级表明你的DirectDraw应用程序将以窗口的形式运行。在这种控制级下,你将不能改变显示器分辨率,主页面的调色板,或进行换页操作。除此之外,你也不能够调用那些会使视频RAM或视频RAM产生激烈反应的函数,例如:IDirectDraw2::Compact等。

当应用程序为全屏并且独占的控制级时,你就可以充分的利用硬件资源了。在这种控制级下,你可以设置自定义和动态的调色板,改变显示器分辨率,紧凑内存,和实现换页操作等。独占模式(也可称为全屏模式)不会妨碍其它的应用程序分配页面内存,也不会阻止它们使用DirectDraw或GDI的函数性。然而,它的确会阻止除了它自己(为活跃状态时)以外的应用程序改变显示模式或调色板。

因为DirectDraw应用程序可以具有多窗口,所以,在调用IDirectDraw2::SetCooperativeLevel设置控制级时,如果应用程序请求了DDSCL_NORMAL模式(表明应用程序以普通窗口的形式运行),则不 28

需要提供一个指定窗口的句柄。给窗口句柄参数为NULL,所有的窗口的消息进程都可以同时被使用。

IDirectDraw2::SetCooperativeLevel函数在内部捆绑了消息进程和一个窗口句柄。如果IDirectDraw2::SetCooperativeLevel函数在一个进程中被调用了一次,那么,这个进程就会和一个窗口句柄捆绑起来。如果该函数在同一进程中再次被调用,并且指定了另一个合法的窗口句柄,那么会返回一个DDERR_HWNDALREADYSET错误。当DirectSound在设置控制级时指定了与DirectDraw不同的窗口时,有些应用程序也可能会返回这个错误值棗它们必须被设为同一个、顶层的窗口句柄。

显示模式

关于显示模式 测定支持的显示模式 设置显示模式 恢复显示模式

Mode X和Mode 13显示模式 对高分辨率和真彩色的支持

关于显示模式

显示模式指的是显示器的当前设置,描述了显示器的分辨率和位深度,这个信息通常是由显示硬件从主页面传递给显示器的。显示模式是由三个特征定义的:宽、高、位深度。例如,大多数的显示器可以显示宽为640像素、高为480像素的图象,每一个像素的位深度是8。通常我们把这个显示模式称作640x480x8。随着显示模式的尺寸和位深度的增加,它所需要的视频RAM也随之增加。

有两种显示模式:调色板式和非调色板式。对于调色板式显示模式来说,每一个像素的颜色值是以一个相关调色板的索引值来代表。显示模式的位深度决定了调色板中可容纳的颜色数量。举例来说,在8位的调色板显示模式中,每一个像素的值从0到255,该调色板可容纳256个颜色入口。

非调色板式显示模式,就象它的名称所表示的那样,不需要使用调色板。在这种显示模式下,像素的位深度为16、24或32,每个像素分别占用2字节、3字节或4字节,用来描述像素的真实颜色。

主页面、以及在换页链中的所有页面必须符合显示模式的尺寸,位深度和像素格式(请参阅“像素格式”)。

测定支持的显示模式

因为显示硬件(包括显示卡和显示器)的不同,不是所有的显示设备都支持所有的显示模式。要测定某系统所支持的显示模式,应该调用IDirectDraw2::EnumDisplayModes函数。设置正确的参数和标志符,IDirectDraw2::EnumDisplayModes可以列举出该系统所支持的所有的显示模式,或判断是否支持用户所指定的显示模式。该函数的第一个参数,dwFlags,控制该函数的额外选项,在大多数情况下,你应该设置dwFlags为0以表明忽略额外的选项。第二个参数,lpDDSurfaceDesc,是一个DDSURFACEDESC结构的地址,包含了要被测定的显示模式信息,通常,该参数被设为NULL,以列举出该系统所支持的所有显示模式。第三个参数,lpContext,是你想让DirectDraw传递给其回调函数的一个指针,如果在回调函数中不需要任何数据,给该参数值为NULL。最后一个参数,lpEnumModesCallback,一个应用程序定义的回调函数的地址,在DirectDraw每列举出一个显示模式的时候,该回调函数将被调用。

在调用IDirectDraw2::EnumDisplayModes时所提供的回调函数必须符合EnumModesCallback函数的原型。每当找到一个硬件所支持的显示模式的时候,DirectDraw调用该回调函数,并且传递了两个参数。第一个参数是一个DDSURFACEDESC结构的地址,包含了一个支持的显示模式的描述。第二个参数是一个应用程序定义的数据的地址,是在调用IDirectDraw2::EnumDisplayModes时所指定的第三个参 29

数。

检查DDSURFACEDESC结构中的值以获得它所描述的显示模式,关键的成员是dwWidth、dwHeight、和ddpfPixelFormat。dwWidth和dwHeight成员代表了显示模式的长和宽,ddpfPixelFormat成员是一个DDPIXELFORMAT结构的地址,它包含了显示模式的位深度信息。

DDPIXELFORMAT结构不仅包含了显示模式的位深度,还可以告诉你该显示模式是否使用调色板,以及像素格式。如果dwFlags成员包含了PALETTEINDEXED1、DDPF_PALETTEINDEXED2、DDPF_PALETTEINDEXED4、或DDPF_PALETTEINDEXED8标志,显示模式的位深度为1、2、4或8,并且每个像素是一个相关调色板的索引。如果dwFlags成员包含了DDPF_RGB标志,那么该显示模式是非调色板式的,并且它的位深度由DDPIXELFORMAT 结构中的dwRGBBitCount成员所提供。

设置显示模式

你可以用IDirectDraw2::SetDisplayMode来设置显示器的显示模式。该函数的前四个参数用来描述要设置的显示模式的尺寸、位深度以及显示器的刷新率。函数的第五个参数是用来指定额外的选项,目前,唯一可用的标志是DDSDM_STANDARDVGAMODE,它将使显示模式被设为Mode 13,而不是Mode X 320x200x8。如果你要设置另一种分辨率,位深度或Mode X模式,不要使用这个参数,并且将其设为0

尽管你可以指定所需要的显示模式的位深度,但是你不能指定显示硬件的像素格式。要测定显示硬件用于该位深度的RGB位掩码,在设置好显示模式之后,调用IDirectDraw2::GetDisplayMode。如果当前的显示模式不是基于调色板的,你可以从dwRBitMask、dwGBitMask和dwBBitMask中获得掩码值。

要正确的测定red、green和blue的所在的位,请参阅“DirectDraw参考手册”中的“像素格式掩码”。

显示模式可以由多于一个的应用程序改变,只要它们共享同一块显卡。只有当应用程序拥有对DirectDraw对象独占的访问,你才可以改变显示模式的位深度。当显示模式被改变的时候,所有的DirectDrawSurface对象将丢失它们的页面内存,并且对任何操作不起反应。这时,一个页面的内存必须被重新分配,调用IDirectDrawSurface3::Restore函数。

必须重声的是:DirectDraw的独占模式并不阻止其它的应用程序分配DirectDrawSurface对象,也不阻止它们使用DirectDraw或GDI的函数特性。但是,它确实阻止了其它的应用程序对显示模式或调色板的访问。

还原显示模式

在应用程序结束的时候,你可以明确的调用IDirectDraw2::RestoreDisplayMode函数,使显示器还原到原始的显示模式。如果你使用的是IDirectDraw2::SetDisplayMode函数来改变显示模式,并且应用程序具有独占的控制级,那么,当重新设置控制级为普通时,原始的显示模式会自动还原。如果你使用的是IDirectDraw::SetDisplayMode函数,那么你必须明确的调用RestoreDisplayMode以还原显示模式。

ModeX与Mode 13显示模式

DirectDraw同时支持Mode 13和Mode X显示模式。Mode 13是一种线性不可换页的320x200x8的基于调色板的显示模式,因为它的16进制BIOS模式编号是13,而被广泛的称之为Mode 13模式。要得到更多关于它的资料,请参阅“Mode 13的支持”。Mode X是从标准的VGA Mode 13模式演化而来的。通过使用VGA显示适配器的EGA多图象平面系统,它允许开发者使用最多可达256K字节的视频RAM(而Mode 13仅为64K)。

30

在Windows 95系统上,DirectDraw为所有的显示卡提供了两种Mode X模式:320x200x8和320x240x8。某些显卡同样也支持线性低分辨率模式。在这种模式中,主页面可以被锁定和直接访问,这在Mode X模式中是不可能的。

应用程序在调用IDirectDraw2::SetCooperativeLevel函数时,只有使用了DDSCL_ALLOWMODEX、DDSCL_FULLSCREEN、和DDSCL_EXCLUSIVE标志符,才能使用Mode X模式。如果没有指定DDSCL_ALLOWMODEX标志符,IDirectDraw2::EnumDisplayModes将不会列举出Mode X模式,并且调用IDirectDraw2::SetDisplayMode函数以请求一个Mode X模式,将会失败。

Windows 95和Windows NT不直接支持Mode X模式,因此,当你的应用程序处于Mode X模式时,你将无法使用IDirectDrawSurface3::Lock或IDirectDrawSurface3::Blt以锁定或Blit到主页面。你同样也不能对主页面,或GDI的屏幕设备环境使用IDirectDrawSurface3::GetDC函数。Mode X模式是在DDSCAPS结构中由DDSCAPS_MODEX标志符指定的,该结构是DDSURFACEDESC结构的一部分(DDSURFACEDESC结构是由IDirectDrawSurface3::GetCaps和IDirectDraw2::EnumDisplayModes函数返回的)。

目前,Windows NT还不能支持Mode X模式和某些线性低分辨率模式。

对高分辨率和真彩色的支持

DirectDraw支持显示设备驱动所支持的所有屏幕分辨率和色彩位深度。DirectDraw允许应用程序改变显示模式到计算机显示驱动所支持的任何一个模式,包括24或32位色彩模式(也称为真彩色)。

DirectDraw同样也支持对真彩色页面的硬件仿真层(HEL)的Blit操作。如果显示设备驱动支持这些分辨率的Blit操作,那么硬件Blitter(位块传送器)将被用来进行视频RAM对视频RAM的Blit操作。否则,HEL将被用来完成此项操作。

Windows 95和Windows NT允许用户指定它们所使用的显示器类型。DirectDraw核对已知的显示模式与已安装的显示器所限制使用的显示模式。如果DirectDraw发现所请求的模式与显示器不兼容,对IDirectDraw2::SetDisplayMode函数的调用失败。当你调用IDirectDraw2::EnumDisplayModes函数时,只有显示器支持的模式才可以被列举出来。

DirectDraw对象

该节包含了关于DirectDraw对象的信息,以及如何通过IDirectDraw或IDirectDraw2接口对该对象进行操作。探讨了以下主题:

什么是DirectDraw对象? IDirectDraw2接口的新特性? 单进程的多DirectDraw对象

使用CoCreateInstance创建DirectDraw对象

31

什么是DirectDraw对象?

DirectDraw对象是所有DirectDraw应用程序的核心,并且与Direct3D应用程序形成一个整体。它是你要创建的第一个对象,通过它,你可以创建所有其它相关的对象。典型的,通过调用DirectDrawCreate函数可以创建一个DirectDraw对象,它代表了IDirectDraw接口。如果你想使用该接口的另一个更高级的版本(比如:IDirectDraw2接口),以获得更加优秀的性能,你可以请求获得该接口。应注意的是,你可以创建若干个DirectDraw对象,每一个都代表了系统已安装的显示设备。

DirectDraw对象代表显示设备,并且可以利用硬件的加速特性。如果DirectDraw对象所实例化的显示设备具有硬件加速,则该对象是硬件加速的。DirectDraw对象可以创建三种对象:DirectDrawSurface(页面)、DirectDrawPalette(调色板)、和DirectDrawClipper(裁剪器)创建这些对象的函数分别是:IDirectDraw2::CreateSurface、IDirectDraw2::CreatePalette和IDirectDraw2::CreateClipper。

每次,多于一个的DirectDraw对象可以被实例化。最简单的例子是在Windows 95系统上使用两台显示器。尽管Windows 95并不支持双显示器,但为每一种显示驱动程序配置一个DirectDraw硬件抽象层(HAL)是可能的。当缺省的DirectDraw对象被实例化时,Windows 95和GDI将使用它所认识的显示驱动程序。Windows 95和GDI不认识的显示驱动程序可与另外一个设备相匹配,独立的DirectDraw对象必须通过第二个显示驱动程序的全局唯一标志符(GUID)来创建。这个GUID可由DirectDrawEnumerate函数获得。

DirectDraw对象管理它所创建的所有对象。它控制缺省的调色板(如果主页面是8位色彩模式)、缺省的关键色,和硬件显示模式。它跟踪哪些资源已经被分配了,以及哪些资源正有待分配。

IDirectDraw2接口的新特性?

IDirectDraw2接口扩展了原先IDirectDraw接口的函数性:增加了一个IDirectDraw2::GetAvailableVidMem函数。该函数可以询问所有可用的视频RAM容量值,和当前可以为某种指定类型的页面所用的空余视频RAM容量值。

DirectX使用COM模型表明,可以通过提供新的接口而给旧的接口加入新的函数特性。在DirectX3的版本中,IDirectDraw2接口取代了原先的IDirectDraw接口。这个新的接口可以通过调用IDirectDraw::QueryInterface方法来获得,如下例所示:

……

//*********************************************************************

//本例程片段演示如何创建一个IDirectDraw2接口.

//*********************************************************************

LPDIRECTDRAW lpDD;

LPDIRECTDRAW2 lpDD2;

//首先创建一个IDirectDraw接口

ddrval = DirectDrawCreate(NULL, &lpDD, NULL);

if(ddrval != DD_OK)

return;

ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_NORMAL);

if(ddrval != DD_OK)

return;

//获得新的接口

ddrval = lpDD->QueryInterface(IID_IDirectDraw2, (LPVOID *)&lpDD2);

32

if(ddrval != DD_OK)

return;

= DDSCAPS_OFFSCREENPLAIN;

ddrval = lpDD2->GetAvailableVidMem(&ddscaps, &total, &free);

if(ddrval != DD_OK)

return;

这个例子演示了用C++创建一个IDirectDraw接口的方法,然后再调用IDirectDraw::QueryInterface方法来创建一个IDirectDraw2接口。这个新接口包含了IDirectDraw2::GetAvailableVidMem函数,而试图从IDirectDraw接口调用该函数将会在编译的时候导致一个错误。

IDirectDraw2::GetAvailableVidMem是唯一一个被加入IDirectDraw接口的新方法。而且,IDirectDraw2::SetDisplayMode 和 IDirectDraw2::EnumDisplayModes,这两个方法被修改和扩展。

IDirectDraw::SetCooperativeLevel 和 IDirectDraw::SetDisplayMode之间的相互关系与

IDirectDraw2::SetCooperativeLevel 和 IDirectDraw2::SetDisplayMode之间的相互关系有一些改变。如果你使用旧的IDirectDraw接口,而且应用程序通过调用设置有DDSCL_EXCLUSIVE标志的IDirectDraw::SetCooperativeLevel取得了独占(全屏)显示模式,用IDirectDraw::SetDisplayMode来改变模式,再调用设置有DDSCL_NORMAL标志的IDirectDraw::SetCooperativeLevel来释放独占模式,原始的显示模式不会被还原。除非应用程序明确的调用IDirectDraw::RestoreDisplayMode方法或DirectDraw对象被销毁时,原始的显示模式才能恢复。然而,如果你使用新的IDirectDraw2接口,然后按着与上面同样的方法,当DirectDraw对象失去独占模式时,显示器的原始显示模式将会自动被恢复。

因为有些接口可能会因新接口的发布而该动,混合使用一个接口和它的替代者的方法(比如IDirectDraw 与IDirectDraw2)可以导致意想不到的错误。你必须只使用某接口的同一个版本的函数或方法。

单进程的多DirectDraw对象

DirectDraw允许一个进程在需要的时候,可以任意多次的调用DirectDrawCreate函数。每次调用后,返回一个唯一的与设备无关的接口。每一个DirectDraw对象可以随心所欲的使用;在对象与对象之间没有相互依赖的关系。每个对象的行为就象它是由一个唯一的进程创建的。

因为DirectDraw对象之间是不相互依赖的,由一个特定的DirectDraw对象创建的DirectDrawSurface、DirectDrawPalette、和DirectDrawClipper对象不应该与其它的DirectDraw对象一起使用,因为这些对象会在它的DirectDraw对象销毁时而自动被释放。如果它们与其它的DirectDraw对象一起使用,调用它们的函数将在它们被销毁时退出。

例外的是,DirectDrawClipper对象是由DirectDrawCreateClipper函数创建的。这些对象与任何特定的DirectDraw对象是没有关系的,可以与一个或多个DirectDraw对象一起使用。

使用CoCreateInstance创建DirectDraw对象

除了用常规的DirectDrawCreate方法创建一个DirectDraw对象外,你还可以使用CoCreateInstance函数,再调用IDirectDraw2::Initialize来创建一个DirectDraw对象。以下的例程片段描述了这个方法的各步骤。

33

第一步、在程序的最开始调用CoInitialize来初始化COM对象,参数为NULL。

if ( FAILED( CoInitialize( NULL )))

return FALSE;

第二步、然后,调用CoCreateInstance和IDirectDraw2::Initialize来创建DirectDraw对象。

ddrval = CoCreateInstance( &CLSID_DirectDraw,

NULL, CLSCTX_ALL, &IID_IDirectDraw2, &lpdd );

在CoCreateInstance函数中,第一个参数CLSID_DirectDraw,是DirectDraw驱动对象类的类标志符;IID_IDirectDraw2参数指定了要创建的特定的DirectDraw对象;最后的lpdd参数接收创建的对象。如果调用成功,这个函数返回一个没有初始化的对象。

第三步、在你使用这个DirectDraw对象之前,你必须调用IDirectDraw2::Initialize。在此之后,你就可以操作和释放该对象,就象它是用DirectDrawCreate创建的一样。如果在使用之前,你没有调用IDirectDraw2::Initialize,将返回DDERR_NOTINITIALIZED的错误。

if( !FAILED ( ddrval ))

ddrval = IDirectDraw2_Initialize( lpdd, NULL );

第四步、在关闭应用程序之前,使用CoUninitialize来关闭COM。

CoUnitialize();

页面

该节包含了关于DirectDrawSurface对象的信息,探讨了以下主题:

页面的基本概念

什么是页面

页面接口

宽度和宽距

关键色

像素格式

创建页面

创建主页面

创建离屏页面

创建复杂页面和换页链

创建超宽页面

换页

页面丢失

释放页面

更新页面属性

直接访问帧缓存

页面的基本概念

34

使用非本地视频RAM页面

色彩和格式转换

覆盖页面

覆盖页面概览

DDCAPS的重要成员和标志

源和目标矩形

边界和大小限制

最小和最大缩放系数

覆盖页面关键色

覆盖页面的定位

创建覆盖页面

覆盖页面的Z轴次序

覆盖页面的换页

Blit到多窗口

1、什么是页面?

页面,或被我们称作DirectDrawSurface对象,代表了内存里的一个连续的线性的数据区。这个数据区可以被代表显示硬件的DirectDraw对象所识别和确认。通常,DirectDrawSurface对象被置于显卡上的视频RAM中,而这并不是绝对的。除非明确的指定是在视频RAM还是系统RAM中创建DirectDrawSurface对象,DirectDraw可以将其放置在其中任一位置,条件是这样可以获得最佳性能。

DirectDrawSurface对象可以从显卡上的特效处理器上获得好处,不仅仅是通常意义上的加快处理速度,而是可以与系统CPU并行工作,以达到最优的效率和速度。

调用IDirectDraw2::CreateSurface函数可以创建若干类型的DirectDrawSurface对象,包括最简单的单页面对象,复杂的由若干个页面组成的换页链,以及三维页面等等。CreateSurface函数创建我们所请求的页面或换页链,并且返回指向主页面的IDirectDrawSurface接口的指针,通过该接口可以暴露DirectDrawSurface对象的函数性。如果你想使用该接口的较高级的版本,如IDirectDrawSurface3,你也可以询问系统并且得到它。

IDirectDrawSurface3接口通过Blit函数可以使你间接的访问页面内存,例如:IDirectDrawSurface3::BltFast函数。DirectDrawSurface对象可以创建Windows的GDI设备环境句柄(HDC),这样,就可以允许使用Win32的API函数来访问代表DirectDrawSurface对象的页面。GDI识别这些HDC(设备环境句柄),如果它们存在于视频RAM中,那么就可以获得硬件的加速特性。除此之外,你还可以使用IDirectDrawSurface3接口的函数直接访问页面内存。例如:可以使用IDirectDrawSurface3::Lock函数锁定页面内存,并且获得指向该页面上相应区域(用户指定的矩形区域)的内存区的地址。视频RAM上的地址可以指向可见的祯缓存(存储了当前显示画面的缓冲区,也称作主页面),也可以是不可见的缓存(离屏页面或覆盖页面)。不可见的缓存通常被置于视频RAM中,但是如果是受硬件限制或DirectDraw正以仿真模式运行,它也可以被置于系统RAM中。IDirectDrawSurface3接口还扩展了另外一些函数,比如可以用来设置或获得调色板的函数,专门用于某特定类型页面的函数(如换页链或覆盖页面)。

从下面这个例图中,你可以看到所有的DirectDrawSurface页面对象都是由DirectDraw对象创建的,并且与调色板协同工作。尽管每一个页面对象都可以被分配一个调色板,除了像素格式的位深度小于等于8的主页面以外,调色板并不总是必须的。

2、页面接口

前面已经提到过,DirectDrawSurface对象是通过IDirectDrawSurface、IdirectDrawSurface2和IDirectDrawSurface3接口来暴露其函数性的。接口的每一个新的版本与旧的版本相比,除了提供所有原有的函数并且扩充其功能之外,还提供了一些新的函数。

三种接口中,IDirectDrawSurface接口是最早的一个版本,当你调用IDirectDraw2::CreateSurface函数时,系统会为你缺省的创建一个该接口的页面对象。要利用新版接口的函数性,你必须通过调用QueryInterface函数来询问是否存在新版本,并获得它。下面的例程为你展示了这是怎样完成的。

LPDIRECTDRAWSURFACE lpSurf;

LPDIRECTDRAWSURFACE2 lpSurf2;

//填充页面结构

memset(&ddsd, 0, sizeof(ddsd)); //调用Win32 API函数清空ddsd结构

= sizeof(ddsd);

s = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;

= DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;

35

h = 10;

ht = 10;

//创建页面,该页面使用IDirectDrawSurface接口

ddrval = lpDD2->CreateSurface(&ddsd, &lpSurf, NULL);

if(ddrval != DD_OK)

return;

//询问IDirectDrawSurface3接口

ddrval = lpSurf->QueryInterface( IID_IDirectDrawSurface3, (LPVOID *)&lpSurf2);

if(ddrval != DD_OK)

return;

//调用IDirectDrawSurface3接口特有的函数

ddrval = lpSurf2->PageLock(0);

if(ddrval != DD_OK)

return;

ddrval = lpSurf2->PageUnlock(0);

if(ddrval != DD_OK)

return;

上面的例子通过调用QueryInterface函数(指定IID_IDirectDraw2引用标志符)获得一个DirectDrawSurface对象的IDirectDrawSurface3接口。要得到IDirectDrawSurface3接口,使用IID_IDirectDrawSurface3引用标志符即可。

3、宽度(Width)和宽距(Pitch)

如果你的应用程序要写视频RAM,内存中的位图并不需要占据连续的内存块。在这种情况下,一条线的width和pitch含义是不同的。width是指内存中位图的一条线的开始和结束位置的内存地址之差。这个距离只代表了内存中位图的宽度,它不包括位图中到达下一条线开始位置所需要的任何额外的内存。pitch是指内存中位图的一条线到下一条线开始位置的内存地址之差。

对矩形内存来说,比如,视频RAM的pitch将包括位图的宽度加上一部分缓存。下面的例图表示了矩形内存中width和pitch的区别。

在这个例图中,前台缓存和后台缓存大小都是640x480x8,高速缓存是384x480x8。要到达下一条线的地址,你必须在640后加上384,得到1024,这就是下一条线的地址。

因此,当直接向页面内存中着色时,一般用IDirectDrawSurface3::Lock(或IDirectDrawSurface3::GetDC)方法返回的pitch值。不要认为pitch只是基于显示模式的。如果你的应用程序在某些显示器上发生显示混乱,这多半是因为pitch使用错误造成的。

4、关键色

DirectDraw支持带源或目标关键色的Blit操作和覆盖页面。这个关键色可以是单个的颜色值,也可以是一个颜色范围。要得到关于关键色的详细介绍,请参阅前一章的“关键色”一节。通过调用 36

IDirectDrawSurface3::SetColorKey函数,可以为一个页面设置一个关键色。

源关键色(Source color key)指定了一个颜色或一个颜色范围,在Blit过程中,不被复制,或在覆盖页面中,对目标层来说是不可见的。目标关键色(Destination color key)指定了一个颜色或一个颜色范围,在Blit过程中,将被替换,或在覆盖页面中,将被目标层所覆盖。源与目标关键色的一个显著的区别就是:源关键色指定了在源页面上什么是可以读和什么是不可以读的;目标关键色指定了在目标页面上,什么是可以写和什么是不可以写的。如果目标页面有关键色,则只有那些符合关键色的像素可以被改变(在Blit操作中),或被覆盖(在覆盖页面中)。

除了与Blit相关的关键色之外,覆盖页面还可以使用覆盖关键色。要得到更多信息,请参阅“覆盖关键色”。

有些硬件只支持YUV像素数据的颜色范围。YUV数据通常用于视频显示的像素格式,并且其透明背景不是一个特定颜色而导致在数值转换过程中发生错误。所以,只要可能,就应该将数据写到一个特定的透明颜色上,而不管它是什么像素格式。

关键色是按页面的像素格式指定的。如果一个页面是调色板格式,关键色是以一个调色板索引或一组调色板索引指定的。如果页面的像素格式是按FOURCC代码指定的,描述了一个YUV格式,YUV关键色是由DDCOLORKEY结构的dwColorSpaceLowValue 和dwColorSpaceHighValue成员的低三位字节指定的。最低的字节包含V数据,下一个包含U数据,第三个包含Y数据。IDirectDrawSurface3::SetColorKey的dwFlags参数指定了关键色是用在Blit操作中还是覆盖页面中,以及它是源还是目标关键色。以下是一些合法的关键色的例子。

8位调色板模式:

//调色板登录项26是关键色。

dwColorSpaceLowValue = 26;

dwColorSpaceHighValue = 26;

24位真彩模式:

//(255,128,128)颜色是关键色

dwColorSpaceLowValue = RGBQUAD(255,128,128);

dwColorSpaceHighValue = RGBQUAD(255,128,128);

FourCC YUV模式:

//只要Y在100和110之间,并且U或V在50和55之间的的任何一个YUV颜色为透明。

dwColorSpaceLowValue = YUVQUAD(100,50,50);

dwColorSpaceHighValue = YUVQUAD(110,55,55);

5、像素格式

像素格式规定了页面内存中的每个像素的数据是怎样进行编码的。DirectDraw使用DDPIXELFORMAT结构来描述各式各样的像素格式(请参阅“DirectDraw参考手册中关于该结构的帮助”)。DDPIXELFORMAT结构中的成员包含了各种像素格式相互区别的以下几个显著的特点:

像素格式是基于调色板的还是非调色板式的

如果是非调色板式,像素是RGB,还是YUV格式

位深度

位掩码

37

通过调用IDirectDrawSurface3::GetPixelFormat函数,你可以获得关于当前页面的像素格式的信息。

创建页面

DirectDrawSurface对象代表了一个页面,调用IDirectDraw2::CreateSurface函数可以创建一个DirectDrawSurface对象,也可以同时创建由若干个页面组成的复杂页面结构,最典型的就是换页链。调用该函数时,需要提供要创建的页面的描述,如页面的尺寸、是单个页面还是复杂页面、所采用的像素格式(如果页面将不使用索引调色板)等等。所有这些描述信息存储在一个DDSURFACEDESC结构中,在调用CreateSurface函数时必须提供。如果硬件不支持所请求的页面特性,或是要创建的页面已经存在,则该函数返回一个错误。

要创建单个的页面或若干个页面其实是一项并不复杂的工作,只需要少许的几行代码即可。主要有以下四种类型的页面可以被创建:

创建主页面

创建离屏页面

创建复杂页面和换页链

创建超宽页面

在缺省的情况下,DirectDraw试图在本地的视频RAM中创建页面。如果恰好没有足够的本地(local)视频RAM来容纳要创建的页面的话,DirectDraw将试图使用非本地(non-local)的视频RAM(在某些装备了AGP设备的系统上),如果仍旧无法实现,那么DirectDraw将只能将其创建于系统RAM中。当然,在调用CreateSurface函数时,你也可以在相关的DDSCAPS结构中指定适当的标志符向DirectDraw明确的表明你想将页面置于哪种类型的内存中。

创建主页面

主页面(primary surface)代表的是在显示器的当前可见屏幕,它在页面描述中具有PRIMARYSURFACE标志符。对于每一个DirectDraw对象来说,你只可能拥有一个主页面。

主页面对用户来说是可见的。当你创建一个主页面时,实际上,你创建的这个DirectDrawSurface对象,访问的是由GDI正在使用的已经可见的页面(即显示屏幕),主页面的大小以及像素格式暗中符合当前显示器的显示模式。因此,尽管创建所有其它类型的页面要求填充DDSURFACEDESC结构的dwHeight 和 dwWidth值以及像素格式,而创建主页面时一定不能自己指定它们,甚至你知道它们与当前屏幕是同样大小,否则,CreateSurface函数将调用失败,并且返回DDERR_INVALIDPARAMS。

要创建一个主页面,DDSURFACEDESC 结构(以下为ddsd )的成员是如下填充的。

DDSURFACEDESC ddsd;

= sizeof(ddsd);

//告诉DirectDraw哪些成员是可用的。

s = DDSD_CAPS;

//请求一个主页面

= DDSCAPS_PRIMARYSURFACE;

然后,就可以调用CreateSurface函数并且提供了这个页面描述,如果函数调用成功,可以返回一个指 38

向主页面的指针。如果你想要得到关于这个主页面的尺寸和像素格式信息,应该调用IDirectDrawSurface3::GetSurfaceDesc函数。相关信息,请参阅“显示模式”。

创建离屏页面

离屏页面(off-screen surface),通常被用来存储位图,用于后来的将位图图象Blit到主页面或后台缓存上。因为离屏页面是一个相互独立的页面,不与任何对象产生隶属关系,所以你必须指定你所要创建的离屏页面的大小,这是通过在DDSURFACEDESC结构中包含进DDSC_WIDTH和DDSD_HEIGHT标志符,并且给dwWidth和dwHeight成员填充正确的参数完成的。除此之外,你还必须包含进DDSCAPS_OFFSCREENPLAIN标志符以表明创建的是一离屏页面。

在缺省的情况下,DirectDraw将会在视频RAM中创建离屏页面,除非视频RAM容量不够,那么,DirectDraw将会把离屏页面置于系统RAM中。你可以在DDSURFACEDESC结构的dwCaps成员中包含进DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY标志符,以明确的表明你希望将页面置于何处。如果DirectDraw不能满足你所提供的要求,CreateSurface函数将调用失败,并且返回错误。

下面面的例程展示了要创建一个离屏页面,DDSURFACEDESC 结构(以下为ddsd )的成员是如何填充的。

DDSURFACEDESC ddsd;

= sizeof(ddsd);

//告诉DirectDraw哪些成员是可用的

s = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;

//请求一个离屏页面,大小为100x100

//(这假定了要创建的离屏页面的像素格式将符合主页面的像素格式)

= DDSCAPS_OFFSCREENPLAIN;

dwHeight = 100;

dwWidth = 100;

除此之外,你也可以创建一个页面的像素格式与主页面不同的离屏页面。然而,在这种情况下,有一个缺点棗该离屏页面将只能被限制于系统RAM中。下面的例程片段展示了如何准备DDSURFACEDESC结构的各成员,以用于创建一个8位的调色板式页面(假定当前的显示模式不是8位格式)。

ZeroMemory(&ddsd, sizeof(ddsd));

= sizeof(ddsd);

s = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;

= DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;

ht = 100;

h = 100;

= sizeof(DDPIXELFORMAT);

s = DDPF_RGB | DDPF_PALETTEINDEXED8;

// 设置页面的位深度为8,但是绝对不要设置任何RGB mask值,

39

// 因为对调色板式页面来说,该值一定是0。

itCount = 8;

在DirectX以前的版本中,离屏页面的宽度的最大值被限制于主页面的宽度值之内。而在DirectX 5.0版中,你可以随心所欲的创建任何宽度的离屏页面,如果显示硬件能够承受的话。在请求一个超宽离屏页面时,要小心的是,如果显卡上的内存不能够容纳该页面,页面将被置于系统RAM中。如果你明确的指定在视频RAM中创建超宽页面,而硬件又无法承受的话,调用失败。要得到更多关于超宽页面的信息,请参阅“创建超宽页面”。

创建复杂页面和换页链

除了上面介绍的由单独的一个页面组成的主页面和离屏页面之外,你还可以创建由若干个页面组成的复杂页面(complex surfaces),它同样也是由一步调用IDirectDraw2::CreateSurface函数所创建的。如果你在页面描述中设置了DDSCAPS_COMPLEX标志符,那么在调用CreateSurface函数后,DirectDraw除了创建你所明确要创建的页面之外,还将暗中的为你创建一个或多个附加页面。对复杂页面的管理与对单页面的管理基本上是没有区别的:一步调用IDirectDraw::Release函数将释放复杂页面中所有的页面,并且一步调用IDirectDrawSurface3::Restore函数将恢复所有页面。然而,这些暗中创建的页面不能被脱离,即解除隶属关系,要得到更多的信息,请参阅“DirectDraw参考手册”中关于IDirectDrawSurface3::DeleteAttachedSurface函数的帮助。

你所能创建的最常用的复杂页面之一就是换页链(flipping chain)。通常,一个换页链是由一个主页面以及隶属于它的若干个后台缓存组成。DDSCAPS_FLIP标志符表明页面是一个换页链的一部分。创建一个换页链的页面描述中,同样必须包含进DDSCAPS_COMPLEX标志符。

下面的例程片段展示了要创建一个换页链结构,如何填充页面描述的各成员。

DDSURFACEDESC ddsd;

= sizeof(ddsd);

// 告诉DirectDraw哪些成员是可用的

s = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

// 请求一个主页面,以及后台缓存数量为1

= DDSCAPS_COMPLEX | DDSCAPS_FLIP | DDSCAPS_PRIMARYSURFACE;

BufferCount = 1;

上面的例程构造了一个双缓冲区换页(double-buffered flip)环境:单步调用IDirectDrawSurface3::Flip函数可以交换主页面和后台缓存。如果给DDSURFACEDESC 结构的dwBackBufferCount成员设置了2,将会创建两个后台缓存,在每次调用Flip函数时,主页面将在三个页面间循环,这就构成了一个三缓冲区换页(triple-buffered flip)环境。

创建超宽页面

DirectDraw允许你在显卡内存中创建超宽离屏页面(页面的宽度大于主页面)。这只在显示设备支持超宽页面的情况下才能实现。

40

要判断DirectDraw是否支持超宽页面,调用IDirectDraw2::GetCaps函数,检查你所提供的第一个DDCAPS结构的dwCaps2成员中是否存在DDCAPS2_WIDESURFACES标志。如果存在,表明你的DirectDraw支持超宽页面。

如果你试图在视频RAM中创建一个宽度大于主页面的页面,而DDCAPS2_WIDESURFACES标志并不存在,函数调用将失败,并且返回一个DDERR_INVALIDPARAMS错误。

超宽页面通常被系统RAM页面、视频端口页面、和可执行缓存所支持。

换页

DirectDraw里的任何页面都可以构造为换页页面(Flipping surface)。一个换页页面是位于内存里的任何一个可以在前台缓存(front buffer)和后台缓存(back buffer)之间交换的页面,这个换页环境就是我们所称的换页链(flipping chain)。通常,前台缓存指的就是主页面,当然,这并不是绝对的。

典型的,当你调用IDirectDrawSurface3::Flip函数以请求一次换页操作,指向主页面和后台缓存的指针相互交换。这就是说,换页的操作,是通过交换显示设备用来代表页面内存的指针,而不是通过相互复制页面的实际内存来完成的。但是,也有例外的时候,那就是当DirectDraw以仿真的方式进行换页操作时,在这种情况下,它所做的就是简单的相互复制页面内存。只有在后台缓存不能容纳进视频RAM,或硬件不支持DirectDraw的时候,DirectDraw才会以仿真的方式进行换页操作,当然,这只是极其少见的情况。

当换页链中包含了一个主页面和一个以上的后台缓存时,在换页操作中,指向它们的指针将按前后顺序依次转换。如下图所示:

隶属到DirectDraw对象上的其它类型的页面,只要不是换页链中的一部分,在换页过程中都不会受到任何影响。

请牢记,DirectDraw进行换页,是通过交换指向DirectDrawSurface对象的指针。而不是交换DirectDrawSurface对象本身。这意味着,在任何类型的换页方案中,如果你想将图象Blit到后台缓存,你所使用的始终是同一个DirectDrawSurface对象,而不用去考虑原先的后台缓存已经换页到哪儿了。同样的,你应该始终使用主页面作为Flip函数的调用者,以完成一次换页操作,而不用去管最开始的主页面换页到哪儿了。

当换页对象是可见的页面,比如主页面换页链或一个可见的覆盖页面换页链,进行换页的Flip函数与系统CPU是异步执行的。这就是说,在这些可见的页面上,调用Flip函数,它只是简单的告诉显示硬件该进行换页了,并不需要等待换页操作在硬件设备中实际完成后才返回。这是因为显示硬件(显示器)只有在完成一次垂直刷新后才能进行一次换页。所以,Flip函数调用成功,并不意味着换页已经完成,在实际的换页操作进行之前,对即将成为主页面的后台缓存是不能锁定和进行Blit操作的,如果在这时调用以下这些函数,调用将失败,并且返回DDERR_WASSTILLDRAWING的错误,如IDirectDrawSurface3::Lock、IDirectDrawSurface3::Blt、IDirectDrawSurface3::BltFast和IDirectDrawSurface3::GetDC。但是,对于三缓冲区换页环境,最后一个后台缓存仍是可用的。

要让Flip函数成为与系统CPU同步的操作,在调用时指定DDFLIP_WAIT标志即可。

页面丢失

41

当代表页面内存的DirectDrawSurface对象被不得已的释放时,与该对象相关联的页面内存也会被释放。当一个DirectDrawSurface对象丢失其页面内存的时候,它的许多函数将返回DDERR_SURFACELOST,并且不进行任何其它操作。

页面可能被丢失是因为:.显示设备(显示器)显示模式的改变,或另一个应用程序获得了对显卡的独占访问模式,并且释放了显卡上当前被分派其它应用程序的所有页面内存。对页面调用IDirectDrawSurface3::Restore方法可以为这些丢失了内存的页面重新分配内存,并且将这些内存与DirectDrawSurface对象联系上。重建内存并不会使以前存在于该页面上的图象重新显现出来,因此,如果你的页面丢失了其内存,在调用Restore函数重建之后,必须亲手重新绘制所有的图象。

要得到更多资料,请参阅“设置显示模式”。

释放页面

与所有的COM接口一样,在你不再需要某页面的时候,你可以通过调用Release方法释放它。

每一个单独创建的页面必须逐个的明确的释放掉。然而,如果页面是通过单步调用IDirectDraw2::CreateSurface或IDirectDraw::CreateSurface函数创建一个多页面结构(例如一个换页链)时暗中形成的,那么,你只需要明确的释放前台缓存就可以了。在这种情况下,所有的后台缓存都被暗中的释放了,指向它们的指针将不再合法。

更新页面属性

你可以通过调用IDirectDrawSurface3::SetSurfaceDesc函数来更新一个现存页面的属性。有了这个函数,你可以更改页面的像素格式,还可以使该DirectDrawSurface对象指针重定位,使其指向一块应用程序已经明确分配了的系统RAM。这是很有用的,因为它使得你的页面可以直接使用一个已经存在的缓冲区的数据,而不用进行复制操作。新的页面内存是由客户程序所分配,同样的,这些内存也必须由客户程序释放掉。要得到更多关于如何使用SetSurfaceDesc函数的资料,请参阅“DirectDraw参考手册”中关于此函数的帮助。

在调用IDirectDrawSurface3::SetSurfaceDesc函数时,lpddsd参数必须是一个DDSURFACEDESC结构的地址,描述了新的页面内存并且提供了指向该内存的指针。在这个结构中,你只能设置dwFlags成员为反映了页面内存的地址、大小、宽距、和像素格式的标志符。因此,dwFlags只能是以下标志符的集合:DDSD_WIDTH、DDSD_HEIGHT、DDSD_PITCH、DDSD_LPSURFACE、和DDSD_PIXELFORMAT。

在向DDSURFACEDESC结构中填充数据之前,你必须为新的页面分配内存。你所分配的内存的大小是非常重要的,它不仅要能容纳满足页面的长和宽所需要的内存,还必须能够容纳页面的宽距,宽距必须是WORD(8位)的倍数。应该注意的是,宽距是以字节为单位,而非像素。

在向DDSURFACEDESC结构中填充数据的时候,lpSurface成员是一个指向你刚分配的内存的指针,并且dwHeight和dwWidth成员描述了页面的大小(以像素为单位)。如果你指定了页面的大小,你还必须填充lPitch成员以反映页面宽距的大小。Pitch必须是DWORD的倍数。同样的,如果你指定了宽距,你还必须为其指定一个宽度值。最后,ddpfPixelFormat成员描述了页面的像素格式。如果你没有给这些成员指定新的值,那么,SetSurfaceDesc函数将使用当前页面的原始值,只有lpSurface成员是例外。

42

在使用IDirectDrawSurface3::SetSurfaceDesc方法的过程中,你还应当注意到这样一些细节,当然,它们只是常识。举例来说,DDSURFACEDESC结构的lpSurface成员必须是一个指向系统RAM的合法的指针(SetSurfaceDesc函数目前还不支持指向视频RAM的指针)。同样,dwWidth和dwHeight成员的值不能为0。最后一点,你不能为主页面或换页链中的任何页面调用此函数。

你可以将同一块内存设置给若干个DirectDrawSurface对象,但是,你必须注意到,这块内存被所有的页面对象所使用,它不会因为某一个页面的释放而被释放掉。

不正确的使用SetSurfaceDesc函数将导致不可预知的行为。因为DirectDrawSurface对象不会释放并不是它分配的页面内存,因此,当页面内存不再需要的时候,将其及时的释放掉是你的责任。但是,不管怎样,当SetSurfaceDesc函数被调用的时候,DirectDraw将释放掉该页面在创建的时候被暗中分配的原始的页面内存。

直接访问帧缓存

一个DirectDrawSurface对象允许应用程序通过调用IDirectDrawSurface3::Lock锁定页面以获得对页面内存的直接的访问。当应用程序调用这个函数的时候,需要给lpDestRect参数提供一个指向RECT结构的指针,描述了页面中你所想要直接访问的矩形区域。如果应用程序需要访问整个页面,设置这个参数为NULL即可。两个线程或进程可以同时锁定同一个页面上的若干个矩形区域,条件是这些矩形区域没有相互重叠。

Lock函数调用成功的话,将填充一个DDSURFACEDESC结构,描述了你要正确的访问页面内存所需要的所有信息。如果页面的像素格式与主页面的不一样,该结构中还包含了关于页面的宽距(pitch)和像素格式的信息。当应用程序结束了对页面内存的访问,可以调用IDirectDrawSurface3::Unlock以解锁页面。

当你锁定了一个页面,你就可以对页面内存中的数据进行直接的操作。以下介绍了一些小技巧,可以避免在页面被锁定的过程中,直接向页面内存进行绘图的时候发生的绝大多数一般的错误。

决不要假想页面的宽距(pitch)为一恒定值,每次调用IDirectDrawSurface3::Lock函数的时候都要检查返回信息中的宽距值。这个值的改变可以有各种各样的原因,包括页面内存在内存中的位置,显卡的类型,甚至是DirectDraw引擎的版本。

确保你要进行Blit操作的目标页面是没有被锁定的。如果对一个锁定的页面调用DirectDraw的Blit函数,调用将失败,并且返回DDERR_SURFACEBUSY或DDERR_LOCKEDSURFACES错误。相似的,如果对视频RAM中一个锁定的页面调用了GDI的Blit函数,调用也将失败,但不会返回错误值。

尽量减少你的程序在IDirectDrawSurface3::Lock 和IDirectDrawSurface3::Unlock之间的执行活动。在一个页面被锁定的过程中,DirectDraw通常控制住Win 16锁,于是使得对页面内存的访问得以安全的进行。Win 16锁使对GDI和USER的访问串行化,在Lock和UnLock函数的调用过程中,暂停执行Windows。IDirectDrawSurface3::GetDC函数暗中的调用了IDirectDrawSurface3::Lock以锁定页面,并且IDirectDrawSurface3::ReleaseDC函数暗中的调用了IDirectDrawSurface3::Unlock以使页面解锁。

对齐复制到显示存储器。Windows 95使用了一个页错误处理器棗Vflatd.386文件,来为具有堆交换存储器的显示卡实现虚拟的平面桢缓冲区。该处理器允许这些显示设备为DirectDraw提供线性桢缓冲区。如果复制是跨存储堆的,非对齐复制到显示存储器会导致系统挂起。

锁定页面通常导致DirectDraw控制住Win 16锁。在Win 16锁被控制期间,所有其它的应用程序,包括Windows系统,都会暂停执行。因为这个原因,标准的调试器在此期间都不可能工作,但是只有 43

内核(kenerl)调试器仍可以正常工作。

如果在你调用IDirectDrawSurface3::Lock函数的时候,对于该页面的一次Blit操作还在进行之中,函数将立即返回一个错误值。要防止这种情况的发生,可以在调用Lock函数的过程中指定DDLOCK_WAIT标志,以表明该函数将等待,直到成功的获得锁定之后才返回。

使用非本地视频RAM页面

DirectDraw支持高级图形端口(AGP)结构,所以它可以在非本地的视频RAM中创建页面。在装备了AGP的系统中,如果本地的视频RAM被耗尽,或用户明确的请求非本地的视频RAM,DirectDraw将使用非本地的视频RAM,这依赖于当时的AGP执行模式。

目前,有两种AGP结构的执行模式,这就是通常所说的“执行模式”(execute model)和“DMA模式(DMA model)”。在执行模式中,对于非本地的视频RAM和本地的视频RAM,显示设备支持同样的特性。因此,当你调用IDirectDraw2::GetCaps方法以获取硬件特性的时候,在DDCAPS 结构的dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps、和dwNLVBRops成员中所包含的与Blit操作相关的标志符,与本地视频RAM的那些标志符是一样的。在执行模式中,如果本地的视频RAM被耗尽,DirectDraw将自动的退回到使用非本地的视频RAM,除非调用者明确的指定使用本地的视频RAM。

在DMA模式执行过程中,对非本地视频RAM的Blit操作和材质贴图(texturing)的支持是有限度的。如果显示设备使用DMA模式,当你询问设备特性的时候,dwCaps2成员将会被设置了DDCAPS2_NNLOCALVIDMEMCAPS标志。DDCAPS结构中的dwNLVBCaps、dwNLVBCaps2、dwNLVBCKeyCaps、dwNLVBFXCaps、和dwNLVBRops成员中所包含的Blit相关的标志描述了DirectDraw所支持的特性;这些特性通常是本地的视频RAM页面所支持的特性的一个子集。DMA模式下,当本地的视频RAM被耗尽的时候,对于材质页面,DirectDraw将自动的退回到使用非本地的视频RAM,除非调用者明确的请求使用本地的视频RAM。材质页面是唯一的一种会被这样处理的页面,所有其它类型的页面不能被创建在非本地的视频RAM中,除非调用者明确的指定。

DMA执行模式对材质贴图的支持,因非本地视频RAM页面的不同而不同。如果驱动程序支持从非本地视频RAM页面的材质贴图,当你调用IDirect3DDevice2::GetCaps方法询问3-D驱动程序的特性的时候,D3DDEVCAPS_TEXTURENONLOCALVIDMEM标志符将会被设置其中。

色彩和格式转换

非RGB色彩空间的页面格式是由四字符代码(FOURCC codes)定义的。如果应用程序调用IDirectDrawSurface3::GetPixelFormat方法询问页面的像素格式,并且该页面是一个非RGB页面,那么DDPF_FOURCC标志符将会被设置,并且DDPIXELFORMAT结构中的dwFourCC成员的值是有效的。如果页面的四字符代码(FOURCC code)代表了一个YUV格式,那么DDPF_YUV标志符也将被设置,并且dwYUVBitCount、dwYBits、dwUBits、dwVBits、和dwYUVAlphaBits成员的值是有效的,它们可以被用来提取出页面像素格式的信息。

如果当前的页面是RGB格式,那么DDPF_RGB标志符将被设置,并且dwRGBBitCount、dwRBits、dwGBits、dwBBits、和dwRGBAlphaBits成员的值是有效的,它们可以被用来提取出页面像素格式的信息。DDPF_RGB标志符可以与DDPF_FOURCC标志符一起使用,如果被描述的页面是非标准的 44

RGB格式。

在色彩和格式转换过程中,两套四字符代码可以为应用程序所使用。一套代表了Blit硬件的特性,另一套代表了覆盖硬件的特性。

要得到更多的信息,请参阅“DirectDraw参考手册”中的“四字符代码”部分的帮助。

覆盖页面

该节包含了关于DirectDraw支持覆盖页面的资料。讨论了以下主题:

覆盖页面概览

DDCAPS的重要成员和标志

源和目标矩形

边界和大小限制

最小和最大缩放系数

覆盖页面关键色

覆盖页面的定位

创建覆盖页面

覆盖页面的Z轴次序(Z-Order)

覆盖页面的换页

要得到更多的关于执行覆盖页面的资料,请参阅“使用覆盖页面”。

覆盖页面概览

覆盖页面(Overlay),通常也被称作重叠页面或覆盖层,是一种需要特定的硬件支持的页面。覆盖页面通常被用于显示实时视频、视频录制、或静止的位图于主页面之上,而不需要进行Blit操作到主页面上或用任何方法改变主页面的内容。覆盖页面完全是由硬件支持的;DirectDraw支持由显示设备驱动程序报告的任何一种特性。DirectDraw不支持以软件仿真的方式实现覆盖页面。

可以用一张透明的塑料片来比喻一个覆盖页面,你可以将它放在显示器屏幕前,并且在上面进行绘图。同样的道理,当覆盖页面位于显示器屏幕的上层时,你可以同时看到覆盖页面和主页面的内容,当你将覆盖页面挪开时,主页面的内容不会发生任何改动。实际上,正如前面提到的那样,一个覆盖页面的执行机制与一张透明的塑料片基本上是一样的。当你要显示一个覆盖页面的时候,你要告诉设备驱动程序在哪儿和怎样去显示这个覆盖页面。当显示设备在显示器上绘制扫描线的时候,它会检查主页面上的每一个像素的位置,来判断该处是否应该让覆盖页面可见。如果是的话,显示设备从覆盖页面上取得该处的像素颜色,并且显示出来。整个过程如下图所示:

通过这个方法,显示适配器在显示屏幕上产生了一个由主页面和覆盖页面合成的图象,并且在提供透明和缩放特效的同时,不用修改任何一个源页面的内容。这个合成的页面被注入视频数据流中,直接显示在屏幕上。因为这是一个悬空(on the fly-我们暂且这么称呼它)的操作过程,并且像素的转换是在硬件层中完成的,所以对用户来说,在显示覆盖页面的过程中,并没有可以察觉的时间损失。除此之外,这个方法还允许将具有不同的像素格式的主页面和覆盖页面合成起来。

你可以通过调用IDirectDraw2::CreateSurface函数创建一个覆盖页面,在相应的DDSCAPS结构中指定DDSCAPS_OVERLAY标志符。覆盖页面只能存在于视频RAM中,所以,你还必须包含 45

DDSCAPS_VIDEOMEMORY标志符。与创建其它类型的页面一样,通过包含进适当的标志符,你可以创建一个单独的覆盖页面,或一个由若干个覆盖页面组成的换页链。

DDCAPS结构中的重要成员和标志

通过调用IDirectDraw2::GetCaps方法,你可以获得关于DirectDraw支持的覆盖页面特性的信息。该函数将描述了所有这些特性的信息填充到两个DDCAPS结构中。

当报告硬件特性时,如果硬件对某特定类型的限制加以实施,设备驱动程序会在DDCAPS 结构的dwCaps成员中设置相应的标志符来表明。在获得驱动程序的能力之后,检查dwCaps成员中的标志符,以判断硬件实施了哪些限制。DDCAPS结构中包含了九个描述了硬件对覆盖页面的限制信息的成员。下面的这张表列出了与覆盖页面相关的成员及其相应的标志符。

成员

dwMaxVisibleOverlays

dwCurrVisibleOverlays

dwAlignBoundarySrc

dwAlignSizeSrc

dwAlignBoundaryDest

dwAlignSizeDest

dwMinOverlayStretch

dwMaxOverlayStretch

标志符

该成员总是可用的

该成员总是可用的

DDCAPS_ALIGNBOUNDARYSRC

DDCAPS_ALIGNSIZESRC

DDCAPS_ALIGNBOUNDARYDEST

DDCAPS_ALIGNSIZEDEST

DDCAPS_OVERLAYSTRETCH

DDCAPS_OVERLAYSTRETCH

dwMaxVisibleOverlays和dwCurrVisibleOverlays成员携带了硬件设备可以显示的覆盖页面的最大个数,以及其中的多少个可以同时被显示出来的数据。

除此之外,硬件设备将覆盖页面矩形的位置和大小限制反映在dwAlignBoundarySrc、dwAlignSizeSrc、dwAlignBoundaryDest、dwAlignSizeDest、和dwAlignStrideAlign成员中。这些成员中的值表明了在显示覆盖页面的时候,你必须怎样控制源和目标矩形的大小和位置。要得到更多的资料,请参阅“源和目标矩形,以及边界和大小限制”。

同样,硬件将缩放系数反映在dwMinOverlayStretch和dwMaxOverlayStretch成员中。要得到更多资料,请参阅“最小和最大缩放系数”。

源和目标矩形

要显示一个覆盖页面,调用覆盖页面的IDirectDrawSurface3::UpdateOverlay函数,在dwFlags参数中 46

指定DDOVER_SHOW标志。该方法需要你在lpSrcRect和lpDestRect参数中指定源和目标矩形。源矩形描述了将显示在主页面上的覆盖页面的区域。要请求该方法使用整个页面,设置lpSrcRect参数为NULL即可。目标矩形描述了在主页面上将被用来显示覆盖页面的区域。

源和目标矩形不需要是同样的大小。通常,你所设置的目标矩形是小于或者大于源矩形的,那么在显示出覆盖页面的时候,硬件将会自动的将其进行缩小或放大。

要成功的显示覆盖页面,你可能需要调整源矩形和目标矩形的大小和位置。这是否必要,取决于你的设备驱动所强加的限制。要得到更多的资料,请参阅“边界和大小限制,以及最小和最大缩放系数”。

边界(Boundary)和大小(Size)限制

由于不同的硬件的能力所限,在显示覆盖页面的时候,某些设备驱动对源和目标矩形的位置和大小强加了一些限制。要找出某个设备受到了哪些限制,调用IDirectDraw2::GetCaps函数,并且在DDCAPS的dwCaps成员中检查那些与覆盖页面相关的标志。下面的表展示了表明边界和大小限制的的成员极其标志。

类型

边界(位置)限制

标志

DDCAPS_ALIGNBOUNDARYSRC

成员变量

dwAlignBoundarySrc

DDCAPS_ALIGNBOUNDARYDEST DwAlignBoundaryDest

大小限制

DDCAPS_ALIGNSIZESRC

DDCAPS_ALIGNSIZEDEST

dwAlignSizeSrc

DwAlignSizeDest

有两种类型的限制:边界限制和大小限制。每一种限制都是以像素为单位(而不是以字节为单位),并且可以提供给源和目标矩形。同样,这些限制会因为覆盖页面和主页面的像素格式的不同而不同。

边界限制影响的是你可以放置一个源或目标矩形的位置。dwAlignBoundarySrc和dwAlignBoundaryDest成员所报告的值分别告诉你应如何对齐相应的矩形的左上角。矩形左上角的x坐标(RECT结构的left成员)必须是所报告的该限制值的整数倍。

大小限制影响的是你可以设置一个源或目标矩形的合法的大小。dwAlignSizeSrc和dwAlignSizeDest成员所报告的值分别告诉你应如何对齐相应矩形的宽度(以像素为单位)。源或目标矩形的宽度必须是这个所报告的值的整数倍。如果你按照最小的缩放系数缩放一个矩形,还必须保证缩放后的矩形仍是满足大小限制的。在缩放完成后,用上舍入的方法(只取不舍)调整矩形的的宽度,而不是下舍入,这样才能保证满足最小缩放系数。要得到更多的资料,请参阅“最小和最大缩放系数”。

最小和最大缩放系数

由于硬件的能力所限,某些设备对于覆盖页面从源矩形到目标矩形Blit操作的缩放比例存在一定的限制。DirectDraw将这种限制称之为缩放系数。要获得设备缩放系数的数据,调用IDirectDraw2::GetCaps 47

函数,如果DDCAPS成员中设置了DDCAPS_OVERLAYSTRETCH标志,那么,有关缩放系数的数据就存在dwMinOverlayStretch和dwMaxOverlayStretch成员中。应该注意的是,缩放系数是按照实际数值的1000倍给出的,所以,1300实际上表示缩放系数为1.3,750表示0.75。

对于那些对覆盖页面的缩放不存在限制的设备来说,最小和最大缩放系数的值都被设为0。

最小缩放系数报告的是目标矩形必须至少是源矩形的宽度乘以该最小缩放系数值。如果最小缩放系数比1000大,那么,你所指定的目标矩形必须比源矩形大。举例来说,如果设备报告的最小缩放系数是1300,你必须保证目标矩形的宽度至少是源目标矩形宽度的1.3倍。同样,最小缩放系数小于1000表明目标矩形可以比源矩形小。

最大缩放系数报告了目标矩形必须至多是源矩形的宽度乘以该最大缩放系数。举例来说,如果最大缩放系数为2000,你可以指定目标矩形的大小至多是源矩形大小的两倍。如果最大缩放系数小于1000,那么,你总是必须使目标矩形比源矩形要小,才能正常的实现覆盖页面。

覆盖页面在进行完缩放操作之后,目标矩形还必须满足设备所需要的大小和位置限制。因此,先将目标矩形进行缩放,然后再进行大小和位置的调整是一个好主意。要得到更多的资料,请参阅“边界和大小限制”。

硬件不需要你对目标矩形的高度进行调整。你可以增加一个目标矩形的高度来保证显示正常的外观比例,而不会产生负面影响。

覆盖页面的关键色

与其它类型的页面一样,覆盖页面使用了源和目标关键色,以控制完成在不同页面间的透明Blit操作。因为覆盖页面本身在Blit操作时是不可见的,因此,当你调用IDirectDrawSurface3::UpdateOverlay函数的时候,需要一个另外的方法来控制该覆盖页面是如何被显示到主页面之上的。这种要求是通过覆盖页面的关键色来满足的。覆盖页面的关键色,与其它Blit相关的关键色一样,被分为源关键色和目标关键色,可以通过IDirectDrawSurface3::SetColorKey函数来设置。使用DDCKEY_SRCOVERLAY或DDCKEY_DESTOVERLAY标志来指定是源关键色还是目标关键色。覆盖页面可以使Blit操作、覆盖页面关键色、以及覆盖页面显示操作同时正常的进行,两种类型的关键色不会发生相互冲突。

IDirectDrawSurface3::UpdateOverlay函数使用覆盖页面的源关键色来指定在覆盖页面上哪些像素被视为透明,以使在这些像素覆盖下的主页面的内容可以被显示出来。同样的,该函数还使用覆盖页面的目标关键色来指定在主页面上哪些部分可以被覆盖页面所占据。产生的这些视觉效果与由Blit相关的关键色的是一样的。要得到更多的资料,请参阅“透明Blit,以及源和目标关键色”。

覆盖页面的定位

在调用IDirectDrawSurface3::UpdateOverlay函数初始化显示一个覆盖页面之后,你可以通过调用IDirectDrawSurface3::SetOverlayPositio函数来重新定位覆盖页面的目标矩形。

必须保证你所指定的位置要满足硬件设备所强加的边界限制。要得到更多的资料,请参阅“边界和大小限制”;同时,也要记住IDirectDraw2::SetOverlayPosition函数不会帮你进行裁减操作,函数中使用的坐标值如果有可能使你的覆盖页面超出目标页面的边缘,将会导致该函数调用失败,返回一个DDERR_INVALIDPOSITION的错误。

48

创建覆盖页面

与其它的页面一样,通过调用IDirectDraw2::CreateSurface函数可以创建一个覆盖页面,条件是必须在DDSCAPS结构中指定DDSCAPS_OVERLAY标志。

对覆盖页面的支持随显示设备的不同而存在着巨大的差异。因此,绝对不要认为存在一个被绝大多数硬件设备所支持的像素格式,必须作好准备与各种各样不同的像素格式打交道。通过调用IDirectDraw2::GetFourCCCodes函数,你可以得到设备驱动所支持的有关非RGB格式的信息。

当你创建一个覆盖页面的时候,先试着创建一个被大多数设备所认可的像素格式,如果失败,再试着使用其它的格式,这种方法是比较好的。

你可以创建一个覆盖页面换页链。要得到更多的资料,请参阅“创建复杂页面和换页链”。

覆盖页面的Z轴排序(Z-order)

覆盖页面在DirectDraw中被认为是位于屏幕上所有内容的最上一层,但是,当你需要显示多个覆盖页面的时候,你需要有一个办法来组织这些页面。为此,DirectDraw支持覆盖页面的Z轴排序,来管理这些覆盖页面层次关系和相互裁减。Z轴排序值代表了从主页面到观察者之间的一个抽象的距离。这些值的可能范围是从0,紧靠着主页面的一层,直到40亿,最靠近观察者的一层,并且没有任何两个覆盖页面可以共享同一个Z轴次序值。通过调用IDirectDrawSurface3::UpdateOverlayZOrder可以设置一个覆盖页面的Z轴次序。

目标关键色只受主页面的影响,而不会受到被另一个覆盖页面所覆盖的覆盖页面的影响。源关键色作用于覆盖页面,而不考虑该覆盖页面是否被设置了Z轴次序。

没有指定Z轴次序的覆盖页面,其Z轴次序被默认为0。没有指定Z轴次序的多个覆盖页面间的层次关系是不可预知的,当它们被映射到主页面上时,会产生混乱。

一个DriectDraw对象不能跟踪由另一个应用程序显示的覆盖页面的Z轴次序。

覆盖页面的换页

和其它各类型的页面一样,你可以创建覆盖页面的换页链。在创建好一个覆盖页面的换页链之后,你可以通过调用IDirectDrawSurface3::Flip函数实现换页。要得到更多的资料,请参阅“换页”。

利用覆盖页面的软件视频解码在调用Flip函数实现换页的时候,可以使用DDFLIP_ODD和DDFLIP_EVEN标志,以利用减少运动噪声的特性。如果设备驱动支持奇偶(odd-even)隔行换页,在调用GetCaps函数以获取设备驱动能力的DDCAPS结构中将会被设置DDCAPS2_CANFLIPODDEVEN标志。如果是这样的话,那么,在调用IDirectDrawSurface3::UpdateOverlay函数时,你可以包含进DDOVER_BOB标志,以告知设备驱动你希望它使用BOB(摆动)算法来使运动噪声达到最小。随后,在你调用Flip函数实现换页时,你可以带上DDFLIP_ODD或DDFLIP_EVEN标志,设备驱动将会自动的调整覆盖页面的源矩形来补偿不稳定的噪声。

获取设备驱动能力时,如果DDCAPS结构中没有设置DDCAPS2_CANFLIPODDEVEN标志,那么,指定了DDOVER_BOB 标志的UpdateOverlay函数调用将会失败。

要得到更多的关于Bob算法的资料,请参阅“普通视频噪声的解决方案”。

49