10.1、内存管理(续)
- 把内存管理相关代码放到memory.c中。
- 为了方便以后的内存管理,内存分配、释放时采用4KB为单位(向下取整)。
unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
unsigned int a;
size = (size + 0xfff) & 0xfffff000;
a = memman_alloc(man, size);
return a;
}
int memman_free_4k(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
int i;
size = (size + 0xfff) & 0xfffff000;
i = memman_free(man, addr, size);
return i;
}
10.2、叠加处理
这要提到层的概念
鼠标为一层,背景为一层。
- 图层结构体
每一层都要有相关信息:
struct SHEET {
unsigned char *buf;
int bxsize, bysize, vx0, vy0, col_inv, height, flags;
};
这里参数要好好理解,后面要设计的参数太多了。
buf:这个指针指向图层的缓存地址。之前都是把背景、鼠标都直接绘制在显存里。现在把鼠标、背景都进行了分层,先把每一层的像素信息放到缓存里,如果缓存有改变,再刷新到显存。
bxsize,bysize:该图层的边长,乘积就可以表示缓冲区大小。
vx0,xy0:图层的起始坐标。
col_inv:透明色色号。
height:图层高度,高度越高,优先显示。
flags:该图层管理结构体有没有被使用。
- 图层管理结构体
要管理所有图层就需要一个结构体:
#define MAX_SHEETS 256
struct SHTCTL {
unsigned char *vram;
int xsize, ysize, top;
struct SHEET *sheets[MAX_SHEETS];
struct SHEET sheets0[MAX_SHEETS];
};
vram:显存地址。缓存改变后,就要通过这个变量找到显存位置,把画面刷新到显存。
xsize,ysize:画面大小。就是整个分辨率,最开始设置的为320 * 200。
top:表示目前有多少图层。
sheets0:结构体数组。管理256个图层。
sheets:指向图层的结构体指针数组。主要通过图层高度进行排序。
给图层管理结构体分配空间,并且初始化:
struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize)
{
struct SHTCTL *ctl;
int i;
ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
if (ctl == 0) {
goto err;
}
ctl->vram = vram;
ctl->xsize = xsize;
ctl->ysize = ysize;
ctl->top = -1; /* 一个SHEET都没有 */
for (i = 0; i < MAX_SHEETS; i++) {
ctl->sheets0[i].flags = 0; /* 标记未使用 */
}
err:
return ctl;
}
SHTCTL所占空间:
使用memman_alloc_4k函数分配空间。
初始化vram为显存地址
xsize、ysize为画面大小
top为-1,表示没有图层被显示
flags为0,表示该图层未被使用
初始化好ctl后,使用sheet_alloc函数找出没有使用图层,分配出去:
#define SHEET_USE 1
struct SHEET *sheet_alloc(struct SHTCTL *ctl)
{
struct SHEET *sht;
int i;
for (i = 0; i < MAX_SHEETS; i++) {
if (ctl->sheets0[i].flags == 0) {
sht = &ctl->sheets0[i];
sht->flags = SHEET_USE; /* 标记使用中 */
sht->height = -1; /* 高度设为-1,表示隐藏,不显示 */
return sht;
}
}
return 0; /* 当所有的SHEET都在使用中,就返回0 */
}
分配好之后,就要设置图层的缓冲区地址和大小:
void sheet_setbuf(struct SHEET *sht, unsigned char *buf, int xsize, int ysize, int col_inv)
{
sht->buf = buf;
sht->bxsize = xsize;
sht->bysize = ysize;
sht->col_inv = col_inv;
return;
}
buf:设置为传入的缓冲区地址
xsize,ysize:表示缓冲区的大小
col_inv:表示透明色色号
接下来设置图层高度,并从高往低排好:
void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height)
{
int h, old = sht->height; /* 保存设定前的高度 */
/* 高度范围,穿过来的height过高过低都要重新设定 */
if (height > ctl->top + 1) {
height = ctl->top + 1;
}
if (height < -1) {
height = -1;
}
sht->height = height; /* 修正的高度 */
/* 使用sheets[]进行排序 */
if (old > height) { /* 重新设定的高度比以前低 */
if (height >= 0) {
/* 把高于height的图层提到上面 */
for (h = old; h > height; h--) {
ctl->sheets[h] = ctl->sheets[h - 1];
ctl->sheets[h]->height = h;
}
ctl->sheets[height] = sht;
} else { /* 不显示了 */
if (ctl->top > old) {
/* 把height以上的图层降下来 */
for (h = old; h < ctl->top; h++) {
ctl->sheets[h] = ctl->sheets[h + 1];
ctl->sheets[h]->height = h;
}
}
ctl->top--; /* 不显示了,top就要减一 */
}
sheet_refresh(ctl); /* 按图层顺序刷新 */
} else if (old < height) { /*重设设置的height比以前高 */
if (old >= 0) {
/* 先把比以前高的降下来 */
for (h = old; h < height; h++) {
ctl->sheets[h] = ctl->sheets[h + 1];
ctl->sheets[h]->height = h;
}
ctl->sheets[height] = sht;// 再把该图层提上去
} else { /* 由不显示变成现实 */
/* 先把高于height的提上去 */
for (h = ctl->top; h >= height; h--) {
ctl->sheets[h + 1] = ctl->sheets[h];
ctl->sheets[h + 1]->height = h + 1;
}
ctl->sheets[height] = sht; // 再把该图层提上去(从-1提到height)
ctl->top++; /* 不显示到现实,top要加一 */
}
sheet_refresh(ctl); /* 按图层信息刷新 */
}
return;
}
关于刷新图层的函数sheet_refresh:
void sheet_refresh(struct SHTCTL *ctl)
{
int h, bx, by, vx, vy;
unsigned char *buf, c, *vram = ctl->vram;
struct SHEET *sht;
for (h = 0; h <= ctl->top; h++) {//遍历图层
sht = ctl->sheets[h];
buf = sht->buf;
for (by = 0; by < sht->bysize; by++) {// 遍历对应图层的bysize坐标
vy = sht->vy0 + by;
for (bx = 0; bx < sht->bxsize; bx++) {//遍历xsize坐标
vx = sht->vx0 + bx;
c = buf[by * sht->bxsize + bx];// 取出缓存中对应位置的像素信息
if (c != sht->col_inv) {// 如果不是透明色,就绘制在显存里
vram[vy * ctl->xsize + vx] = c;
}
}
}
}
return;
}
注意:每次刷新都遍历了图层缓冲区内的所有像素。
关于图层的左右移动:
void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0)
{
sht->vx0 = vx0;// 设置图层左上角开始坐标
sht->vy0 = vy0;
if (sht->height >= 0) { /* 如果在显示 */
sheet_refresh(ctl); /* 按图层顺序刷新所有图层 */
}
return;
}
注:没移动一次,就设置移动后的开始坐标,然后刷新所有图层
最后释放图层:
void sheet_free(struct SHTCTL *ctl, struct SHEET *sht)
{
if (sht->height >= 0) {
sheet_updown(ctl, sht, -1); /* 如果该sht图层为显示状态,则设置为隐藏 */
}
sht->flags = 0; /* 设置为未使用状态 */
return;
}
10.3、提高叠加处理速度
之前每次刷新,都是刷新整个图层。如果只是鼠标移动一下,就要把整个背景都重新绘制到显存,现在来改善一下——sheet_refreshsub。
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1)
{
int h, bx, by, vx, vy;
unsigned char *buf, c, *vram = ctl->vram;
struct SHEET *sht;
for (h = 0; h <= ctl->top; h++) {
sht = ctl->sheets[h];
buf = sht->buf;
for (by = 0; by < sht->bysize; by++) {
vy = sht->vy0 + by;
for (bx = 0; bx < sht->bxsize; bx++) {
vx = sht->vx0 + bx;
if (vx0 <= vx && vx < vx1 && vy0 <= vy && vy < vy1) {
c = buf[by * sht->bxsize + bx];
if (c != sht->col_inv) {
vram[vy * ctl->xsize + vx] = c;
}
}
}
}
}
return;
}
这个函数几乎和sheet_refresh差不多,多了一个 if 判断刷新范围:
vx0 <= vx < vx1 && vy0 <= vy <vy1才会往缓存和显存写入。
- 鼠标移动刷新:
void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0)
{
int old_vx0 = sht->vx0, old_vy0 = sht->vy0;
sht->vx0 = vx0;
sht->vy0 = vy0;
if (sht->height >= 0) {
sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize);
sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize);
}
return;
}
首先刷新移动前,再刷新移动后:
如果鼠标移动走了,鼠标移动前的位置范围就是刷新范围。过程如下:
1、鼠标移动,开始移动前刷新;
1.1、背景0图层刷新,会把鼠标原来的位置给抹平。
1.2、鼠标1图层刷新,如果移动后鼠标不和移动前位置重合,该图层就不会刷新,会刷新重合部分。
2、移动后刷新
2.1、背景0图层刷新,如果移动前后位置不重合,背景图层不刷新。如果重合,刷新重叠部分。(注意:移动前刷新,绘制了重叠的鼠标,现在背景又给覆盖了,相当于无用功)
2.2、鼠标1图层刷新,把以后后的鼠标绘制出来。
关于old_vx0:我开始就在想,刚初始化的时候,根本没有设置一个图层vx0,那么sht->vx0不就会是一个随机数吗?
答:不管sht->vx0是多少,图层第一次调用这个函数时,一般height为-1,不会进行刷新,old_vx0是多少就没有关系了,这也是作者的代码思路。
如果先updown,把图层变成显示状态,然后调用sheet_slide函数。比较幸运的是,作者使用的编译器会初始化结构体变量的值,如果是int类型,就初始化为0。我使用vc6测试c语言的时候,该编译器不做初始化。
- 字符刷新:
void sheet_refresh(struct SHTCTL *ctl, struct SHEET *sht, int bx0, int by0, int bx1, int by1)
{
if (sht->height >= 0) {
sheet_refreshsub(ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1);
}
return;
}
10.4、提高叠加处理速度(2)
现在刷新已经很快,如果没在刷新范围内,少了很多往显存写入的过程。但是,即使不在刷新范围内,也要进行很多次判断。要是能够直接精确到要刷新的位置就好了,就能更快的刷新。
oid sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1)
{
int h, bx, by, vx, vy, bx0, by0, bx1, by1;
unsigned char *buf, c, *vram = ctl->vram;
struct SHEET *sht;
for (h = 0; h <= ctl->top; h++) {
sht = ctl->sheets[h];
buf = sht->buf;
bx0 = vx0 - sht->vx0;
by0 = vy0 - sht->vy0;
bx1 = vx1 - sht->vx0;
by1 = vy1 - sht->vy0;
if (bx0 < 0) { bx0 = 0; }// 情况一
if (by0 < 0) { by0 = 0; }
if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }// 情况二
if (by1 > sht->bysize) { by1 = sht->bysize; }
for (by = by0; by < by1; by++) {
vy = sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++) {
vx = sht->vx0 + bx;
c = buf[by * sht->bxsize + bx];
if (c != sht->col_inv) {
vram[vy * ctl->xsize + vx] = c;
}
}
}
}
return;
}
通过这样改造之后就能够精确刷新范围,免除了多余的 if 判断。
拿书上的例子:
当背景文字发生改变,需要刷新,
刷新文字后,鼠标与文字部分重合,
文字刷新之后,鼠标的左上角就缺失了,下次刷新鼠标图层的时候,只需要刷新鼠标的左上角就好了,现在代码就能够精确到这个重叠的位置,然后进行刷新。上图也是情况一。
情况二:
图解: