![22cb44cf7058a6f91e0982beec62c8a0.png](https://i-blog.csdnimg.cn/blog_migrate/60953d06d6039af877d0d7d5c0327587.jpeg)
一、图层技术问题
虽然我们已经成功实现了Windows窗口的叠加显示,但是这个显示机制还会存在如下问题:
1.每次刷新屏幕都需要全部显示所有的图层的卡顿问题:因为目前我们的刷新原理是一旦窗口有消息触发(比如挪动鼠标或窗口),我们会遍历图层管理器的数组ctr->sheets[],保证height值是小到大的来显示所有图层,一直显示完top个图层。如果,我们的top值较大,有10个乃至100个窗口,这样一来计算机动不动就会刷新全部图层,由于这个数据量巨大,显然这个显示会出问题,表现出的现象是屏幕会自上到下的进行“刷新”或出现屏幕卡顿。
2.屏幕同一个像素点同时存在不同图层显示的闪烁问题:现在假设屏幕上有一个16*16的像素点矩阵已经被鼠标图形占据,而我们现在有另外一个窗口在高频显示动态的变化值(如计数器),而这个值的显示位置刚好和鼠标像素位置重合,这样的效果就会出问题:动态变化的时候,窗口和鼠标重叠的像素点会一直闪烁不停。这是因为,鼠标和窗口都在争夺该像素的显示权,所以会导致闪烁不停!
二、解决方法
作为视窗操作系统,以上两个问题是必须要解决的。解决方法是:
1.刷新部分图层。我们需要优化现有逻辑,将各种情况进行枚举,选择性的刷新部分图层:
1.1 窗口图层无变化:窗口仅内容有变化,而位置无变化。这种情况下,该窗口以下的图层就没有必要刷新了,只需要刷新该窗口本身以及它上面的图层(因为窗口刷新可能给会覆盖掉上层部分,包括最上层的鼠标)。具体在程序中为sheet_refresh函数。
1.2 窗口图层无变化:窗口位置有变化,也即比如移动(slide)。这种情况下,需要刷新它下面所有的图层,因为移动之后老位置会漏出所有的下层。另外,还需要刷新它到在位置的图层。具体在程序中为sheet_slide函数。
1.3 窗口图层有变化:up或down。同样的方法,也只需要刷新部分图层,不再累述。在程序中为sheet_updown函数。
可以看出,这种方法的最终目的是:尽最大限度的减少窗口刷新数据。
2.map图形技术。
第2个问题其实是窗口之间的重叠冲突问题,那么我们必须要用下面这种方法来避免:无论有多少个图层,我们只需规定屏幕上的某一个像素点只归属于1个图层,每层窗口的视频数据在显示的时候做一个判断:只有属于本层的数据才显示出了,不属于本层的数据不显示。
![3c3a362a15577504711b597f72f6a4d8.png](https://i-blog.csdnimg.cn/blog_migrate/3f9981210fcbf0869ed4889a30a6ddd5.jpeg)
具体实现过程:
1.专门分配一个和VRAM一样大小的map内存区,这个内存区的每个象素位置用来填写该象素点属于哪个图层(SID)。
2.在图层管理器数据结构中增加一个指针节点:*map,用来指向上面的map区。
struct SHTCTL {
unsigned char *vram, *map;
int xsize, ysize, top;
struct SHEET *sheets[MAX_SHEETS];
struct SHEET sheets0[MAX_SHEETS];
};
3.将各图层sheet的图层编号值(height) ,写进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;
}
}
}
}
return;
}
该程序遍历所有的图层sheet,把所有图层的每个视频象素点都用sid进行编号填写。关键语句:sid = sht - ctl->sheets0,含义:结构体名称 sht和数组名称ctl->sheets0代表的都是内存地址,sid 就是两个内存地址的差值,也即是这两个地址之间间隔多少个变量元素(sht是放在数组sheets0里面的,因此它们是同一种数据类型)。这里由于是从数组开始位置计算的,因此sid其实就是每个图层sheet的数组下标值,对于正在显示的图层窗口来说,这个差值其实就是height值。
![cecf8986d69406c09915b8039be1b9f5.png](https://i-blog.csdnimg.cn/blog_migrate/06ddffff9afc302c4e23d0c15acfc839.jpeg)
这个过程有点麻烦,稍做解释就简单:刚开始是0号图层是桌面,那么它的窗口大小是xsize*ysize,那么执行以上函数之后,map区所有的数据都是一个sid=0。随后,我们增加了一个1号图层窗口WIN1,假设它的窗口大小是100*100,位置是(0,0),那么执行以上函数之后,map区的数据在map[0]---map[100*100]的SID就变成了1。同样,在增加2号窗口,那么map区相应位置的数据就会变成2......始终,底层的一些数据SID值其实会被上层窗口的SID值覆盖掉。这样,就能保证屏幕每个象素点的数据只能归属于某一个图层。不光新增窗口,我们在挪动窗口等操作时,任然需要实时更新map区的数据。
4.显示窗口的时候,关联map区数据来控制不重叠。它最终重要的功能就是显示每层窗口的时候,必须先判断象素点是不是属于当前图层,也即通过map区的SID值来判断,如果不是则不予显示,这样的话,所有的图层就永远不会有交叉争用的时候。函数是:sheet_refreshsub。map区数据控制显示技术,原理就是最终呈现出来的一整幅屏幕图像,实际是由属于不同图层的所有像素“东拼西揍”组合而成!。
![4e5c59084754be48b3cf12fa58c03c10.png](https://i-blog.csdnimg.cn/blog_migrate/8342158eb9313cfa0e8f9e356112b943.jpeg)
所以,现在每个象素点便会有两个显示数据:视频数据和图层数据。这个机制反倒又有点像字符模式下的显示控制了。
三、显示效果
最后,我们通过制作一个高速更新的计数器窗口来验证,如果鼠标和窗口重叠在一起的地方,屏幕没有发生闪烁的情况,则说明是成功的。
![466fee82aa26b90fddf7ac9838a2749c.png](https://i-blog.csdnimg.cn/blog_migrate/3cac02d35c38efa42fb24a35fc6a9afd.png)