关键字:内存填充 Blitter Blt/BltFast 色键(color key)
1. 快速记录
- 需求:图像内存的拷贝
- 将ARect的图像拷贝到BRect
- 为什么需要硬件块移动单元
- 软件copy像素,需要一个像素一个像素的拷贝(优化:乘法使用位移),耗费cpu周期
- 需要处理透明像素:某些像素不必拷贝
- Blit:硬件块移动单元的功能优势
- 速度快: 可以直接填充内存或者移动大块VRAM和DirectDraw表面
- 功能多:处理透明度、缩放、旋转、色键等
- Blit函数:
- IDIRECTDRAWSURFACE7::Blt();
- IDIRECTDRAWSURFACE7::BltFast();
2. 内存填充
- 创建的主显示表面,在VRAM中。但是,当创建后备缓冲显示表面的时候,如果VRAM内存不够的,会将后备缓冲显示表面创建到系统RAM中。当使用页面切换的时候,会发生需要从系统RAM到VRAM的拷贝。 使用硬件显存块移动单元Blitter在VRAM内部移动位图,会快得多。双缓冲方案比较在VRAM动画系统进行页面切换要慢。
- 绘制位图,或者对视频表面进行填充。
- 从一个表面向另一个表面拷贝位图
3. 使用Blitter进行内存填充
1.IDirectDrawSurface7::Blt()函数原型
提供bit块移动。块移动时,不支持z-buffering和alpha blending.
IDirectDrawSurface7自己是目标surface.
1 | HRESULT Blt( |
- lpDestRect:目标矩形。定义了左上角和右下角的Rect结构,是要指向目标表面blit的区域。如果参数为NULL,将使用整个表面作为目标表面。
- lpDDSrcSurface:被用做blit源的DirectDraw表面的IDirectDrawSurface7接口地址
- lpSrcRect:源Rect.指向包含blit源表面上矩形区域的左上角和右下角的RECT结构的地址。如果参数为NULL,将使用整个表面作为目标表面。
- dwFlags:决定DDBLTFX结构的有效成员。在DDBLTFX结构中特殊的行为诸如缩放、旋转等等都可以被控制,还有色彩键的信息。
- lpDDBltFx:指向DDBLTFX,DDBLTFX包含有blitter所需要的信息结构。
DDBLTFX的结构:1
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
31
32
33
34
35
36
37
38
39
40
41
42typedef struct _DDBLTFX {
DWORD dwSize; // 结构体的size
DWORD dwDDFX; // blitter fx的类型
DWORD dwROP; // win32 支持的光栅化操作 (ROP -> raster ops)
DWORD dwDDROP; // DirectDraw 支持的光栅化操作
DWORD dwRotationAngle; // 旋转角度
DWORD dwZBufferOpCode; // z-buffer (高级功能)
DWORD dwZBufferLow;
DWORD dwZBufferHigh;
DWORD dwZBufferBaseDest;
DWORD dwZDestConstBitDepth;
union {
DWORD dwZDestConst;
LPDIRECTDRAWSURFACE lpDDSZBufferDest;
} DUMMYUNIONNAMEN(1);
DWORD dwZSrcConstBitDepth;
union {
DWORD dwZSrcConst;
LPDIRECTDRAWSURFACE lpDDSZBufferSrc;
} DUMMYUNIONNAMEN(2);
DWORD dwAlphaEdgeBlendBitDepth;
DWORD dwAlphaEdgeBlend;
DWORD dwReserved;
DWORD dwAlphaDestConstBitDepth;
union {
DWORD dwAlphaDestConst;
LPDIRECTDRAWSURFACE lpDDSAlphaDest;
} DUMMYUNIONNAMEN(3);
DWORD dwAlphaSrcConstBitDepth;
union {
DWORD dwAlphaSrcConst;
LPDIRECTDRAWSURFACE lpDDSAlphaSrc;
} DUMMYUNIONNAMEN(4);
union {
DWORD dwFillColor; // 要填充的颜色
DWORD dwFillDepth; // 填充的z(高级功能)
DWORD dwFillPixel; // RGB(alpoha)填充的颜色
LPDIRECTDRAWSURFACE lpDDSPattern;
} DUMMYUNIONNAMEN(5);
DDCOLORKEY ddckDestColorkey; // 目标色键
DDCOLORKEY ddckSrcColorkey; // 源色键
} DDBLTFX, *LPDDBLTFX;
值 | 描述 | |
---|---|---|
一般标志 | ||
DDBLT_COLORFILL | dwFillColor起作用,目标矩形区域填充的颜色 | |
DDBLT_DDFX | dwDDFX。blit的效果 | |
DDBLT_DDROPS | dwDDROP成员。指出非win32 API部分的光栅操作(ROPs) | |
DDBLT_DEPTHFILL | dwFillDepth成员。要填充到目标矩形的深度缓冲值 | |
DDBLT_KEYDESTOVERRIDE | ddckDestColorkey成员。目标表面的色彩键 | |
DDBLT_KEYSRCOVERRIDE | ddckSrcColorkey成员。源表面的色彩键 | |
DDBLT_ROP | dwRop.win32 API中的光栅化操作 | |
DDBLT_ROTATIONANGLE | dwRotationAngle.该表面的旋转角度(1/100度为单位)。只硬件支持。HEL不支持 | |
色彩键标志 | ||
DDBLT_KEYDEST | ddckDestColorKey或ddckSrcColorkey.使用和目标表面相关的色彩关键字 | |
DDBLT_KEYSRC | ddckDestColorKey或ddckSrcColorkey.使用和源表面相关联的色彩关键字 | |
行为标志 | ||
DDBLT_ASYNC | 按照接收次序通过FIFO异步地执行转换。若FIFO硬件中没有足够空间,则该调用失败。速度快,但是有点冒险 | |
DDBLT_WAIT | 等待直到blit能被执行,并且即使blitter正忙也不返回错误信息 |
2.IDirectDrawSurface7::BltFast()函数原型
1 | HRESULT BltFast( |
- dwX/dwY:blit到目标表面上的(x,y)坐标
- lpDDSrcSurface:源DirectDraw表面的地址
- lpSrcRect:源表面上需要blit的矩形区域的左上角和右下角的Rect结构
- dwFlags:blitter的操作控制标记
- lpDDBltFx:指向DDBLTFX,DDBLTFX包含有blitter所需要的信息结构。
值 | 描述 |
---|---|
DDBLTFAST_SRCCOLORKEY | 使用源色键的透明blit |
DDBLTFAST_DESTCOLORKEY | 使用目标色键的透明blit |
DDBLTFAST_NOCOLORKEY | 不使用色键。不透明的blit。在硬件上素服要快一些。在HEL上速度更快 |
DDBLTFAST_WAIT | 等待直到blit能被执行,并且即使blitter正忙也不返回错误信息 |
3.Blt和BltFast的区别
Blt()使用了DirectDraw裁剪器,而BltFast()没有。所以BltFast()要快%10,使用硬件时更快。
因此,在需要裁剪的时候,用Blt(),不需要裁剪的时候,用BltFast().
4.Blt/BltFast填充内存的步骤
- 设置DDBLTFX结构的dwFillColor字段,设置要填充的色彩索引(256色)或者RGB编码
- 设置RECT结构,作为目标表面上要填充的区域.
- 设置Blt/BltFast的dwFlags为DDBLT_COLORFLILL|DDBLT_WAIT,并调用blt()/bltFast()方法.注意,方法都是从目标表面的接口上调用的,而不是从源表面。
ps.Rect结构发送给大多数DirectDraw函数的时候,一般是包含左上角,但是不包含右下角。
pps.一般范围的设定都是左闭右开的[a,b)
5.Blt()填充内存代码示例:
1 | // 每帧调用 |
4. 使用Blitter进行内存拷贝
Billter的作用就是从源内从向目标内存复制矩形位图。可以复制整个屏幕,也可以只复制矩形区域。
使用Blt()函数是,一般发送一个源RECT和表面以及一个目标RECT和表面来执行blit.
源表面和目标表面可以是同一个,代表了同一个表面的位图拷贝.(比如sprite引擎)
我们创建了一个主表面,并且给主表面关联了一个后备缓冲表面。那么,我们只需要在后备缓冲表面上进行绘制,然后再将后备缓冲表面内存全部copy到主表面。当前,也可以只拷贝一部分。
向主表面拷贝备用缓冲表面内存区域demo步骤:
- 在初始化的时候,将备用缓冲表面初始化为某一种颜色。(否则,因为不在备用缓冲表面上绘制东西,黑色拷贝到黑色,什么都看不出来)
- 每帧调用,随机一个源矩形区域和一个目标矩形区域,进行拷贝
1
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
31
32
33
34int TestInitBackSurfaceColor()
{
lpddsback->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);
USHORT *video_buffer = (USHORT *)ddsd.lpSurface;
// draw the gradient
for (int index_y = 0; index_y < SCREEN_HEIGHT; index_y++)
{
// build color word up
DWORD color = __RGB16BIT565(0, (index_y >> 3), 0);
// replicate color in upper and lower 16 bits of 32-bit word
color = (color) | (color << 16);
// now color has two pixel in it in 16.16 or RGB.RGB format, use a DWORD
// or 32-bit copy to move the bytes into the next video line, we'll need
// inline assembly though...
// draw next line, use a little inline asm baby!
_asm
{
CLD; clear direction of copy to forward
MOV EAX, color; color goes here
MOV ECX, (SCREEN_WIDTH / 2); number of DWORDS goes here
MOV EDI, video_buffer; address of line to move data
REP STOSD; send the pentium X on its way
} // end asm
// now advance video_buffer to next line
video_buffer += (ddsd.lPitch >> 1);
} // end for index_y
lpddsback->Unlock(NULL);
}
每帧调用:1
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
31
32
33
34
35
36
37
38int TestBlitCopy()
{
DDBLTFX ddbltfx;
RECT dest_rect, src_rect;
DDRAW_INIT_STRUCT(ddbltfx);
int x1 = rand() % SCREEN_WIDTH;
int y1 = rand() % SCREEN_HEIGHT;
int x2 = rand() % SCREEN_WIDTH;
int y2 = rand() % SCREEN_HEIGHT;
int x3 = rand() % SCREEN_WIDTH;
int y3 = rand() % SCREEN_HEIGHT;
int x4 = rand() % SCREEN_WIDTH;
int y4 = rand() % SCREEN_HEIGHT;
// 全屏拷贝
// int x1 = 0;
// int y1 = 0;
// int x2 = SCREEN_WIDTH;
// int y2 = SCREEN_HEIGHT;
// int x3 = 0;
// int y3 = 0;
// int x4 = SCREEN_WIDTH;
// int y4 = SCREEN_HEIGHT;
src_rect.left = x1;
src_rect.top = y1;
src_rect.right = x2;
src_rect.bottom = y2;
dest_rect.left = x3;
dest_rect.top = y3;
dest_rect.right = x4;
dest_rect.bottom = y4;
lpddsprimary->Blt(&dest_rect, lpddsback, &src_rect, DDBLT_WAIT, NULL);
return 1;
}
5.裁剪
1.裁剪知识
当从一个表面向另外一个表面复制像素的时候,一方面,可以通过ColorKey定义透明色彩键,不复制透明色彩;另一方面,可以通过裁剪,从源Rect显示至目标Rect,或者,使得目标表面中指显示某些区域。
这里介绍的是将像素按照窗口或者视口区域进行裁剪,即拷贝显示在视口区域内的像素.
2.位图裁剪技巧(自定义位图裁剪)
裁剪位图有两种方法:
方法一:在生存像素的时候裁剪位图中的每一个像素。简单但是速度慢。
方法二:按视口剪切位图的矩形边界,只画位图位于视口两种的部分。较复杂,但是速度快,几乎没有性能损失。
这里使用方法二。
方法二描述:
8位bitmap的裁剪。
将一个位于x,y位置的bitmap像素绘制到video_buffer中去。
已知:
位置:posx,posy
bitmap:width,height。bitmap像素index,0作为透明ColorKey,不复制。复制的部分index为1;
video_buffer和mempitch
SCREEN_WIDTH,SCREEN_HEIGHT
裁剪代码示例:1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49// 裁剪,bitmap从左上角开始计算位置
// posX,posY是要绘制的bitmap的位置。
void Blit_Clipped(int posX, int posY, int sizeWidth, int sizeHeight, UCHAR *bitmap, UCHAR *video_buffer, int mempitch)
{
// bitmap完全在界面外
if (posX >= SCREEN_WIDTH || posY >= SCREEN_HEIGHT || (posX + sizeWidth) <= 0 || (posY + sizeHeight) <= 0)
{
return;
}
// x1,y1,x2,y2为真正要绘制的bitmap的区域
int x1 = posX; // bitmap 左上角位置
int y1 = posY;
int x2 = x1 + sizeWidth - 1; // bitmap 右下角位置
int y2 = y1 + sizeHeight - 1;
if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0;
if (x2 >= SCREEN_WIDTH) x2 = SCREEN_WIDTH - 1;
if (y2 >= SCREEN_HEIGHT) y2 = SCREEN_HEIGHT - 1;
int x_off = x1 - posX;
int y_off = y1 - posY;
int dx = x2 - x1 + 1;
int dy = y2 - y1 + 1;
// 在video_buffer中的初始位置
video_buffer += (x1 + y1 * mempitch);
// 要绘制的在bitmap中的初始位置,即,bitmap部分要绘制的部分
// (移动bitmmap的绘制起始位置)
bitmap += (x_off + y_off * sizeWidth);
UCHAR pixel;
for (int index_y = 0; index_y < dy; index_y++)
{
for (int index_x = 0; index_x < dx; index_x++)
{
if ((pixel = bitmap[index_x])) // 如果pixel赋值之后是0,返回false,就是不复制
{
video_buffer[index_x] = pixel;
}
}
video_buffer += mempitch;
bitmap += sizeWidth;
}
} // end Blit_Clipped
裁剪demo代码示例:1
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
UCHAR happy_bitmap[64] = {0,0,0,0,0,0,0,0,
0,0,1,1,1,1,0,0,
0,1,0,1,1,0,1,0,
0,1,1,1,1,1,1,0,
0,1,0,1,1,0,1,0,
0,1,1,0,0,1,1,0,
0,0,1,1,1,1,0,0,
0,0,0,0,0,0,0,0 };
typedef struct HappyFace_TYP
{
int x, y;
int vx, vy;
}HappyFace, *HappyFace_PTR;
HappyFace happy_faces[100];
// 初始化100个bitmap的位置
void TestInitHappyFace()
{
for (int i = 0; i < 100; ++i)
{
happy_faces[i].x = rand() % SCREEN_WIDTH;
happy_faces[i].y = rand() % SCREEN_HEIGHT;
happy_faces[i].vx = -2 + rand() % 5;
happy_faces[i].vy = -2 + rand() % 5;
}
}
int TestGameMainHappyFace()
{
DDBLTFX ddbltfx;
DDRAW_INIT_STRUCT(ddbltfx);
ddbltfx.dwFillColor = 255;
// 先将backsurface设置为白色
if (FAILED(lpddsback->Blt(NULL,
NULL,
NULL,
DDBLT_COLORFILL | DDBLT_WAIT,
&ddbltfx)))
{
return 0;
}
DDRAW_INIT_STRUCT(ddsd);
// 在backsurface绘制100个happy_face
if (FAILED(lpddsback->Lock(NULL, &ddsd,
DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL)))
{
return 0;
}
for (int i = 0; i < 100; ++i)
{
UCHAR *video_buffer = (UCHAR*)ddsd.lpSurface;
// 绘制一个happy_face
Blit_Clipped(happy_faces[i].x, happy_faces[i].y, 8, 8, happy_bitmap, video_buffer, ddsd.lPitch);
}
// 改变happy_face的位置
for (int i = 0; i < 100; ++i)
{
happy_faces[i].x += happy_faces[i].vx;
happy_faces[i].y += happy_faces[i].vy;
if (happy_faces[i].x > SCREEN_WIDTH)
{
happy_faces[i].x = -8;
}
else if (happy_faces[i].x < -8)
{
happy_faces[i].x = SCREEN_WIDTH;
}
if (happy_faces[i].y > SCREEN_HEIGHT)
{
happy_faces[i].y = -8;
}
else if (happy_faces[i].y < -8)
{
happy_faces[i].y = SCREEN_HEIGHT;
}
}
if (FAILED(lpddsback->Unlock(NULL)))
{
return 0;
}
// 翻转
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT)));
return 1;
}
注意:
1. 自己定义的调色板不起作用。调用的其实是系统的调色板。index=1为红色。
3.IDirectDrawClipper裁剪
使用DirectDrawClipper裁剪器,只显示有效的裁剪区域,之后再使用Blt得到裁剪后。
DirectDraw提供了IDirectDrawClipper接口,传给它有效的裁剪区域,然后将它同表面连接。
使用DirectDraw进行裁剪的步骤:
- 创建DirectDraw裁剪器对象
- 创建裁剪序列
- 用IDIRECTDRAWCLIPPER::SetClipList()将裁剪序列发送给裁剪器
- 用IDIRECTDRAWSURFACE7::SetClipper()将裁剪器同窗口或表面向关联
1.创建裁剪器
1 | HRESULT LPDIRECTDRAW7::CreateClipper(DWORD dwFlags, // 控制标记,填0 |
2.裁剪序列
DirectDraw下,裁剪列表是矩形的列表,存储为RECT。这些矩形对应于可以进行blit的有效区域。
blitter硬件将只绘制裁剪区内的内容,这些裁剪区就叫做裁剪序列(Clip List)。
创建一个裁剪序列RGNDATA(Region Data)数据结构:1
2
3
4typedef struct _RGNDATA {
RGNDATAHEADER rdh; // 存储裁剪RECT的描述信息
char Buffer[1]; // 存储真正的RECT信息
} RGNDATA, *PRGNDATA, NEAR *NPRGNDATA, FAR *LPRGNDATA;
1 | // RGNDATAHEADER结构 |
3.创建裁剪器示例代码
1 |
|
4.裁剪器代码示例
- 在Game_Init()中添加初始化裁剪器
- 在Game_Main()中调用primarySurface->Blt,拷贝backSurface区域内容
1 |
|
n. 注意事项
i5-4570 cpu,win7操作系统,分辨率水平最小640,垂直最小480. 小于其中任何一个值的分辨率
不能正常显示.
-> 由于分辨率的设置,会导致显示不正常- 编译的时候,使用win32,32位应用程序。64位会出错。