第10天:叠加处理

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所占空间:
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 判断。

拿书上的例子:
当背景文字发生改变,需要刷新,
刷新文字后,鼠标与文字部分重合,
重叠
文字刷新之后,鼠标的左上角就缺失了,下次刷新鼠标图层的时候,只需要刷新鼠标的左上角就好了,现在代码就能够精确到这个重叠的位置,然后进行刷新。上图也是情况一。

情况二:

情况二

图解:
图解

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值