第10天 叠加处理
2020.4.9
1. 内存管理(续)(harib07a)
-
先整理一下源文件:把用于内存管理的函数、数据结构和常量汇总到memory.c中去。
-
memman_alloc和memman_free是以1字节为单位进行内存管理。 这样在反复进行内存分配和释放之后就会产生很多不连续的小段未使用,从而导师man->frees达到饱和。
-
因此,编写以0x1000字节(4KB)为单位进行内存分配和释放的函数memman_alloc_4k和memman_free_4k.
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; }
- memman_alloc_4k返回值是地址或者0(失败)。
memman_free_4k返回值是0(成功)或-1(失败)。 - 这两个函数都会把指定的内存大小size按照0x1000字节为单位向上舍入(round up)。
-
向下舍入:
- 比如,123以10为单位向下舍入的结果是120。其实就是***抹零操作***。
- 在2进制下,对某一个数字进行抹零操作,只需要使用与运算& 即可。比如,101111 & 111000 = 101000。就完成了将101111的低3位置0的操作。
- 16进制的抹零操作和2进制差不多。比如,0x12345678 & 0xfffff000 = 0x12345000。
- 把变量i总的数字以0x1000为单位进行向下取舍:
i = i & 0xfffff00;
-
向上舍入:
- 比如,123以10为单位向上舍入的结果是130。我们似乎可以发现,向上舍入和向下舍入有某种联系。
- 具体做法是:先判断最后几位,如果是0则什么也不做,如果不是0,我们再向上取整。。
if ((i & 0xfff) != 0){ i = (i & 0xfffff000) + 0x1000; }
- 更好的代码是:
i = (i + 0xfff) & 0xfffff000;
-
- memman_alloc_4k返回值是地址或者0(失败)。
-
拓展阅读:
2. 叠加处理(harib07b)
-
图层
- 在画面上进行叠加处理,类似于将绘制了图案的透明图层叠加在了一起。
- 最上面的图层是用来描绘鼠标的;最下面的图层是用来描绘桌面壁纸的。
- 需要通过移动图层来实现鼠标指针的移动以及窗口的移动。
-
图层结构体SHEET(bootpack.h中声明):
struct SHEET { unsigned char *buf; int bxsize, bysize, vx0, vy0, col_inv, height, flags; };
- sheet代表透明图层的意思。
- buf是用来记录图层上所描画内容的开始地址。
- 图层的整体大小用bxsize*bysize来表示。b代表buf。
- vx0和vy0表示图层左上角在屏幕上的位置。v代表vram。
- col_inv表示的是透明色号。col是color,inv是invisible。
- height是代表该图层的高度。-1代表不显示该图层。
- flags代表该图层是否被使用。
- 这个结构体占4+7*4=32字节。
-
管理多层图层信息的结构体SHTCTL:
#define MAX_SHEETS 256 struct SHTCTL { unsigned char *vram; int xsize, ysize, top; struct SHEET *sheets[MAX_SHEETS]; struct SHEET sheets0[MAX_SHEETS]; };
- SHTCTL,是sheet control的缩写。
- MAX_SHEETS代表能够管理的最大图层数。256应该够了。
- vram代表VRAM的地址。
- xsize,ysize代表屏幕分辨率的大小。
- 保存vram、xsize和ysize是为了不想每次获取这些信息都从BOOTINFO查询。
- top代表最上面图层的高度。
- sheets0这个结构体数组用于存放256个图层的信息。
- sheets这个结构体指正数组用于记录图层顺序。sheets0中的图层顺序混乱,而更改sheets中指针的指向,便可以实现将图层按照高度排序。
- SHTCTL占用4 + 3 * 4 + 4 * 256 + 32 * 256 = 9232字节,超过了9KB。因此我们需要使用memman_alloc来给SHTCTL来分配内存空间。
-
初始化SHTCTL的函数shtctl_init(sheet.c中):
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)); /*以4KB为单位分配空间,因此ctl占空间12KB。*/ 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; }
- 使用memman_alloc_4k来分配用于记录图层控制变量ctl的内存空间。ctl占12KB。
- sizeof是C语言的操作符,不是函数。sizeof(变量/变量类型),C语言编译器就会计算出该括号内变量/变量类型所需要的字节数。
-
取得新生成的未使用的图层,函数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; /* 暂时隐藏(不显示)该图层 */ return sht; } } return 0; /* 0代表无法取得图层,即所有的图层都被占用 */ }
- 高度设为-1代表:图层的高度还未设置,而不是不显示该图层。
&ctl->sheets0[i]
等价于&(ctl->sheets0[i])
。
-
设定图层的缓冲区大小和透明色的函数sheet_setbuf():
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; }
- 这个函数设置图层的缓冲区大小和透明色。
-
设定图层高度并维护sheets[]的函数sheet_updown:
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) { /* 把介于height和old之间的图层往上提 */ for (h = old; h > height; h--) { ctl->sheets[h] = ctl->sheets[h - 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { /* 隐藏 height=-1*/ if (ctl->top > old) { /*top>=old*/ /* 把top和old之间的图层往下降 */ for (h = old; h < ctl->top; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } } ctl->top--; /* 显示的图层少了一个,所以top-=1。*/ } sheet_refresh(ctl); /* 按照新图层信息重新绘制画面 */ } else if (old < height) { /* 比以前高 */ if (old >= 0) { /* 把介于old和height之间的图层往下降 */ for (h = old; h < height; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } ctl->sheets[height] = sht; } else { /* 由隐藏状态转为显示状态 */ /* 把介于top和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; ctl->top++; /* 多显示了一个图层,所以top需要加1 */ } sheet_refresh(ctl); /* 按照新图层信息重新绘制画面 */ } return; }
- 手动维护sheets[]数组的有序状态。
-
按照新图层信息重新绘制画面的函数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++) { 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; }
- 对于每一个图层,按照从下往上的顺序,依次将图层上所有的像素复制到VRAM相应的区域。由于是由下往上复制,所以就实现了叠加效果。
-
上下左右移动图层的函数sheet_slide:
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; }
-
释放已使用图层的内存的函数sheet_free:
void sheet_free(struct SHTCTL *ctl, struct SHEET *sht) { if (sht->height >= 0) { sheet_updown(ctl, sht, -1); /* 如果处于显示状态,那么先设置为隐藏 */ } sht->flags = 0; /* 设置该图层可用 */ return; }
-
改造bootpack.c中的HariMain函数:
void HariMain(void) { …… struct SHTCTL *shtctl; struct SHEET *sht_back, *sht_mouse; unsigned char *buf_back, buf_mouse[256];/*鼠标图层的内存已经分配*/ …… init_palette(); shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny); /*初始化shtctl,占用内存空间*/ sht_back = sheet_alloc(shtctl); /*背景占用图层*/ sht_mouse = sheet_alloc(shtctl); /*鼠标占用图层*/ buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny); /*为背景分配内存空间*/ sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); /* 没有透明色 */ sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99); /*透明色号99*/ init_screen8(buf_back, binfo->scrnx, binfo->scrny); init_mouse_cursor8(buf_mouse, 99); sheet_slide(shtctl, sht_back, 0, 0); mx = (binfo->scrnx - 16) / 2; /* 按显示在画面中央来计算坐标 */ my = (binfo->scrny - 28 - 16) / 2; sheet_slide(shtctl, sht_mouse, mx, my); sheet_updown(shtctl, sht_back, 0); sheet_updown(shtctl, sht_mouse, 1); sprintf(s, "(%3d, %3d)", mx, my); putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s); sprintf(s, "memory %dMB free : %dKB", memtotal / (1024 * 1024), memman_total(memman) / 1024); putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s); sheet_refresh(shtctl); for (;;) { io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { io_stihlt(); } else { if (fifo8_status(&keyfifo) != 0) { i = fifo8_get(&keyfifo); io_sti(); sprintf(s, "%02X", i); boxfill8(buf_back, binfo->scrnx, COL8_008484, 0, 16, 15, 31); putfonts8_asc(buf_back, binfo->scrnx, 0, 16, COL8_FFFFFF, s); sheet_refresh(shtctl); } else if (fifo8_status(&mousefifo) != 0) { i = fifo8_get(&mousefifo); io_sti(); if (mouse_decode(&mdec, i) != 0) { /* 已经得到3字节的数据 */ sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y); if ((mdec.btn & 0x01) != 0) { s[1] = 'L'; } if ((mdec.btn & 0x02) != 0) { s[3] = 'R'; } if ((mdec.btn & 0x04) != 0) { s[2] = 'C'; } boxfill8(buf_back, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31); putfonts8_asc(buf_back, binfo->scrnx, 32, 16, COL8_FFFFFF, s); /* 移动光标 */ mx += mdec.x; my += mdec.y; if (mx < 0) { mx = 0; } if (my < 0) { my = 0; } if (mx > binfo->scrnx - 16) { mx = binfo->scrnx - 16; } if (my > binfo->scrny - 16) { my = binfo->scrny - 16; } sprintf(s, "(%3d, %3d)", mx, my); boxfill8(buf_back, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 消坐标 */ putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 写坐标 */ sheet_slide(shtctl, sht_mouse, mx, my); /* 不再是调用putblock8_8来重新绘制鼠标 */ } } } } }
- 两个图层,分别是sht_back和sht_mouse。
- 两个缓冲区,buf_back和buf_mouse。
- 以前指定为binfo->vram的部分,大都改成了buf_back。因为不再直接在VRAM上修改背景了,需要使用我们上述说明的函数。
shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
shtctl占12KB。buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
buf_back占64KB。(320*200字节以4KB向上取整。)
-
在QEMU上运行,free应该显示29228KB(29304-12-64 KB).
-
在VMware上运行,free显示258604KB(258680-12-64 KB)。
-
出现了问题:鼠标移动有延迟!!画面比较卡。
3. 提高叠加处理速度(1)(harib07c)
-
先从图层的移动方面来提升速度。
-
鼠标指针有16*16=256个像素。当我们稍微移动一下鼠标。根据harib07b的实现方法,程序就会对整个画面进行刷新,重新绘制320 *200=64000个像素(实际上,是先绘制背景图层的64000个像素,在绘制鼠标图层的256个像素)。
-
其实,只需要重新绘制与移动有关的部分,也就是移动前的部分和移动后的部分。
-
编写新函数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_refreshsub几乎与harib07b的sheet_refresh函数一样。
- 此函数添加了4个参数:vx0,vy0,vx1和vy1。是屏幕的坐标位置。
- 这个函数的作用是:只在屏幕矩形范围【(vx0,vy0)~(vx1,vy1)】内刷新像素点。 原先是图层有多大,屏幕就刷新多大的范围;现在是根据指定的矩形范围刷新。
-
使用sheet_refreshsub函数提高sheet_slide的速度:
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; }
- 可以想象鼠标图层在背景图层上移动。
- 如果是harib07b,鼠标动一下,需要更改320* 200+16* 16个像素点。现在,只需要更改,16*16 + 16*16*2个像素点。
-
我们再来解决一下图层内文字显示的问题:
- 我们在图层上显示文字,实际上并不是改写图层的全部内容。而是只需要修改文字对应的像素范围即可。一个字符占用的像素区域是8*16。
- 假设我们现在已经写了20个字符,那么8*16*20=2560个像素,我们只需要修改这2560个像素即可,而不需要修改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; }
- 这里的参数bx0,by0,bx1,by1是图层内的坐标范围。
- 这里的参数bx0,by0,bx1,by1是图层内的坐标范围。
-
相应地,需要修改sheet_updown:
- 就是将harib07b中的sheet_refresh改成sheet_refreshsub。
- 图层上下移动,所以只调用一次sheet_refreshsub即可。注意,需要修改sheet_updown中的2处。
-
修改HariMain:
void HariMain(void) { …… sprintf(s, "(%3d, %3d)", mx, my); putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s); sprintf(s, "memory %dMB free : %dKB", memtotal / (1024 * 1024), memman_total(memman) / 1024); putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s); sheet_refresh(shtctl, sht_back, 0, 0, binfo->scrnx, 48); /*修改1*/ for (;;) { io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) { io_stihlt(); } else { if (fifo8_status(&keyfifo) != 0) { …… sheet_refresh(shtctl, sht_back, 0, 16, 16, 32); /*修改2*/ } else if (fifo8_status(&mousefifo) != 0) { i = fifo8_get(&mousefifo); io_sti(); if (mouse_decode(&mdec, i) != 0) { …… boxfill8(buf_back, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31); putfonts8_asc(buf_back, binfo->scrnx, 32, 16, COL8_FFFFFF, s); sheet_refresh(shtctl, sht_back, 32, 16, 32 + 15 * 8, 32); /* 修改3 */ …… sprintf(s, "(%3d, %3d)", mx, my); boxfill8(buf_back, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 消去坐标 */ putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 写出坐标 */ sheet_refresh(shtctl, sht_back, 0, 0, 80, 16); /*修改4*/ sheet_slide(shtctl, sht_mouse, mx, my); } } } } }
- 修改1:修改图层sht_back内的文字显示
- 修改2:当键盘按键发生变化时,修改sht_back内的文字显示:
- 修改3:修改范围:
- 修改4:修改范围:
- 只要往buf_back中写入数据,就需要调用sheet_refresh。
- 修改1:修改图层sht_back内的文字显示
-
使用QEMU或VMware运行系统,鼠标速度的确比以前快了许多。
4. 提高叠加处理速度(2)(harib07d)
-
仔细想一下,其实,函数sheet_refreshsub写的不太理想。
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1) { …… for (h = 0; h <= ctl->top; h++) { …… for (by = 0; by < sht->bysize; by++) { …… for (bx = 0; bx < sht->bxsize; bx++) { …… if (vx0 <= vx && vx < vx1 && vy0 <= vy && vy < vy1) { …… } } } } return; }
- 我们每次执行sheet_refreshsub时,即使不写入像素内容,也需要多次执行if语句。
-
问题所在:即便只刷新图层的一部分,也需要对所有图层的全部像素执行if语句。 对于刷新范围之外的部分,只执行if,而不刷新,这就是一种“浪费”。
-
解决方式:把for循环的范围设置成刷新范围。
-
优化的sheet_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; /* (1) */ bx0 = vx0 - sht->vx0; by0 = vy0 - sht->vy0; bx1 = vx1 - sht->vx0; by1 = vy1 - sht->vy0; if (bx0 < 0) { bx0 = 0; } /*(2)*/ if (by0 < 0) { by0 = 0; } if (bx1 > sht->bxsize) { bx1 = sht->bxsize; } /*(3)*/ 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; }
-
解释(1):
- (vx0,vy0)~(vx1,vy1)表示的是刷新范围相对于屏幕的范围。
- (bx0,by0)~(bx1,by1)表示的是刷新范围相对于图层的范围。
- (sht->vx0,sht->vy0)表示的是图层相对于屏幕的左上角坐标。
- 因此就有如下关系式:
- vx0 = bx0 + sht->vx0.
- vy0 = by0 + sht->vy0.
- vx1 = bx1 + sht->vx1.
- vy1 = by1 + sht->vy1.
- 因此,遍历变量bx和by的范围就是:
bx0 <= bx < bx1; by0 <= by < by1;
-
解释(2)(3):
【红色框是屏幕的刷新范围(固定),黑色框是屏幕(固定),绿色框代表图层(变化),蓝色区域代表需要真正刷新的范围(相对于图层)。以下情况不全】
-
其实就是取绿色框和红色框的交集。
- 当vx0 > sht->vx0, bx0 = vx0 - sht->vx0;
- 当vx0 < sht->vx0, bx0 = 0;
- 当vy0 > sht->vy0, by0 = vy0 - sht->vy0;
- 当vy0 < sht->vy0, by0 = 0;
- 当vx1 > sht->vx0 + sht->xsize, bx1 = sht->xsize;
- 当vx1 < sht->vx0 + sht->xsize, bx1 = vx1 - sht->vx0;
- 当vy1 > sht->vy0 + sht->ysize, by1 = sht->ysize;
- 当vy1 < sht->vy0 + sht->ysize, by1 = vy1 - sht->vy0;
-
令tx0 = vx0 - sht->vx0:
- 当tx0 > 0, bx0 = tx0;
- 当tx0 < 0, bx0 = 0;
-
令ty0 = vy0 - sht->vy0:
- 同理tx0;
-
令tx1 = vx1 - sht->vx0:
- 当tx1 > sht->xsize, bx1 = sht->size;
- 当tx1 < sht->xsize, bx1 = tx1;
-
令ty1 = vy1 - sht->vy0:
- 同理tx1。
-
解释了这么多,不过是用了一个小的数学变换技巧。
-
当不相交的时候:
必然,计算出来的范围不合法,因此循环不会执行。这里不再分析。
-
-
使用QEMU或者VMware运行以后,鼠标移动相当丝滑。
5. 写在后面
- 写到这里是2020.4.10 18:25。
- 肚子好饿。今天下了一天的雨,听说济南还下雪了!
- 昨天跑步了,今天由于天气原因没有跑。
- 腰痛依旧,不过感觉好点了。
- 200页完成,28%完成了。继续加油!