0.依赖
- dxguid.lib
- ddraw.lib
1.DirectDraw接口介绍
- IUnknow
COM基本接口
包含AddRef()、ReleaseRef()、QueryInterface()三个函数。 - IDirectDraw
一对一的表示显卡及其支持硬件的对象的接口。
计算机上有n个显卡的时候,就有n个DirectDraw对象 - IDirectDrawSurface
用DirectDraw创建、控制和显示的实际显示表面
使用显存(Video RAM,VRAM,存于显卡中或存在系统内存里)
双缓冲使用两种显示表面:主显示表面(Primary Surface)、从显示表面(Secondray surface)
主显示表面:正在被显卡光栅化和显示的实际视频缓冲区。
从显示表面:离屏渲染下一帧。 - IDirectDrawPalette
1~8位的调色板。
创建、加载和控制调色板,以及将调色板关联到显示表面。 - IDirectDrawClipper
裁剪位图到显示表面的区域。
2.创建DirectDraw应用程序的步骤
- 创建DirectDraw对象。IDirectDraw
使用DirectDrawCreateEx()创建一个IDirectDraw7接口 - 创建显示表面,用于绘图。IDirectDrawSurface
基于颜色深度和视频模式。
如果视频模式是8位的,需要一个调色板。 - 创建调色板,并初始化到显示表面中。IDirectDrawPalette
- 使用裁剪器避免位图溢出到窗口外。IDirectDrawClipper
创建一个裁剪器,并将其尺寸设置为窗口的范围。 - 在显示表面上绘图。
3.函数介绍
1.创建DirectDraw对象
1.函数DirectDrawCreateEx()
1 | HRESULT WINAPI DirectDrawCreateEx( GUID FAR * lpGuid, |
- lpGUid:需要使用的显卡驱动的GUID。传递为NULL表示使用系统默认值。
- lplpDD:指向指针的指针。调用成功返回指向IDirectDraw的指针的指针。
- iid:需要包含dxguid.lib库。表示DirectDraw版本的id。eg.IID_IDirectDraw7
DX中iid的格式是:IID_IDirectCD
C表示组件:Draw代表DirectDraw,Sound代表DirectSount,Input代表DirectInput等
D是一个2~n的数字代表需要的接口
在ddraw.h中可以找到常量的定义 - pUnkOuter:高级功能,总是设为NULL。
2.对错误进行处理
微软对DX中的错误处理机制用如下两个宏:
FAILED():检测是否失败。返回值是不是DD_OK。
SUCCEEDED():检测是否成功。返回值是DD_OK。
因为DX中的函数返回值有多个。
DirectDrawCreate()返回值参考表page 174,表6-1。
3.释放接口
1 | lpdd->Release(); |
4.示例代码
1 | // 创建 |
2.DirectDraw与Windows协作
我们之前创建的win窗口是windows的功能,使用DirectDraw画出的黑框框调用的是DX的功能。
如果win窗口的大小和DX的窗口设置的大小不一样,dx不会只在win窗口中绘制,而是按照dx中设置的尺寸绘制。这也是IDirectDrawClipper存在的意义。
DirectDraw的两种视频模式:
1.设置协作模式
- 全屏模式: 整个屏幕分配给游戏,并且是直接写到视频设备里。
- 窗口模式: 必须与windows协作,因为其他程序可能需要更新win窗口的用户区域。
IDirectDraw7::SetCooperativeLevel()原型:1
2HRESULT SetCooperativeLevel(HWND hWnd, // 窗口句柄
DWORD dwFlags); // 标识符
如果调用成功,返回DD_OK。
值 | 描述 |
---|---|
DDSCL_ALLOWMODEX | 允许MODEX显示模式。仅当设置了DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN是生效 |
DDSCL_ALLOWREBOOT | 允许在排他(全屏)模式下检测Ctrl+Alt+Del |
DDSCL_EXCLUSIVE | 排他模式。需要和DDSCL_FULLSCREEN共同使用 |
DDSCL_FPUSETUP | 自定义配置FPU |
DDSCL_FULLSCREEN | 全屏。其他应用的GUI不能写屏。与DDSCL_EXCLUSIVE共同使用 |
DDSCL_MULTIHREADED | 请求用于多线程安全的DirectDraw行为 |
DDSCL_NORMAL | 是一个普通的windows应用程序。即不使用dx,而使用GDI。不能和DDSCL_ALLOWMODEX、DDSCL_EXCLUSIVE、DDSCL_FULLSCREEN同时使用 |
DDSCL_NOWINDOWCHANGES | 不允许DirectDraw激活时最小化或还原应用程序窗口 |
总结:
- MODEX+全屏模式:DDSCL_ALLOWMODEX|DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN(|DDSCL_ALLOWREBOOT)
- 排他模式(全屏模式):DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN(|DDSCL_ALLOWREBOOT)
- 普通模式(GDI):DDSCL_NORMAL
- 可以单独使用的:DDSCL_FPUSETUP、DDSCL_MULTIHREADED、DDSCL_NOWINDOWCHANGES
2.示例代码
1
2
3
4
5
6// 设置全屏模式
if(FAILED(lpdd7->SetCooperativeLevel(hwnd,
DDSCL_ALLOWMODEX|DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN|DDSCL_ALLOWREBOOT)))
{
// failed...
}
1 |
|
3.DirectDraw设置显卡模式
在DOS里面通过记不得ROM BIOS设置视频模式。在DX里面,对于视频模式的转换非常简单,使用函数IDirectDraw7::SetDisplayMode():1
2
3
4
5HRESULT SetDisplayMode(DWORD dwWidth, // dx窗口宽度(单位是像素)
DWORD dwHeight, // dx窗口高度(单位是像素)
DWORD dwBPP, // 每像素深度(bit per pixel)
DWORD dwRefreshRate, // 刷新速率,0使用默认值
DWORD dwFlags); // 高级功能标识。0使用默认值。
宽度 | 高度 | 色深(BPP) | 是否为ModeX |
---|---|---|---|
320 | 200 | 8 | yes |
320 | 240 | 8 | yes |
320 | 400 | 8 | yes |
512+ | 512+ | 8,16,24,32 | no |
只要显卡支持,可以请求任何希望的模式。
如果是8位以下的,需要调色板。
4.创建调色板
创建256色调色板的步骤:
1.创建调色板数组:大小为256,类型为PALETTEENTRY调色板项的数组
2.创建调色板对象IDirectDrawPalette。将直接映射到VGA调色板寄存器
3.将调色板对象关联到绘图表面(显示表面)
4.(可选)改变调色板项或整个调色板
1.创建调色板项
调色板项PALETEEENTRY原型:1
2
3
4
5
6
7typedef struct tagPALETTEENTRY
{
BYTE peRed; // red 8-bit
BYTE peGreen; // green 8-bit
BYTE peBlue; // blue 8-bit
BYTE peFlags; // 控制标记,设置为 PC_NOCOLLAPSE,禁止win32/dx优化调色项
}PALETTEENTRY;
创建的256色调色板,一般第0项为黑色,第255项为白色。
示随机颜色调色板例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17PALETEENTRY palette[256];
for(int i=1;i<255;++i)
{
paletee[i].peRed=rand()%256;
paletee[i].peGreen=rand()%256;
paletee[i].peBlue=rand()%256;
paletee[i].peFlags=PC_NOCOLLAPSE;
}
palette[0].peRed=0;
palette[0].peGreen=0;
palette[0].peBlue=0;
paletee[0].peFlags=PC_NOCOLLAPSE;
palette[255].peRed=255;
palette[255].peGreen=255;
palette[255].peBlue=255;
paletee[255].peFlags=PC_NOCOLLAPSE;
2.创建调色板
创建IDirectDrawPalette实例,使用函数:
IDirectDraw7::CreatePalette()原型:1
2
3
4HRESULT CreatePalette(DWORD dwFlags,
LPPALETTEENTRY lpColorTable, // 调色项表
LPDIRECTDRAWPALETTE FAR * lplpDDPalette,
IUnKnow FAR *pUnkOuter);
值 | 描述 |
---|---|
DDPCAPS_1BIT | 1-位色彩。色彩表包含2项 |
DDPCAPS_2BIT | 2-位色彩。色彩表包含4项 |
DDPCAPS_4BIT | 4-位色彩。色彩表包含16项 |
DDPCAPS_8BIT | 8-位色彩。色彩表包含256项 |
DDPCAPS_8BITENTRIES | 用于一个称为索引调色板(indexed palette)的高级特性,适用于1、2和4位调色板 |
DDPCAPS_ALPHA | 高级性能。将PALETTEENTRY中的pfFlags翻译成一个控制透明度的8位alpha值。这个标记创建的调色板只能配置到一个用DDSCAPS_TEXTURE性能标记创建的D3D文理表面 |
DDPCAPS_ALLOW256 | 调色板可以定义所有256项内容。正常情况下,0项和255项分别相应的接收黑和白。 |
DDPCAPS_INITIALIZE | 用lpDDColorArray传递的色彩数组中的色彩初始化调色板。使得调色板数据能被传递并下载到硬件调色板 |
DDPCAPS_PRIMARYSURFACE | 这个调色板将配置到主显示表面。改变调色板的色表会立即影响显示,除非DDPSETPAL_VSYNC是特定的值并受支持的 |
DDPCAPS_VSYNC | 强调调色板更新只能在垂直同步的空白期间执行。最小化色彩异常和闪烁。不过还未获完全支持 |
8位调色板需要的控制标记是 DDPCAPS_8BIT|DDPCAPS_ALLOW256|DDPCAPS_INITIALIZE
创建调色板的示例代码:1
2
3
4
5
6
7
8LPDIRECTDRAWPALETTE lpddpal =NULL;
if(FAILED(lpdd->CreatePalette(DDPCAPS_8BIT|DDPCAPS_ALLOW256|DDPCAPS_INITIALIZE,
palette,
&lpddpal,
NULL)))
{
// error
}
3.创建显示表面
显示在屏幕上图像只不过是以某种格式存储在内存中的有色像素组成的矩阵。
DirectDraw的设计者将显存的概念予以抽象,从而使得无论用什么显卡,访问视频表面的方法都是一样的。因此,DirectDraw支持显示表面(Surface)。
显示表面是能存储位图数据的矩形内存区域。
主显示表面:被光栅化的实际显存,任何时候都是可见的。指向屏幕图像并常驻VRAM或系统中。只能有一个。
从显示表面:可以有多个。和主显示表面有同样的色彩深度和几何分步。使用下一帧的动画离屏渲染,然后拷贝或者用换页技术奖离屏面切换到主显示面。也可以保存你的位图图像和游戏中表示对象的动画。
1.创建显示表面步骤
- 填充一个DDSURFACEDESC2数据结构,用于描述创建的显示表面
- 调用IDirectDraw7::CreateSurface()来创建显示表面
1.CreateSurface()函数原型:1
2
3
4HRESULT CreateSurface(
LPDDSURFACEDESC2 lpDDSurfaceDesc2, // 显示表面描述
LPDIRECTDRAWSURFACE4 FAR *lplpDDSurface, // 显示表面
IUnknow Far *pUnOuter); // 设置为NULL
2.DDSURFACEDESC2描述结构
1 | typedef struct _DDSURFACEDESC2 |
其中几个关键的字段的具体功能如下:
dwsize:所有的DirectX数据结构里面最重要的一个字段。很多DirectX数据结构都是通过地址传递的。因此,先通过dwsize设置数据结构的大小,接收函数就知道传过来的内容有多少数据需要处理了。
举例:1
2DDSURFACEDESC2 ddsd;
ddsd.size=sizeof(DDSURFACEDESC2);dwFlags:用于指定,把有效数据填充到哪个字段。
举例:1
ddsd.dwFlags=DDSD_WIDTH|DDSD_HEIGHT;
表明dwWidth和dwHeight字段是有效数据。
可选标志的命名规则是DDSD_XXX- dwWidth:像素计数的宽度
- dwHeight:像素计数的高度
lPitch:所在显示模式的水平内存跨距。该显示模式中每行的字节数。(详细内容在page 188)
注意,lPitch可能不等于设置的显示模式的水平分辨率。1
2// 8位颜色模式下,使用示例
ddsd.lpSurface[x+y*ddsd.lPitch]=color;lpSurface:指向所创建的显示表面所驻留的实际内存的指针。
- dwBackBufferCount:设置或者是读取后备缓冲或主显示表面的二级从属离屏切换缓冲的数目。
为实现动画的平滑显示,通过创建一个主缓冲,一个或多个后备缓冲。渲染是在后备缓冲离屏渲染的,之后将渲染结果快速拷贝到主缓冲。
使用一个后备缓冲的技术称为双缓冲(Double Buffering),使用两个后备缓冲的技术称为三缓冲(Triple Buffer). - ddckCKDestBlt:控制目标色键(color key)。即在位块传输操作中控制可以写入的色彩部件。
- ddckCKSrcBlt:指示源色键,即基本上是执行位图操作是不希望进行位块传输的颜色。是为位图设置透明颜色的方法。
- ddpfPixelFormat:显示表面的像素格式。(数据结构:DDPIXLFORMAT)
DDPIXLFORMAT中几个重要的字段:dwSize/dwFlags/dwRGBBitCount ddsCaps:返回显示表面的一些未在别处定义的属性。(数据结构:DDSCAPS2)
另外,还有dwCaps2字段是给3D内容使用的,dwCap3和dwCaps4是留给将来扩展的。
设置主显示表面的示例:1
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
其中,dwCaps标志控制显示表面的容量控制:
值 | 描述 |
---|---|
DDSCAPS_BACKBUFFER | 表示该显示表面是一个屏幕翻转结构的后备缓冲(是个后备缓冲) |
DDSCAPS_COMPLEX | 是个复杂的显示表面,即该表面拥有一个主显示缓冲和一个或多个后备缓冲以生成翻转链 |
DDSCAPS_FLIP | 显示表面是一个平面翻转结构的一部分。当这个标记传递给CreateSurface()方法时,将会创建一个前端缓冲和一个或多个后备缓冲 |
DDSCAPS_LOCALVIDMEM | 显示表面存在于本地显存。使用时DDSCAPS_VIDEOMEMORY也必须指定。 |
DDSCAPS_MODEX | 是一个Mode X显示表面 |
DDSCAPS_NONLOCALVIDMEM | 显示表面存在于非本地显存。使用时DDSCAPS_VIDEOMEMORY也必须指定 |
DDSCAPS_OFFSCREENPLAN | 是个离屏表面。不是一个特殊表面如覆盖、纹理、z-缓冲、前端缓冲、后端缓冲或者alpha缓冲表面。通常用于图元精灵 |
DDSCAPS_OWNDC | 该表面有一个长期的设备环境描述 |
DDSCAPS_PRIMARYSURFACE | 是个主显示表面 |
DDSCAPS_STANDARDVGAMODE | 标准的VGA平面,而不是Mode X平面。不能与DDSCAPS_MODEX同时使用 |
DDSCAPS_SYSTEMMEMORY | 该显示表面内存从系统内存中分配 |
DDSCAPS_VIDEOMEMORY | 显示表面存在于显存中 |
2.创建显示表面示例代码
创建一个和显示模式有同样的大小和色彩深度的简单主显示表面的示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13LPDIRECTDRAWSURFACE7 lpddsprimary=NULL;
DDSURFACEDESCS2 ddsd;
memset(&ddsd,0,sizeof(DDSURFACEDESCS2));
ddsd.dwSize= sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS;
ddsd.ddsCaps=DDSCAPS_PRIMARYSURFACE;
if(FAILED(lpdd->CreateSurface(&ddsd,&lpddsprimary,NULL)))
{
// error....
}
4.关联调色板
调用IDirectDrawSurface7::SetPalette()函数:
示例代码:1
lpddsprimary->SetPalette(lpddpal)
5.绘制像素
关键字:video_buffer 内存间距 像素格式 Lock
1. 显示内存video_buffer的获取:ddsd.lpSurface
1 | UCHAR *video_buffer = (UCHAR *)ddsd.lpSurface; |
其中,lpSurface是LPVOID类型,可以转为UCHAR类型,也可以转为USHORT类型。
具体根据像素的位深来决定,有利于找到像素的地址。
2. 内存间距(memory_pitch)
内存间距代表了实际硬件中每行的字节数。
但是,我们设定的显示分辨率width,有可能并不是和内存间距一样的。
在找(x,y)像素的地址的时候,就需要使用到内存间距去计算了。
eg. 8位位深的像素设置color的时候:1
video_buffer[x+y*memory_pitch]=color;
如果不是一个字节的位深,例如,使用了16位位深的color16。
memory_pitch是字节数,而一个像素是2个字节。所以,memory_pitch>>1为内存间距每行的USHORT数。
此时设置像素的代码为:1
2USHORT *video_buffer16=(USHORT *)ddsd.lpSurface; // 转为USHORT,两个字节
video_buffer16[x+y*memory_pitch>>1]=color16;
3. 像素格式
8位位深的时候,使用调色板,8位位深的值代表了调色板的index。
16位位深像素的格式有一下几种:
RGB16BIT555:最高一位不使用,或者用作alpha。其他以此是5位red,5位green,5位blue.
blue不用移位;green左移5位;red左移10位。
定义宏:1
RGB16BIT565:5位red,6位green,5位blue。 绿色多一位是因为人眼对绿色比较敏感。
blue不用移位;green左移5位;red左移11位。
定义宏:1
4. LOCK:加锁和解锁
无论是主显示表面还是从显示表面, 在使用video_buffer之前,要先对其进行加锁;使用完毕之后,进行解锁释放。
加锁原因:
1. 告诉DirectDraw已经控制内存了
2. 指示显示设备不能在被操作期间移动catch和虚拟内存缓冲区。(如果显存是虚拟的,可能会移动)
加锁和解锁函数:
- IDirectDrawSurface7::Lock()原型:
1
2
3
4HRESULT Lock(LPRECT lpDestRect, // 加锁的rect。设置为NULL的时候表示全部加锁
LPDDSURFACEDESC lpDDSurfaceDesc, // 要加锁的表面
DWORD dwFlags, // 控制标记
HANDLE hEvent); // 高级事件,设置为NULL
值 | 描述 |
---|---|
DDLOCK_READONLY | 被锁的显示表面是只读的 |
DDLOCK_SURFACEMEMORYPTR | 将返回一个有效的内存指针,指向特定的RECT顶部。如果没有指定矩形,将会返回一个指向显示表面顶部的指针 |
DDLOCK_WAIT | 如果由于正在进行一个位块传输操作或者有另外的错误发生而不能获得一个锁,将会一直重试,直到得到锁。 |
DDLOCK_WRITEONLY | 被锁的显示表面是只写的 |
- IDirectDrawSurface7::UnLock()原型:
1
2HRESULT UnLock(LPRECT lpDestRect, // 解锁的rect。设置为NULL的时候表示全部解锁
);
5. 绘制像素的函数
显示表面内存为ddsd.lpSurface,内存跨距为ddsd.lPitch1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30// 1. 8位位深(调色板)模式下的绘制像素函数:
inline void Plot8(int x,int y,
UCHAR color,
UCHAR *buffer,
int mempitch)
{
buffer[x + y * mempitch] = color;
}
// 2. 16位555格式下的绘制像素函数:
inline void Plot16BIT555(int x,int y,
UCHAR red,
UCHAR green,
UCHAR blue,
USHORT *buffer,
int mempitch)
{
buffer[x + y * mempitch >> 1] = RGB16BIT555(red,green,blue);
}
// 2. 16位565格式下的绘制像素函数:
inline void Plot16BIT565(int x,int y,
UCHAR red,
UCHAR green,
UCHAR blue,
USHORT *buffer,
int mempitch)
{
buffer[x + y * mempitch >> 1] = RGB16BIT565(red,green,blue);
}
6.清理资源
在Game_Exit的时候,要清空占用的资源。需要注意的是,清空资源的时候,按照创建资源的反向顺序执行。(资源栈,先进后出)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19int Game_Exit(void *params , int num_parms )
{
if(lpddpal)
{
lpddpal->Release();
lpddpal = NULL;
}
if (lpddsprimary)
{
lpddsprimary->Release();
lpddsprimary = NULL;
}
if(lpdd)
{
lpdd->Release();
lpdd = NULL;
}
return 1;
}