請參看視頻,已獲得更詳細的講解和展示
Linux kernel Hacker, 從零構(gòu)建自己的內(nèi)核
上一節(jié),我們消除了因刷新而導(dǎo)致的嚴重閃爍,但問題并沒有從根子上解決,因為當(dāng)我們把鼠標挪動不斷刷新自己的Message Box上面時,發(fā)現(xiàn)鼠標居然變得閃動起來,這是因為,當(dāng)窗體自身刷新時,它會把處于它上方的窗體也進行刷新,而這種操作其實是沒有必要的。我們看下面這種情況:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
所以數(shù)字1代表的是標號為1的窗口所對應(yīng)的像素點,數(shù)字2所代表的是編號為2的窗口所對應(yīng)的像素點,顯然,窗口1的右下邊部分被窗口2所覆蓋,并且窗口2比窗口1的高度還要高,當(dāng)窗口1的像素進行刷新時,如果窗口2的內(nèi)容沒有變化的話,那么窗口2 完全不需要進行刷新,這就要求窗口1只能更新那些沒有被窗口2覆蓋的點,也就是說,窗口1只能更新一下部分像素點:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
也就是右下角那部分,他是不能去更新的,這就要求,當(dāng)我們?nèi)ニ⑿嘛@存時,必須知道哪一個像素點屬于窗口1,哪一個像素點屬于窗口2,刷新時,屬于窗口1的像素點,我就更新,屬于窗口2的像素點,我就忽略,這樣才能解決上節(jié)看到的鼠標閃爍的問題。這樣我們就需要在圖層的數(shù)據(jù)結(jié)構(gòu)中引入新的變量,用來記錄該圖層像素點所對應(yīng)的窗體編號,代碼如下,win_sheet.h:
struct SHTCTL {
unsigned char *vram, *map;
int xsize, ysize, top;
struct SHEET *sheets[MAX_SHEETS];
struct SHEET sheets0[MAX_SHEETS];
};
上面的結(jié)構(gòu)體定義中,多增加了一個變量map, 它的作用就是用來記錄圖層每一個像素點所對應(yīng)的窗體編號的。在win_sheet.c中做如下更改:
struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram,
int xsize, int ysize) {
....
//為圖層像素編號數(shù)值分配內(nèi)存
ctl->map = (unsigned char*)memman_alloc_4k(memman, xsize * ysize);
if (ctl->map == 0) {
memman_free_4k(memman, (int)ctl, SIZE_OF_SHTCTL);
return 0;
}
.....
}
在初始化圖層信息時,為map變量分配內(nèi)存,以便用來記錄圖層像素點的窗口編號。如何確定每個窗體圖層所對應(yīng)的編號呢,很簡單,所有的圖層都是從圖層控制器SHTCTL 的圖層數(shù)組sheets0中分配的,我們把圖層對象在這個數(shù)值中的下標作為它的窗體編號,我們在win_sheet.c里面添加一個函數(shù)叫refresh_map,用來記錄當(dāng)前需要刷新的窗口的像素所對應(yīng)的編號。
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];
//獲取圖層編號,也就是圖層對象在圖層數(shù)組中的下標
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) {
//將圖層標號設(shè)置到map變量里
map[vy * ctl->xsize + vx] = sid;
}
}
}
}
return;
}
這個函數(shù)跟以前的刷新函數(shù)refresh_sub幾乎一模一樣,是用來設(shè)置像素對應(yīng)的圖層編號的,假如窗口1的某個像素位于坐標(20, 30),那么我就在map[30xsize + 20] 處設(shè)置為1, xsize 是桌面的寬度,如果窗口2移動后,有像素點也移動到了坐標(20,30), 那么上面的代碼就會把map[30xsize + 20] 處設(shè)置為2,也就是窗口1的像素被窗口2所覆蓋了。
有了像素所在位置對應(yīng)的窗口號后,我們就可以只刷新對應(yīng)窗口的像素點,于是我們對窗口刷新函數(shù)做下列修改:
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0, int h1) {
int h, bx, by, vx, vy;
unsigned char *buf, c, *vram = ctl->vram, *map = ctl->map, sid;
struct SHEET *sht;
if (vx0 < 0) {vx0 = 0;}
if (vy0 < 9) {vy0 = 0;}
if (vx1 > ctl->xsize) {vx1 = ctl->xsize;}
if (vy1 > ctl->ysize) {vy1 = ctl->ysize;}
for (h = h0; h <= h1; h++) {
sht = ctl->sheets[h];
buf = sht->buf;
//獲得當(dāng)前圖層對應(yīng)的窗口號
sid = sht - ctl->sheets0;
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];
//確定當(dāng)前坐標對應(yīng)的素點是否屬于要刷新的窗口
if (map[vy * ctl->xsize + vx] == sid && c != sht->col_inv) {
vram[vy * ctl->xsize + vx] = c;
}
}
}
}
}
}
refresh_sub在刷新時,會遍歷給定高度(h0)以上的所有圖層,如果某個高度比當(dāng)前要刷新的窗口高度高,并且它覆蓋了當(dāng)前窗體的某部分,那么兩個窗體的對應(yīng)像素就會重合,高度高的窗體會把對應(yīng)坐標處的像素標號設(shè)置成自己的窗體標號,也就是在上面的循環(huán)中,map[vy * ctl->xsize + vx] 所對應(yīng)的窗體標號與當(dāng)前窗體的標號就會不一樣,于是vram[vy * ctl->xsize + vx] = c;這一句就不會執(zhí)行,于是與當(dāng)前刷新窗體重合的,但高度更高的窗體就不用做沒必要的刷新。
其他對應(yīng)的圖層刷新函數(shù)也做相應(yīng)改動:
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_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);
}
}
void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height) {
....
if (old > height) {
....
if (height >= 0) {
....
//窗體高度更新后,要更改像素點所在位置的窗體標號
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 {
....
//窗體高度更新后,要更改像素點所在位置的窗體標號
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 {
....
//窗體高度更新后,要更改像素點所在位置的窗體標號
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);
}
}
做完上面的修改后,鼠標閃爍的問題就可以消失了。