处理器:Intel Celeron® Dual-Core CPU
操作系统:Windows7 专业版 x86
阅读书籍:《30天自制操作系统》—川合秀实[2015.04.10]
工具:../toolset/
当高速刷新图层时,屏幕上高速交替地显示各图层的内容,屏幕刷新处会出现闪烁情况。
图层缓冲区内保存的是图层要在屏幕上显示的图画。
/* 保存显示一个窗口图画的信息 * buf是用来保存窗口图画信息的缓冲区 * xsize, ysize为窗口图画在x,y方向的像素值 * title为窗口图画的标题 */ void make_window8(unsigned char *buf, int xsize, int ysize, char *title) { //窗口关闭按钮的编码 static char closebtn[14][16] = { "OOOOOOOOOOOOOOO@", "OQQQQQQQQQQQQQ$@", "OQQQQQQQQQQQQQ$@", "OQQQ@@QQQQ@@QQ$@", "OQQQQ@@QQ@@QQQ$@", "OQQQQQ@@@@QQQQ$@", "OQQQQQQ@@QQQQQ$@", "OQQQQQ@@@@QQQQ$@", "OQQQQ@@QQ@@QQQ$@", "OQQQ@@QQQQ@@QQ$@", "OQQQQQQQQQQQQQ$@", "OQQQQQQQQQQQQQ$@", "O$$$$$$$$$$$$$$@", "@@@@@@@@@@@@@@@@" }; int x, y; char c; //绘制窗口形状 boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, xsize - 1, 0); boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, xsize - 2, 1); boxfill8(buf, xsize, COL8_C6C6C6, 0, 0, 0, ysize - 1); boxfill8(buf, xsize, COL8_FFFFFF, 1, 1, 1, ysize - 2); boxfill8(buf, xsize, COL8_848484, xsize - 2, 1, xsize - 2, ysize - 2); boxfill8(buf, xsize, COL8_000000, xsize - 1, 0, xsize - 1, ysize - 1); boxfill8(buf, xsize, COL8_C6C6C6, 2, 2, xsize - 3, ysize - 3); boxfill8(buf, xsize, COL8_000084, 3, 3, xsize - 4, 20); boxfill8(buf, xsize, COL8_848484, 1, ysize - 2, xsize - 2, ysize - 2); boxfill8(buf, xsize, COL8_000000, 0, ysize - 1, xsize - 1, ysize - 1); putfonts8_asc(buf, xsize, 24, 4, COL8_FFFFFF, title); //窗口标题 //按照窗口关闭按钮编码储存关闭按钮图画 for (y = 0; y < 14; y++) { for (x = 0; x < 16; x++) { c = closebtn[y][x]; if (c == '@') { c = COL8_000000; } else if (c == '$') { c = COL8_848484; } else if (c == 'Q') { c = COL8_C6C6C6; } else { c = COL8_FFFFFF; } buf[(5 + y) * xsize + (xsize - 21 + x)] = c; } } return; }
窗口关闭按钮的制作“鼠标图标”、“字符”制作一样;窗口的其它区域就是绘制不同大小的矩形。此函数主要将“窗口”这个图画的信息保存到了buf缓冲区中。
将make_window8()暂定义在bootpack.c与主函数HarMain()一个文件中。
#include "bootpack.h" #include <stdio.h> void make_window8(unsigned char *buf, int xsize, int ysize, char *title); void HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; char str[40], mousebuf[128]; int mx, my, i; unsigned int memtotal, count = 0; struct MOUSE_DEC mdec; struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR; struct SHTCTL *shtctl; struct SHEET *sht_back, *sht_mouse, *sht_win; unsigned char *buf_back, buf_mouse[256], *buf_win; init_gdtidt(); init_pic(); io_sti(); //初始化GDT和IDT后解除中断屏蔽 fifo8_init(&mousefifo, 128, mousebuf); io_out8(PIC0_IMR, 0xf9); //检测主PIC的IRQ2(从PIC) io_out8(PIC1_IMR, 0xef); //检测从PIC的IRQ12(鼠标中断) init_keyboard(); enable_mouse(&mdec); memtotal = memtest(0x00400000, 0xbfffffff); memman_init(memman); memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */ memman_free(memman, 0x00400000, memtotal - 0x00400000); init_palette(); shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny); //初始化控制图层的结构体 sht_back = sheet_alloc(shtctl); //为桌面背景图像获取一个图层 sht_mouse = sheet_alloc(shtctl); //为鼠标图像获取一个图层 sht_win = sheet_alloc(shtctl); //---为窗口获取一个图层 buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny); buf_win = (unsigned char *) memman_alloc_4k(memman, 160 * 52); //----窗口的图画缓冲区 sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); //为屏幕图层指定桌面背景的图像信息 sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99); //为鼠标图层指定鼠标的图像信息 sheet_setbuf(sht_win, buf_win, 160, 52, -1); //-----为窗口图层sht_win指定缓冲区buf_win,无背景色 init_screen(buf_back, binfo->scrnx, binfo->scrny); init_mouse_cursor8(buf_mouse, 99); make_window8(buf_win, 160, 52, "high_refresh"); //----将窗口图画信息保存到buf_win中 sheet_slide(sht_back, 0, 0); //桌面背景显示移到(0,0)处 mx = (binfo->scrnx - 16) / 2; //屏幕中间坐标 my = (binfo->scrny - 28 - 16) / 2; sheet_slide(sht_mouse, mx, my); //鼠标显示移到屏幕中间 sheet_slide(sht_win, 80, 72); //----将窗口显示在始于屏幕(80, 72)处 sheet_updown(sht_back, 0); //桌面背景图层层数为0 sheet_updown(sht_win, 1); //窗口图层层数为1 sheet_updown(sht_mouse, 2); //鼠标图层层数为2 sprintf(str, "(%3d, %3d)", mx, my); putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, str); sprintf(str, "memory %dMB free : %dKB", memtotal / (1024 * 1024), memman_total(memman) / 1024); putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, str); sheet_refresh(sht_back, 0, 0, binfo->scrnx, 48); //保证文字显示在鼠标图标之下 for (;;) { count++; sprintf(str, "%010d", count); boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43); putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, str); sheet_refresh(sht_win, 40, 28, 120, 44); //----高速刷新屏幕的(40,28)到(120,44)区域 io_cli(); if (fifo8_status(&mousefifo) == 0) { io_sti(); //---CPU无需休眠,高速刷新图层 } else { if (fifo8_status(&mousefifo) != 0) { i = fifo8_get(&mousefifo); io_sti(); if (mouse_decode(&mdec, i) != 0) { //显示鼠标数据 sprintf(str, "[lcr %4d %4d]", mdec.x, mdec.y); if ((mdec.btn & 0x01) != 0) { str[1] = 'L'; } if ((mdec.btn & 0x02) != 0) { str[3] = 'R'; } if ((mdec.btn & 0x04) != 0) { str[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, str); sheet_refresh(sht_back, 32, 16, 32 + 15 * 8, 32); mx += mdec.x; my += mdec.y; if (mx < 0) { mx = 0; } if (my < 0) { my = 0; } if (mx > binfo->scrnx - 1) { mx = binfo->scrnx - 1; } if (my > binfo->scrny - 1) { my = binfo->scrny - 1; } sprintf(str, "(%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, str); sheet_refresh(sht_back, 0, 0, 80, 16); sheet_slide(sht_mouse, mx, my); } } } } }
打开”!cons_nt.bat”,整个程序通过编译后,使用“makerun”命令。在QEMU中的运行效果为:
Figure1. 高速刷新图层的闪烁
程序运行后,屏幕(40,28)--( 120, 44)区域以背景图层内容和窗口图层内容交替显示。这是由于高速刷新图层造成的屏幕闪烁效果。
屏幕闪烁的原因在于有多个图层在交替显示。解决屏幕闪烁的方式是只刷新内容发生变化的图层的可见部分,这样也能够使图层叠加程序变得更快。
修改sheet_refreshsub()函数:
* 刷新图层内容发生变化及其以上图层 * ctl为控制图层的结构体 * vx0 - vy1为屏幕图画显示发生变化的区域 */ void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0) { int h, bx, by, vx, vy, bx0, by0, bx1, by1; unsigned char *buf, c, *vram = ctl->vram; struct SHEET *sht; //超出屏幕坐标外的区域 if (vx0 < 0) { vx0 = 0; } if (vy0 < 0) { vy0 = 0; } if (vx1 > ctl->xsize) { vx1 = ctl->xsize; } if (vy1 > ctl->ysize) { vy1 = ctl->ysize; } for (h = h0; h <= ctl->top; h++) { sht = ctl->sheets[h]; buf = sht->buf; //得到屏幕图画变化的区域bx0-by1 bx0 = vx0 - sht->vx0; by0 = vy0 - sht->vy0; bx1 = vx1 - sht->vx0; by1 = vy1 - sht->vy0; //处理特殊情况(10.3) 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; }
修改后的sheet_refreshsub()函数增加了h0参数,被刷新的图层为h0~ top,且图层内被刷新的区域属于屏幕内容发生变化的区域。修改所有调用sheet_refreshsub()函数的函数。在QEMU中运行程序:
Figure2. 快速显示数字不再闪烁
屏幕出现闪烁的本质在于存在两层以上的图层内容在交替显示。如果将鼠标放在数字的区域时,鼠标所在区域也会略有闪烁。只有消除同一区域无两层及以上图层内容交替显示才能消除屏幕闪烁现象。
对于最上层图层来说,在此之下的图层内容都可以不显示。所以,鼠标所在区域的下层图层内容都不必显示,若鼠标无移动,这样不仅可以消除闪烁,鼠标所在图层也不必更新。
用一个与屏幕对应显存相同大小的缓冲区来保存屏幕各区域显示的是哪一个图层的内容。用“书”中的图来图示这一方法:
Figure3. 缓冲区标识屏幕各区域属于哪一个图层的显示
有了这样的缓冲区后,若某个图层某区域的内容发生了变化,程序检查这个区域的显示是否可见,可见则刷新,不可见则不刷新。
//控制图层的结构体 struct SHTCTL { unsigned char *vram, *map; //显存首地址,描述屏幕各区域显示图层缓冲区的首地址 int xsize, ysize, top; //屏幕X,Y方向像素,处于显示状态的图层数 struct SHEET *sheets[MAX_SHEETS]; //用于保存图层结构体的地址 struct SHEET sheets0[MAX_SHEETS]; //描述图层的结构体 }; /* 初始化控制图层的结构体,包括为控制图层、记录屏幕显示图层的结构体分配内存及付初值 * memman为管理空闲内存的结构体, * vram为屏幕对应显存的首地址 * xsize, ysize为屏幕X,Y方向的像素值 * 返回控制图层结构体的首地址(若为0,则为此结构体分配内存失败) */ struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize) { struct SHTCTL *ctl; int i; //管理内存中的函数memman_alloc_4k() ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL)); if (ctl == 0) { goto err; } //为记录屏幕各区域所显示图层画面的缓冲区 ctl->map = (unsigned char *) memman_alloc_4k(memman, xsize * ysize); if (ctl->map == 0) { memman_free_4k(memman, (int) ctl, sizeof (struct SHTCTL)); goto err; } ctl->vram = vram; ctl->xsize = xsize; ctl->ysize = ysize; ctl->top = -1; //无图层显示 for (i = 0; i < MAX_SHEETS; i++) { ctl->sheets0[i].flags = 0; //图层i未被使用 ctl->sheets0[i].ctl = ctl; //图层的控制结构体地址为ctl } err: return ctl; }
在控制图层结构体内增添用于记录屏幕各区域显示的图层指针map,在初始化控制图层结构体函数中将记录屏幕各区域所显示图层的缓冲区的首地址赋予map。
/* 刷新记录屏幕各区域所显示的内容属于哪一个图层 * ctl为控制图层结构体 * vx0-vy1为屏幕内容显示发生改变的区域 * [h0, top]图层在屏幕上的显示情况(vx0-vy1)将在map缓冲区中更新 */ void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0) { int h, bx, by, vx, vy, bx0, by0, bx1, by1; unsigned char *buf, sid, *map = ctl->map; struct SHEET *sht; if (vx0 < 0) { vx0 = 0; } if (vy0 < 0) { vy0 = 0; } if (vx1 > ctl->xsize) { vx1 = ctl->xsize; } if (vy1 > ctl->ysize) { vy1 = ctl->ysize; } for (h = h0; h <= ctl->top; h++) { sht = ctl->sheets[h]; sid = sht - ctl->sheets0; //用本图层在所有图层在内存中的偏移地址作为本图层的标记量 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; if (buf[by * sht->bxsize + bx] != sht->col_inv) { map[vy * ctl->xsize + vx] = sid; //标记屏幕此区域显示的是sid图层的内容 } } } } return; }
/* 根据map缓冲区刷新屏幕指定区域的各图层 * ctl为控制图层的结构体 * vx0 - vy1为屏幕图画显示发生变化的区域 */ 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, *vram = ctl->vram, *map = ctl->map, sid; struct SHEET *sht; //超出屏幕坐标外的区域 if (vx0 < 0) { vx0 = 0; } if (vy0 < 0) { vy0 = 0; } if (vx1 > ctl->xsize) { vx1 = ctl->xsize; } if (vy1 > ctl->ysize) { vy1 = ctl->ysize; } for (h = 0; h <= ctl->top; h++) { sht = ctl->sheets[h]; buf = sht->buf; sid = sht - ctl->sheets0; //得到屏幕图画变化的区域bx0-by1 bx0 = vx0 - sht->vx0; by0 = vy0 - sht->vy0; bx1 = vx1 - sht->vx0; by1 = vy1 - sht->vy0; //处理特殊情况(10.3) 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; if (map[vy * ctl->xsize + vx] == sid) { //能够显示这一部分内容,则刷新显示 vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx]; } } } } return; }
/* 若sht图层发生改变的区域在屏幕上可见,刷新sht图层 * sht为描述图层的结构体 * bx0 - by1为需要刷新的区域 */ void sheet_refresh(struct SHEET *sht, int bx0, int by0, int bx1, int by1) { //只需要检查sht图层内容发生改变在屏幕上是否可见,可见则刷新显示 if (sht->height >= 0) { sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1, sht->height, sht->height); } return; }
/* 有图层移动时,刷新各图层内容在屏幕上可见记录,再根据记录刷新在屏幕上的显示 * sht为描述图层的结构体 * vx0,vy0为图层内图像显示的新的起始坐标 */ void sheet_slide(struct SHEET *sht, int vx0, int vy0) { struct SHTCTL *ctl = sht->ctl; int old_vx0 = sht->vx0, old_vy0 = sht->vy0; sht->vx0 = vx0; sht->vy0 = vy0; if (sht->height >= 0) { //刷新0~top图层在屏幕上可见的记录 //刷新本图层到top层在屏幕上可见的记录 //刷新显示0 ~ sht->height - 1图层内容(vx0, vy0) //刷新本图层内容(vx0, vy0) sheet_refreshmap(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0); sheet_refreshmap(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height); sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0, sht->height - 1); sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height, sht->height); } return; }
/* 重新设置当前图层的层数,重新记录相应图层在屏幕上可见的记录,重新刷新相应图层的显示 * sht为描述图层的结构体 * height为sht描述图层的新的层数值 */ void sheet_updown(struct SHEET *sht, int height) { struct SHTCTL *ctl = sht->ctl; int h, old = sht->height; //层数最多在最上层图像的上一层 if (height > ctl->top + 1) { height = ctl->top + 1; } //sht最少值为不显示值 if (height < -1) { height = -1; } sht->height = height; //sht图层层数往下降 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; //记录sht图层地址的为sheets[height] //刷新height + 1 ~ top图层在屏幕上可见的记录 //刷新height + 1 ~ old图层内容 sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height + 1); sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height + 1, old); } else {//sht图层被隐藏 if (ctl->top > old) { //(height,old]之间的图层往下调 for (h = old; h < ctl->top; h++) { ctl->sheets[h] = ctl->sheets[h + 1]; ctl->sheets[h]->height = h; } } ctl->top--;//最上层层数减少1层 sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, 0); sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, 0, old - 1); } } else if (old < height) { //sht图层数比原来的图层数大 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 { //sht从不显示到显示 //[height,top]图层往上调 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_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height); sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height, height); } return; }
这些函数围绕的一个中心是:屏幕当前显示的是哪些图层的哪些内容,只刷新发生改变的且在屏幕上可见的这些图层中的内容(同一时间只刷新一个图层的显示)。
HariMain()函数保持不变。这些函数都在sheet.c中改写,在”!cons_nt.bat”中编译调试整个工程。用“make run”命令在QEMU中运行:
Figure4. 只刷新屏幕可见部分的图层内容
----superposition_handleII
窗口移动代码在“书”的p.549– p.559。包括减少for语句中的if语句、for循环内的写4次字节内存单元用写1次整型(此类型占4字节内存)代替、用缓冲区保存窗口移动坐标的数据。
[x86OS] Note Over.
[2015.04.17]