本文主要是在网上收集到的一些关于DirectDraw的文章!
一下为相关文章和链接(为什么给了链接还要给文章?因为怕给的链接失效,这样我们就找不到那些好的文章了,在这也谢谢那些提供文章的人!!!)
文章地址:http://dev.gameres.com/Program/Visual/2D/fromfirst.htm
| |||||||
|
文章地址:http://blog.youkuaiyun.com/xqhrs232/article/details/7991481
DirectDraw简单用法
原文地址::http://www.webgou.info/content/programming/363/?jdfwkey=9sycs3
GDI:win32 gdi 与 DirectDraw 操作DIB的,DirectDraw 直接操作内存。
//ce 6下面测试:
IDirectDraw *pDraw;
IDirectDrawSurface *pDFace;
IDirectDrawSurface *pLayer;
HRESULT hr;
DDSURFACEDESC ddsd;
RECT rect;
dlgMain.GetWindowRect(&rect);
DirectDrawCreate(NULL, &pDraw, NULL);
pDraw->SetCooperativeLevel(dlgMain,DDSCL_NORMAL);
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
hr = pDraw->CreateSurface(&ddsd, &pDFace,NULL);
DDCAPS ddcaps;
memset(&ddcaps, 0, sizeof(ddcaps));
hr = pDraw->GetCaps(&ddcaps,NULL);
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_VIDEOMEMORY;
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH |DDSD_PIXELFORMAT;
ddsd.dwWidth = 800;
ddsd.dwHeight = 480;
ddsd.ddpfPixelFormat = ddpfOverlayFormats;
hr = pDraw->CreateSurface(&ddsd, &pLayer,NULL);
hr = pLayer->IsLost();
DDOVERLAYFX ofx;
memset(&ofx, 0, sizeof(DDOVERLAYFX));
ofx.dwSize = sizeof(DDOVERLAYFX);
ofx.dckSrcColorkey.dwColorSpaceLowValue = 0x0000F81F;
ofx.dckSrcColorkey.dwColorSpaceHighValue = 0x0000;
ofx.dwAlphaConstBitDepth = 8;
ofx.dwAlphaConst = 0x50;
RECT rt1= {0,0,400,480};
hr = pLayer->UpdateOverlay(&rt1, pDFace, &rt1,DDOVER_SHOW|DDOVER_KEYSRCOVERRIDE, &ofx);
HDC hdc;
hr = pLayer->GetDC(&hdc);
FillRect(hdc,&rt1, (HBRUSH)GetStockObject(BLACK_BRUSH));
SetBkColor(hdc,TRANSPARENT);
SetTextColor(hdc,0x0000F81F);
DrawText(hdc,_T("pLayerpLayer pLayervpLayer "), -1, &rt1, 1);
hr = pLayer->ReleaseDC(hdc);
pLayer->Release();
pDFace->Release();
pDraw->Release();
1.链接库
动态装载
typedef HRESULT (* DIRECTDRAWCREATE)( GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter );
m_hModule=::LoadLibrary(_T("ddraw.dll"));
m_pDirectDrawCreate= (DIRECTDRAWCREATE)::GetProcAddress(m_hModule,L"DirectDrawCreate");
静态链接
#pragma comment(lib, "ddraw.lib") 调用DirectDrawCreate
2.GUID的使用
直接定义
const GUID g_IID_IDirectDraw ={0x9c59509a,0x39bd,0x11d1,
0x8c,0x4a,0x00,0xc0,0x4f,0xd9,0x30,0xc5};
链接#pragma comment(lib, "dxguid.lib")
QueryInterface IID_IDirectDraw4
3.创建主页面
// Create the main DirectDraw object
LPDIRECTDRAW g_pDD;
hRet = DirectDrawCreate(NULL, &g_pDD, NULL);
hRet = g_pDD->QueryInterface(g_IID_IDirectDraw, (LPVOID *)&g_pDD);
// Get normal mode
hRet = g_pDD->SetCooperativeLevel(hWnd, DDSCL_NORMAL);
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
// creates a DirectDrawSurface object for this DirectDraw object.
hRet = g_pDD->CreateSurface(&ddsd, &g_pDDSPrimary, NULL);
创建主页面无须指定像素格式和页面大小
4.页面的像素格式
//YUV4:1:1
DDPIXELFORMAT pixel_format=
{sizeof(DDPIXELFORMAT), DDPF_FOURCC, MAKEFOURCC('Y','V','1','2'),0,0,0,0,0};
//RGB565
DDPIXELFORMAT ddpfOverlayFormats =
{sizeof(DDPIXELFORMAT), DDPF_RGB, 0, 16, 0xF800, 0x07e0, 0x001F, 0};
5.创建OverLay页面
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.ddsCaps.dwCaps = DDSCAPS_OVERLAY | DDSCAPS_FLIP | DDSCAPS_VIDEOMEMORY;
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_BACKBUFFERCOUNT | DDSD_PIXELFORMAT;
ddsd.dwWidth = DDRAW_X;
ddsd.dwHeight = DDRAW_Y;
ddsd.dwBackBufferCount = 1;//有个后台页面
ddsd.ddpfPixelFormat = pixel_format;
hRet = g_pDD->CreateSurface(&ddsd, &g_pDDSOverlay, NULL);
创建OverLay页面应该指定页面的大小(可以全屏),像素格式。
6.OverLay页面的显示与隐藏
void ShowOverLay()
{
DDOVERLAYFX ovfx = {0};
ovfx.dwSize = sizeof(DDOVERLAYFX);
ovfx.dckSrcColorkey.dwColorSpaceLowValue = COLOR_KEY;//指定页面的透明色
ovfx.dckSrcColorkey.dwColorSpaceHighValue = COLOR_KEY;
ovfx.dwAlphaConstBitDepth = 8;//指明alpha位宽度
ovfx.dwAlphaConst = 0x50;//alpha透明值 指明页面的透明度.16级.
RECT rs;
rs.left = 0;
rs.top = 0;
rs.right = DDRAW_X;
rs.bottom = DDRAW_Y;
DWORD dwUpdateFlags = DDOVER_KEYSRCOVERRIDE | DDOVER_SHOW;
if(g_pDDSOverlay != NULL && g_pDDSOverlay->IsLost() == DD_OK)
{
HRESULT hret = g_pDDSOverlay->UpdateOverlay(&rs,g_pDDSPrimary,&rs,dwUpdateFlags,&ovfx);
//将主页面和OverLay页面关联起来
if(hret != DD_OK)
{
printf("Update OverLay errorrn");
if(hret == DDERR_SURFACELOST)
{
g_pDDSOverlay->Restore();
}
}
}
}
dwUpdateFlags = DDOVER_HIDE即隐藏页面。
UpdateOverlay这个接口一般是在页面初始化后,显示,隐藏这三种状况下调用,
每次在页面上绘图后不必要调用UpdateOverlay,否则就会造成画面显示卡动,不连续。
7.在页面上绘图
在页面上绘图有两种方法
一是获取页面DC后,以GDI方式绘图
if( g_pDDSOverlay->GetDC(&hDC) == DD_OK)
//进行DC绘图
g_pDDSOverlay->ReleaseDC(hDC);
第二种状况当知道页面的像素格式,Lock页面获取页面指针,直接操作页面存储区。
g_pDDSOverlay->Lock(NULL,&ddsd,0,NULL);
BYTE *pFramePhyPtr = (LPBYTE)ddsd.lpSurface;
int framesize = DDRAW_X * DDRAW_Y;
memcpy(pFramePhyPtr,src[0],framesize);
memcpy(pFramePhyPtr + framesize,src[2],framesize / 4);
memcpy(pFramePhyPtr + framesize + framesize / 4,src[1], framesize / 4);
g_pDDSOverlay->Unlock(NULL);//YUV4:1:1
8.遍历后台页面
EnumAttachedSurfaces
enumerates all the surfaces attached to a given primary surface.
可以先到后台页面上绘制,然后Flip操作.
9.注意要点
不要频繁调用UpdateOverLay。
注意页面的像素格式与图像帧的像素格式的匹配,
为了加快显示速度,最好与显示驱动所支持的像素格式相同。
可以用后台页面和Flip操作,加快页面显示。
具体操作见资源中心的示例。
//=====================================
备注:
1.
UpdateOverlay这个接口一般是在页面初始化后,显示,隐藏这三种状况下调用,
每次在页面上绘图后不必要调用UpdateOverlay,否则就会造成画面显示卡动,不连续
2.
不要频繁调用UpdateOverLay。
注意页面的像素格式与图像帧的像素格式的匹配,
为了加快显示速度,最好与显示驱动所支持的像素格式相同。
可以用后台页面和Flip操作,加快页面显示。
文章地址:http://www.cnblogs.com/Jade2009/archive/2009/05/12/1454689.html
DIRECTDRAW 1:创建一个简单的DIRECTDRAW程序
步骤 1: 创建一个 DirectDraw 对象
要创建一个 DirectDraw 对象的实例,你的应用程序要象 DDEx1 例程中的 doInit 函数那样先使用 DirectDrawCreate 函数. DirectDrawCreate 包含三个参数. 第一个参数获得了一个代表显示设备的全局唯一标识符(GUID). 这个 GUID 在大多数情况下被设为 NULL, 表示 DirectDraw 使用系统缺省的显示驱动. 第二个参数包含了 DirectDraw 创建以后的指针的地址. 第三个参数留作将来的扩展之用只能为 NULL.
下面的例子演示了如何创建一个 DirectDraw 对象以及确定是否创建成功:
ddrval = DirectDrawCreate(NULL, &lpDD, NULL);
if(ddrval == DD_OK)
{
}
else
{
}
步骤 2: 确定应用程序的行为
在你能够改变显示率之前, 你必须至少为 IDirectDraw::SetCooperativeLevel 函数的 dwFlags 参数指定 DDSCL_EXCLUSIVE 和 DDSCL_FULLSCREEN 标志. 这使得你的应用程序能完全控制显示设备,而其它应用程序不能共享之.另外, DDSCL_FULLSCREEN 标志设置应用程序为独占(全屏)模式. 你的应用程序覆盖了整个桌面,而且只有你的应用程序能够写到屏幕上. 然而桌面仍然是可用的.(要看独占模式的应用程序下的桌面,执行 DDEx1 然后按 ALT+ TAB.)
以下的例子演示了如何使用 SetCooperativeLevel 函数:
HRESULT
LPDIRECTDRAW lpDD;
ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE |
if(ddrval == DD_OK)
{
}
else
{
}
如果 SetCooperativeLevel 没有返回 DD_OK,你仍然可以运行你的程序.然而程序将不能取得独占模式,并且有可能不能实现你所需要的功能.在这种情况下,你也许应该显示信息让用户来决定退出还是继续.
如果你设置全屏,独占的控制等级,你必须把你的应用程序的窗口句柄传递给 SetCooperativeLevel 来允许 Windows 确定你的应用程序是否异常终止.例如,如果发生了一个一般保护(GP)错误而 GDI 换页到 后台缓存(back buffer),使用者将不能返回 Windows 屏幕.要防止这种现象的发生, DirectDraw 提供了一个在后台运行的进程来捕获发向 window 的消息. DirectDraw 使用这些消息来测定何时应用程序终止.然而这一特性强加了一些限制.你不得不指明为你的应用程序获取消息的窗口句柄,这意味着,如果你创建另一个窗口,你必须确定你指明了活动窗口.否则,你将会遇到问题,包括 GDI 的不可预知的行为,或你按了 ALT + TAB 而没有响应.
步骤 3:改变显示模式
在你设置了应用程序的行为之后,你就能使用 IDirectDraw::SetDisplayMode 函数来改变显示分辨率.以下的例子演示了如何把显示模式设为 640×480×8 bpp:
HRESULT
LPDIRECTDRAW lpDD;
ddrval = lpDD->SetDisplayMode(640, 480, 8);
if(ddrval == DD_OK)
{
}
else
{
}
当你设置显示模式时,你应该确定如果用户的硬件不能支持更高的显示模式,你的应用程序将恢复到被大数显示适配器所支持的模式. 例如,你的可以把应用程序制作为把 640×480×8 作为一个后备显示模式来运行与所有支持它的系统上.
注意
步骤 4:创建换页页面
在你设置了显示模式之后,你应该创建页面来安放你的应用程序.因为 DDEx1 例子使用了 IDirectDraw::SetCooperativeLevel 函数设置成独占(全屏)模式,你就能创建换页的页面.你如果使用 SetCooperativeLevel 把模式设置为 DDSCL_NORMAL, 你将只能创建位块传送的页面.创建换页页面(flipping surfaces)需要以下步骤:
定义页面参数
创建页面
定义页面参数
创建换页页面(flipping surfaces)的第一步是在一个 DDSURFACEDESC 结构中定义页面参数.以下的例子演示了结构的定义以及创建换页页面所需的标志.
// 创建带有一个后台缓存的主页面.
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
ddsd.dwBackBufferCount = 1;
在这个例子中, dwSize 成员被设为 DDSURFACEDESC 结构的大小.者可以预防任何 DirectDraw 函数返回不可用成员错误. (dwSize 成员是为 DDSURFACEDESC 结构将来的扩展提供的.)
dwFlags 成员确定了 DDSURFACEDESC 结构中的哪一个成员将被填充有效信息.对于 DDEx1 例子, dwFlags 被设置为指明你要使用 DDSCAPS 结构(DDSD_CAPS)以及你要创建一个后台缓存(back buffer)(DDSD_BACKBUFFERCOUNT).
在这个例子中 dwCaps 成员指出这些标志将在 DDSCAPS 结构中使用.在这种情况下,它指明了一个主页面(primary surface)(DDSCAPS_PRIMARYSURFACE),一个换页页面(flipping surface)(DDSCAPS_FLIP),和一个复杂页面(complex surface)(DDSCAPS_COMPLEX).
最后,这个例子指明了一个后台缓存.后台缓存是背景和精灵将被实际写入的地方.然后后台缓存被换页到主页面.在 DDEx1 例子中,后台缓存的个数被设为 1.然而,你可以创建显存所允许的个数的后台缓存.要得到关于创建一个或更多后台缓存的信息,请参阅三缓冲(Triple Buffering).
页面内存可以是显示内存或系统内存.如果显存不够 DirectDraw 使用系统内存(例如,如果你在只有 1M 显存的显示适配器上指明超过一个后台缓存(back buffer)).你也可以通过设置 DDSCAPS 结构的 dwCaps 成员为 DDSCAPS_SYSTEMMEMORY 或 DDSCAPS_VIDEOMEMORY 指明只用系统内存或显示内存.(如果你指明 DDSCAPS_VIDEOMEMORY,而创建页面的有用显存不够, IDirectDraw::CreateSurface 将返回一个 DDERR_OUTOFVIDEOMEMORY 错误.)
创建页面
在 DDSURFACEDESC 结构被填充后,你可以使用它和由 DirectDrawCreate 函数创建的指向 DirectDraw 对象的指针 lpDD,来调用 IDirectDraw::CreateSurface 函数,如下所示:
ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL);
if(ddrval == DD_OK)
{
}
else
{
}
lpDDSPrimary 参数将指向如果 CreateSurface 调用成功返回的主页面(primary surface).
在指向主页面的指针可用后,你可以使用 IDirectDrawSurface3::GetAttachedSurface 函数来提取一个指向后台缓存(back buffer)的指针,如下所示:
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->GetAttachedSurface(&ddcaps, &lpDDSBack);
if(ddrval == DD_OK)
{
}
else
{
}
通过提供主页面(primary surface)的地址以及使用 DDSCAPS_BACKBUFFER 标志设置特性值, 如果 IDirectDrawSurface3::GetAttachedSurface 调用成功 lpDDSBack 参数将指向后台缓存(back buffer).
步骤 5:着色到页面
在一个主页面(primary surface)和一个后台缓存(back buffer)被创建之后, DDEx1 例子通过使用标准的 Windows GDI 函数向主页面和后台缓存着色了一些文本,如下所示:
if (lpDDSPrimary->GetDC(&hdc) == DD_OK)
{
}
if (lpDDSBack->GetDC(&hdc) == DD_OK)
{
}
这个例子使用了 IDirectDrawSurface3::GetDC 函数来获得设备描述句柄,同时它内在地琐定了页面.如果你不使用需要设备描述句柄的 Windows 函数, 你可以使用 IDirectDrawSurface3::Lock 和 IDirectDrawSurface3::Unlock 函数来锁定和解锁后台缓存(back buffer).
锁定页面内存(不论整个页面或一部分页面)使得你的应用程序和系统位块传送器不能同时获得对主页面的访问.这预防了由于你的应用程序正在写向页面内存而引发的错误.另外,在主页面被解锁前你的应用程序不能换页.
在页面解锁后,这个例子使用标准的 Windows GDI 函数: SetBkColor 来设置背景颜色, SetTextColor 来选择放在背景上的文本的颜色, 以及 TextOut 来把文本和背景颜色打印在页面上.
在文本被写到缓存后,这个例子使用 IDirectDrawSurface3::ReleaseDC 函数来解锁页面和释放句柄.一旦你的应用程序结束写向后台缓存(back buffer),你必须调用 IDirectDrawSurface3::ReleaseDC 或 IDirectDrawSurface3::Unlock,视你的程序而定.在页面被解锁前你的应用程序不能换页.
典型的,你写向后台缓存,然后换页到主页面(primary surface)以被显示.对于 DDEx1,在第一次换页前没有明显的延迟,所以 DDEx1 在初始函数中写向主缓存来预防显示页面前的延迟. 就如你在这个DIRECTDRAW 后面的步骤中将要看到的那样, DDEx1 只在 WM_TIMER 期间写向后台缓存.通常你只在初始函数或标题页中写向主页面.
注意
步骤 6:写向页面
DDEx1 中 WM_TIMER 消息的前一半专门写向后台缓存(back buffer),如下所示:
case WM_TIMER:
调用 IDirectDrawSurface3::GetDC 函数的那行代码锁定后台缓存(back buffer)来预备写操作. SetBkColor 和 SetTextColor 函数设置背景和文本的颜色.
接着, phase 变量决定了应该写主缓存消息还是后台缓存消息. 如果 phase 等于 1,将写主页面(primary surface)消息,同时 phase 被设为 0.如果 phase 等于 0,将写后台缓存消息,同时 phase 被设为 1.注意,其实两种情况下消息都是写到后台缓存里的.
在消息被写到后台缓存后,后台缓存被使用 IDirectDrawSurface3::ReleaseDC 函数解锁.
步骤 7:换页
在页面内存解锁后,你就可以使用 IDirectDrawSurface3::Flip 函数来换页后台缓存(back buffer)到主页面(primary surface)了,如下所示:
while(1)
{
}
在这个例子里, lpDDSPrimary 参数指明了主页面(primary surface) 和与之关联的后台缓存(back buffer). 当调用 IDirectDrawSurface3::Flip 后,前后页面被交换(只改变了页面的指针,数据并未实际移动). 如果换页成功返回 DD_OK,程序从 while 循环跳出.
如果换页返回 DDERR_SURFACELOST,可以尝试使用 IDirectDrawSurface3::Restore 函数来恢复页面.如果恢复成功,程序跳回 IDirectDrawSurface3::Flip 调用并再次尝试.如果恢复不成功,程序从循环跳出,并返回一个错误.
注意
步骤 8: 释放 DirectDraw 对象
当你按 F12 时, DDEx1 程序在退出前执行 WM_DESTROY 消息.这个消息调用 finiObjects 函数,它包含了所有的 IUnknown::Release 调用,如下所示:
static void finiObjects(void)
{
} // finiObjects
程序检查 DirectDraw 对象(lpDD)和 DirectDrawSurface 对象(lpDDSPrimary)的指针是否不等于 NULL. 然后 DDEx1 调用 IDirectDrawSurface3::Release 函数把 DirectDrawSurface 对象的引用计数(reference count)减 1.因为这使得引用计数变为 0, DirectDrawSurface 对象被释放. DirectDrawSurface 指针通过值被设为 NULL 而被释放.然后,程序调用 IDirectDraw::Release 把 DirectDraw 对象的引用计数减为 0,释放 DirectDraw 对象.它的指针同样通过值被设为 NULL 而被释放.
文章地址:http://www.cppblog.com/tim/archive/2007/07/22/28574.aspx
DirectDraw编程方法与技巧
添加时间:2006-10-18 出处:www.3DGameDev.org 作者:songtao
1 概 述
DirectX是Microsoft为软件开发人员提供的一套精心设计的接口,用于开发高性能、实时的应用程序。它以COM(component object modal)为基本结构[1],位于硬件和软件之间,像gdi(graphics device interface)一样提供了硬件无关的API(application programming interface)接口;它和GDI有一重要不同点:DirectX是一套底层的API接口,它提供了直接访问硬件的能力,使得DirectX应用程序能充分发挥硬件的威力。
DirectDraw是DirectX中提供直接操纵显存、执行硬件映射、硬件覆盖及切换显示页等功能的组件。它不但兼容已有的Windows应用程序和驱动程序,而且还兼容许多显示卡:从简单的SVGA到支持图像剪裁、拉伸和非RGB格式图像的高档显示卡。DirectDrawHAL(hardware-abstraction layer)抽象了显示卡的硬件功能,以设备无关的方式提供了一些以前是设备相关的功能,如多显示页技术、访问并控制显示卡映射寄存器、支持3DZ-Buffer、支持Z-order硬件覆盖、访问图像拉伸硬件,以及同时访问标准显存和控制显存。正因为DirectDraw有这些优点,现在许多基于Windows95/98/NT的游戏程序都使用了DirectDraw;而它的设备无关性使开发者摆脱了繁重的显示卡接口工作,集中精力实现程序的主要功能。DirectX中的DDraw.dll实现了DirectDraw所需要的函数、对象及其接口。下面先介绍DirectDraw中主要的函数、对象和接口,然后再说明DirectDraw的使用方法。
2 DirectDraw中的函数、对象和接口简介
DirectDraw包括一个代表显示卡的主对象DirectDraw,由函数DirectDrawCreate创建,实现接口IDirectDraw和IDirectDraw2。如果机器上装有多个显示卡,可以为每个显示卡创建一个DirectDraw对象。也可以创建多个DirectDraw对象,它们各自独立并代表同一物理对象。IDirectDraw是为了和以前版本DirectX兼容而保留的接口,基于DirectX3以上版本的程序应该使用IDirectDraw2。IDirectDraw2和IDirectDraw接口所包含的方法定义基本相同,但具体实现不同。DirectDraw对象主要用于创建其它3个对象:DirectDrawSurface、DirectDrawPalette、DirectDrawClipper。
DirectDrawSurface对象由函数IDirectDraw2::CreateSurface创建,代表显存中一块线性区域,实现接口IDirectDrawSurface和IDirectDrawSurface2。IDirectDrawSurface是为了和以前版本DirectX兼容而保留的接口,基于DirectX3以上版本的程序应该使用IDirectDrawSurface2。二者的接口定义也基本相同。可以为一个DirectDraw对象创建多个IDirectDrawSurface对象,代表物理屏幕或逻辑屏幕,通过IDirectDrawSurface2::Flip、IDirectDrawSurface2::BltFast等方法切换显示页或映射部分屏幕内容。
DirectDrawPalette对象由函数IDirectDraw2::CreatePalette创建,实现接口IDirectDrawPalette。它代表显示卡的物理调色板,可以是16色或256色。每个DirectDrawPalette必须附着(attach)在一个DirectDrawSurface上,不同的DirectDrawSurface对象可以有不同的DirectDrawPalette。
DirectDrawClipper对象由函数IDirectDraw2::CreateClipper或DirectDrawCreateClipp
er创建,实现接口IDirectDrawClipper。DirectDraw用它来处理屏幕的剪贴。它常用于在Window模式(与全屏模式相对应)下运行的DirectDraw程序,在使用前也必须被附着在一个DirectDrawSurface上。
3 DirectDraw程序一般工作过程
和一般的Windows程序[2]一样,DirectDraw程序要先创建一个主窗口,然后进行DirectDraw的初始化:创建所需要的对象,设置程序的工作模式,建立必要的数据结构。在初始化工作完成后,就可以在主窗口的消息循环中根据用户的输入调用相应对象的方法。
3.1 初始化DirectDraw
3.1.1 创建DirectDraw对象
首先调用DirectDrawCreate创建代表某个显示卡的DirectDraw对象:
ddres=DirectDrawCreate(NULL,&lpDD,NULL);
DirectDrawCreate的第一项参数是代表显示卡驱动程序的GUID(globally unique identifier),若为NULL则表示采用系统默认的驱动程序。第二项参数是IDirectDraw接口类型指针的地址,用于接收指向由DirectDrawCreate所创建对象的指针。这个指针不需应用程序预先分配内存。DirectDrawCreate成功时会调用AddRef将对象的引用计数加1。如果要知道系统中所有驱动程序的GUID,可以调用DirectDrawEnumerate,它接收两个参数:回调函数的地址和传给回调函数的自定义数据的地址,其工作方式和Win32 API Enum Windows类似。
3.1.2 设置DirectDraw的工作模式
创建DirectDraw对象后应该马上设置DirectDraw对象的工作模式:
ddres=lpDD→SetCooperativeLevel(hWnd,DDSCL_EXCLUSIVE|DDSCLFUL_LSCREEN);
SetCooperativeLevel的第一个参数是和DirectDraw对象关联的窗口句柄,一般是程序主窗口的句柄;第二个参数指明了DirectDraw对象的工作模式。DirectDraw对象有两种工作模式:普通模式(Windowedmode,参数DDSCL_NORMAL)和独占模式(Full_Screen mode,参数DDSCL_EXCLUSIVE)。普通模式下DirectDraw和普通Windows程序的区别不大,主要是DirectDraw程序可以随心所欲地读取整个屏幕的内容或在屏幕的任意位置输出,而其它的Windows程序毫无察觉。独占模式就是游戏“红色警报”和“赤壁”所采用的方式,并必须和全屏模式(参数DDSCL_FULLSCREEN)联用,此时程序的主窗口被扩展为整个屏幕。其它应用程序都成为后台程序,使用Alt+Tab键可以在程序间切换。
3.1.3 得到IDirectDraw2类型的接口
接下来利用COM的重要方法QueryInterface,通过IDirectDraw接口得到一个IDirectDraw2类型的接口。QueryInterface成功时会将对象的引用计数加1,而我们也不再需要IDirectDraw接口,因此这里调用IDirectDraw::Release将对象的引用计数减1,也即释放先前得到的IDirectDraw接口。
3.1.4 根据需要切换屏幕显示模式
如果DirectDraw的工作模式设定为全屏独占模式,则可以根据需要切换屏幕显示模式:
ddres=lpDD2→SetDisplayMode(800,600,16);
SetDisplayMode的前两个参数是屏幕的横、纵分辨率,最后一个是每个像素点的颜色位数。上例将屏幕设为800×600,16位色。
3.1.5 创建DirectDrawSurface对象并得到IDirectDrawSurface2接口
创建DirectDraw对象后,下一步调用IDirectDraw2::CreateSurface创建代表物理屏幕或逻辑屏幕的DirectDrawSurface对象。IDirectDraw2::CreateSurface的第一个参数是DDSURFACEDESC结构的地址,第二个参数是一个IDirectDrawSurface接口类型的指针地址,第三个参数必须是NULL。数据结构DDSURFACEDESC包含了创建DirectDrawSurface所需信息。在得到一个IDirectDrawSurface类型接口后,仍使用QueryInterface得到一个IDirectDrawSurface2类型的接口,并释放先前得到的IDirectDrawSurface接口:
//Get the IDirectDrawSurface2interface.
LPDIRECTDRAWSURFACE2lpDDSPrimary=NULL;
ddres=lpDDSPrimaryTemp>QueryInterface(IID_IDirectDrawSurface2,(LPVOID)&lpDDSPrimary;
lpDDSPrimaryTemp→Release();
上面得到的lpDDSPrimary指向代表物理屏幕的对象。还需调用IDirectDrawSurface2::GetAttachedSurface得到指向创建时附带的代表相关逻辑屏幕的对象指针。至此,DirectDraw初始化工作完成。
3.2 使用DirectDraw
3.2.1 使用GDI函数向物理屏幕或逻辑屏幕输出
调用IDirectDrawSurface2::GetDC可得代表某个屏幕的设备描述表的句柄,使用GDI函数输出,最后调用IDirectDrawSurface2::ReleaseDC释放句柄。为防止在对屏幕作图期间其它应用程序争夺显存,IDirectDrawSurface2::GetDC调用IDirectDrawSurface2::Lock得到Winl6 Lock。这意味着其它程序在该程序释放Win16Lock前都不能访问GDI和USER资源。IDirectDrawSurface2::ReleaseDC调用IDirectDrawSurface2::Unlock释放Win16 Lock。于是,在调用IDirectDrawSurface2::GetDC和IDirectDrawSurface2::ReleaseDC期间,Windows将被挂起。因此,应用程序应尽量缩短这一对函数调用之间的间隔时间,而且调试程序也无法跟踪这段时间内执行的操作。
3.2.2 交替切换物理屏幕和逻辑屏幕或执行屏幕内容的映射
准备好内存中的逻辑屏幕后,可以调用IDirectDrawSurface2::Flip方法切换物理屏幕和逻辑屏幕,也可调用IDirectDrawSurface2::BltFast、IDirectDrawSurface2::Blt等方法执行部分屏幕内容的映射。一般情况下程序采用异步方式,在显示卡硬件执行切换动作的同时准备下一页屏幕,使CPU和显示卡硬件并行,提高整体执行速度。
使用Flip切换物理屏幕和逻辑屏幕后,原指向物理屏幕的指针仍然指向物理屏幕,原指向逻辑屏幕的指针仍然指向逻辑屏幕,即指针所指内容也被交换了,便于程序操纵各个屏幕而不至于混淆。
3.2.3 释放DirectDraw对象
程序结束之前要释放所创建的DirectDraw对象。这只要在相应接口上调用Release方法即可。
4 使用DirectDraw的技巧和注意事项
4.1 检查方法的返回值
正确执行DirectDraw方法时都返回DDOK。且其值是零。返回其它值表明发生了某种错误。一般地,程序应检查这些返回值以决定是否出错。
4.2 检查Flip和Blt的状态
如果在Flip或Blt操作的返回值是DDERR_WASSTILLDRAWING情况,为提高效率,DirectDraw提供了IDirectDrawSurface2::GetFlipStatus和IDirectDrawSurface2::GetBltStatus方法。它们能立即返回当前的Flip和Blt状态,于是应用程序可以在上一操作完成之前执行某些其他的任务。
4.3 在位图映射中使用ColorKey
ColorKey是一种或几种颜色的集合,用于在位图映射操作中区分前景色和背景色。ColorKey包括两种:Source color key和Destination color key。前者是指源位图中代表透明色的颜色,在执行映射操作时将不被映射到目标位图上;后者是指目标位图中将被源位图中相应位置颜色取代的颜色,如果目标位图指定了Destination color key,则只有这些指定的颜色被替换。可以在创建DirectDrawSurface对象时指定Color Key;也可使用方法IDirectDrawSurface2::GetColorKey和IDirectDrawSurface2::SetColorKey以获取和设置已有的DirectDrawSurface对象的ColorKey。
4.4 GDI重定向
由于GDI在Windows系统启动时先于DirectDraw被装入,而DirectDraw工作时又绕过了GDI,因此GDI不知道DirectDraw对物理屏幕所进行的操作。即使DirectDraw调用了Flip方法将先前的物理屏幕切换为逻辑屏幕,如不采取措施,GDI将仍然向切换后的逻辑屏幕上输出。如果DirectDraw程序拥有菜单、滚动条等由GDI负责绘制的元素,那么在Flip完成后这些元素就会成为不可见,而在对应的逻辑屏幕被切换成物理屏幕时又会显示出来。为避免屏幕闪烁,DirectDraw提供了IDirectDraw2::GetGDISurface,用于确定当前被GDI认为是物理屏幕的DirectDrawSurface对象;以及IDirectDraw2::FlipToGDISurface,用于将GDI的输出重新定向到当前的物理屏幕上。如果需要,可以在每次Flip操作后调用它,以保证屏幕正常。
4.5 在显存中存放位图
由于从显存到显存的映射比从系统内存到显存的映射快,所以经常将需要映射的位图存放在显存中以提高速度。大多数显示卡在存放了物理屏幕和相关逻辑屏幕之外还有足够的内存可以用来存放位图。可以调用IDirectDraw2::GetCaps检查显存;在创建DirectDrawSurface对象时可以通过结构DDSURFACEDESC中的DDSCAPS域指定该对象存在于显存或系统内存中。如果指定在显存中创建对象,而显存又没有地方容纳该对象,IDirectDraw2::CreateSurface会返回错误信息DDERR_OUTOFVIDEOMEMORY。如果没有指定创建的位置,DirectDraw总是尽量利用空闲的显存。
4.6 检查硬件的性能
虽然DirectDraw通过HAL和HEL屏蔽了硬件的具体特性,但应用程序也需根据硬件的不同性能来改变自身的执行方式。利用IDirectDraw2::GetCaps方法可以得到有关硬件性能的详细信息。
4.7 保持主窗口的消息循环畅通
在调用SetCooperativeLevel设置DirectDraw的工作模式时,应用程序为DirectDraw指定了主窗口。由于DirectDraw直接操纵硬件可能导致死机,因此DirectDraw在后台监视主窗口的消息循环,当消息循环长时间没有反应时,DirectDraw就释放所有的资源,结束应用程序的执行。所以DirectDraw程s序应该注意避免长时间封锁消息循环。