harib07a:
整理内存管理函数:memman_alloc和memman_free能够以最小1字节进行内存管理,但时间久了后,容易产生外部碎片;为此,笔者编写了一些以0x1000字节为单位进行内存分配和释放的函数,它们会把指定的内存大小按照0x1000字节为单位向上舍入(roundup),0x1000大小正好是4KB.
1、向下舍入:以0x1000为单位向下舍入
0x1234_5678 & 0xffff_f000 = 0x1234_5000;
i = i & 0xffff_f000 ;
2、向上舍入:以0x1000为单位向上舍入
i = (i + 0xfff) & 0xffff_f000
unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size) { unsigned int a; //以0x1000为单位向上舍入的方式进行内存分配 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; //以0x1000为单位向上舍入的方式进行内存释放 size = (size + 0xfff) & 0xfffff000; i = memman_free(man, addr, size); return i; }
harib07b:
图层的定义:下面我们来解决图层叠加的问题,编写一段程序,既适用于于鼠标的叠加,也适应于窗口的叠加!
#define MAX_SHEETS 256 //图层的整体大小:bxsize*byseze //vx0和vy0表示图层在画面上坐标的位置 //col_inv表示透明色色号 //height :表示图层的高度 //flags : 用于存放图层的各种设定信息 struct SHEET { //图层的结构体 unsigned char *buf; //记录图层上描绘内容的结构体 int bxsize, bysize, vx0, vy0, col_inv, height, flags; }; /* 图层管理的结构体;实现图层的叠加 */ struct SHTCTL { unsigned char *vram; //VRAM地址 int xsize, ysize, top; //VRAM画面的大小;top:最上面图层的高度 struct SHEET *sheets[MAX_SHEETS];//sheets0[]啊没找高度排序后的图层地址 struct SHEET sheets0[MAX_SHEETS];//用于存放256个图层的信息 };
图层的处理:界面图层处理的函数都被封装在sheet.c中,关于图层的具体实现方法和原理请对照图片和注释看
(注释已经很详细了,应该能看懂!代码有点长,我们折起来吧!)
//sheet.c #include "bootpack.h" #define SHEET_USE 1 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; /* 表示没有图层(0) */ for (i = 0; i < MAX_SHEETS; i++) { ctl->sheets0[i].flags = 0; /* 标记为未使用 */ } err: return ctl; //初始化失败 } 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; /* 隐藏,此时高度还没有设置 */ return sht; //取得(分配)成功 } } return 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; } //设定底板高度的函数: void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height) { int h, old = sht->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) { /* 把中间的往上提 */ for (h = old; h > height; h--) { //这里不断把当前高度低的图层,不断的向上赋值 ctl->sheets[h] = ctl->sheets[h - 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { /* 高度小于0时,表示图层没有初始化,隐藏 */ if (ctl->top > old) { /* 把上面的降下来 */ for (h = old; h < ctl->top; h++) {//这里不断把当前高度图层,不断地向下赋值 ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } } ctl->top--; /* 现实中的图层少了一个,最上面的图层高度下降 */ } sheet_refresh(ctl); /* 按照新图层的信息重新绘制画面 */ } else if (old < 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 { /* 由隐藏状态转为显示状态 */ /* 将已经在上面的提上来 */ 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; ctl->top++; /* 由于已显示的图层增加了1个,所以最上面的图层高度增加 */ } sheet_refresh(ctl); /* 按照新图层信息重新描绘画面 */ } return; } //图层刷新函数。 //刷新原理:对于已经设定的高度的所有图层,从下往上,将透明以外的所有像素都复制到VRAM中,由于是从下往上复制,所以最后最上面的内容就留在了画面上。 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++) { //从图层0 开始,向高图层一个一个走 sht = ctl->sheets[h]; //获得每一层的地址 buf = sht->buf; //将该地址的内容放到VRAM缓冲区 for (by = 0; by < sht->bysize; by++) { //VRAM按照从左到右,从上到下的顺序,不断的更新VRAM缓存 vy = sht->vy0 + by; for (bx = 0; bx < sht->bxsize; bx++) { 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); /* 如果该图层正在显示,设置height=-1;隐藏 */ } sht->flags = 0; /* FLAG=0表示该图层未使用 */ return; }
harib07c:
上一步我们实现了界面图层显示的叠加,但当我们MAKE RUN 发现实在是太慢了!接下来我们来提高图层叠加处理的速度!
1、提高刷新的速度
从void sheet_refresh(struct SHTCTL *ctl)中我们可以看到,只要鼠标动了,就会对整个界面进行刷新!然而我们只需让它刷新相关的部分。我们在函数sheet_refresh()添加一个条件函数IF,只刷新鼠标经过的部分。
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; }
2、使用fefreshsub提高sheet_slide的速度
//首先记住移动前的位置,在设定新的位置,最后只要重新描绘移动前和移动后的地方。 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);
3、图层内文字显示问题
当我们在图层上显示或者改写文字时,并不是改写图层的全部内容;但是我们却要每次重写64000个像素的内容,怎么办?我们重新来编写sheet_refresh()函数吧!把文字显示单独处理。
注 意:指定的范围不是直接指定画面内的左边,而是以缓冲区的坐标来表示的。
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; }
同时也要对sheet_updown进行修改,当然改动的只有sheet_refresh(ctl):
sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize);
harib07c:
make run一下,还是感觉有些别扭;我们看看还有那些可以改进的地方。我们看看上面的refreshsub()发现即使不写入像素,任然要对每一层的每一个像素进行判断。但是对于需要刷新以外的部分,这就做了很多无用功。因此我们做了一下改良(如下图)
void 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; /* 使用vx0--vy1,对bx0--by1进行倒推得到图层中需要刷新的范围 vx = sht->vx0 ; --> bx = vx - sht->vx0; */ bx0 = vx0 - sht->vx0; //倒推得到需要刷新的范围(bx0,by0)(bx1,by1) 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; //将该像素出缓冲区的内容给VRAM } } } } return; }