8 消除闪烁(2)(harib08h)

怎么样才能让鼠标不再闪烁呢?闪烁现象是由于一会儿描绘一会儿消除造成的。所以说要想消除闪烁,就要在刷新窗口时避开鼠标所在的地方对VRAM进行写入处理。这好像挺难的,但不管怎样我们还是要努力一下。

而且如果这里做好了,刷新窗口时就不需要重绘鼠标了,这样速度也能相应提高。一想到这里,就充满了克服困难的勇气和动力!

■■■■■

这样也不行,那样也不行,左思右想之后我们决定采用下面这种方法。首先,开辟一块内存,大小和VRAM一样,我们先称之为map(地图)吧。至于为什么要叫做地图,我们马上就来讲解。

本次的bootpack.h和sheet.c程序的节选

  1. struct SHTCTL {
  2. unsigned char *vram, *map; /* 这里! */
  3. int xsize, ysize, top;
  4. struct SHEET *sheets[MAX_SHEETS];
  5. struct SHEET sheets0[MAX_SHEETS];
  6. };
  7. struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize)
  8. {
  9. struct SHTCTL *ctl;
  10. int i;
  11. ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
  12. if (ctl == 0) {
  13. goto err;
  14. }
  15. /* 从这里开始 */
  16. ctl->map = (unsigned char *) memman_alloc_4k(memman, xsize * ysize);
  17. if (ctl->map == 0) {
  18. memman_free_4k(memman, (int) ctl, sizeof (struct SHTCTL));
  19. goto err;
  20. }
  21. /* 到这里结束 */
  22. ctl->vram = vram;
  23. ctl->xsize = xsize;
  24. ctl->ysize = ysize;
  25. ctl->top = -1; /* 没有一张SHEET */
  26. for (i = 0; i < MAX_SHEETS; i++) {
  27. ctl->sheets0[i].flags = 0; /* 未使用标记 */
  28. ctl->sheets0[i].ctl = ctl; /* 记录所属 */
  29. }
  30. err:
  31. return ctl;
  32. }

这块内存用来表示画面上的点是哪个图层的像素,所以它就相当于是图层的地图。

8 消除闪烁(2)(harib08h) - 图1

当刷新图层1的时候,如果一边看着这个map一边刷新的话,就不必担心图层1和图层2重叠的部分被覆盖了。

■■■■■

下面我们来写向map中写入1、2等图层号码的函数。

本次的sheet.c节选

  1. void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0)
  2. {
  3. int h, bx, by, vx, vy, bx0, by0, bx1, by1;
  4. unsigned char *buf, sid, *map = ctl->map;
  5. struct SHEET *sht;
  6. if (vx0 < 0) { vx0 = 0; }
  7. if (vy0 < 0) { vy0 = 0; }
  8. if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }
  9. if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }
  10. for (h = h0; h <= ctl->top; h++) {
  11. sht = ctl->sheets[h];
  12. sid = sht - ctl->sheets0; /* 将进行了减法计算的地址作为图层号码使用 */
  13. buf = sht->buf;
  14. bx0 = vx0 - sht->vx0;
  15. by0 = vy0 - sht->vy0;
  16. bx1 = vx1 - sht->vx0;
  17. by1 = vy1 - sht->vy0;
  18. if (bx0 < 0) { bx0 = 0; }
  19. if (by0 < 0) { by0 = 0; }
  20. if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }
  21. if (by1 > sht->bysize) { by1 = sht->bysize; }
  22. for (by = by0; by < by1; by++) {
  23. vy = sht->vy0 + by;
  24. for (bx = bx0; bx < bx1; bx++) {
  25. vx = sht->vx0 + bx;
  26. if (buf[by * sht->bxsize + bx] != sht->col_inv) {
  27. map[vy * ctl->xsize + vx] = sid;
  28. }
  29. }
  30. }
  31. }
  32. return;
  33. }

这个函数与以前的refreshsub函数基本一样,只是用色号代替了图层号码而已。代表图层号码的变量sid是“sheet lD1 ”的缩写。

1 ID是英文identification的缩写,意为“用来表示身份的证件以及某一身份所对应的证件号码或记号等”。

■■■■■

下面是sheet_refreshsub函数。我们对它进行改写,让它可以使用map。

本次的sheet.c节选

  1. void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0, int h1)
  2. {
  3. int h, bx, by, vx, vy, bx0, by0, bx1, by1;
  4. unsigned char *buf, *vram = ctl->vram, *map = ctl->map, sid;
  5. struct SHEET *sht;
  6. /* 如果refresh的范围超出了画面则修正*/
  7. (中略)
  8. for (h = h0; h <= h1; h++) {
  9. sht = ctl->sheets[h];
  10. buf = sht->buf;
  11. sid = sht - ctl->sheets0;
  12. /* 利用vx0~vy1,对bx0~by1进行倒推 */
  13. (中略)
  14. for (by = by0; by < by1; by++) {
  15. vy = sht->vy0 + by;
  16. for (bx = bx0; bx < bx1; bx++) {
  17. vx = sht->vx0 + bx;
  18. if (map[vy * ctl->xsize + vx] == sid) {
  19. vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];
  20. }
  21. }
  22. }
  23. }
  24. return;
  25. }

今后程序会对照map内容来向VRAM中写入,所以有时没必要从下面开始一直刷新到最上面一层,因此不仅要能指定h0,也要可以指定h1。

■■■■■

现在我们来修改调用了sheet_refreshsub的3个函数,先从较短的2个入手吧。

本次的sheet.c节选

  1. void sheet_refresh(struct SHEET *sht, int bx0, int by0, int bx1, int by1)
  2. {
  3. if (sht->height >= 0) { /* 如果正在显示,则按新图层的信息进行刷新 */
  4. sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1,
  5. sht->height, sht->height);
  6. }
  7. return;
  8. }
  9. void sheet_slide(struct SHEET *sht, int vx0, int vy0)
  10. {
  11. struct SHTCTL *ctl = sht->ctl;
  12. int old_vx0 = sht->vx0, old_vy0 = sht->vy0;
  13. sht->vx0 = vx0;
  14. sht->vy0 = vy0;
  15. if (sht->height >= 0) { /* 如果正在显示,则按新图层的信息进行刷新 */
  16. sheet_refreshmap(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0);
  17. sheet_refreshmap(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height);
  18. sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0,
  19. sht->height - 1);
  20. sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height,
  21. sht->height);
  22. }
  23. return;
  24. }

在sheet_refresh函数里,由于图层的上下关系没有改变,所以不需要重新进行refreshmap的处理。实际上,我们有时候要把透明的地方变成不透明的,或者反过来要把不透明的地方变成透明的,遇到这些情况就必须重新编写map了,不过这里的sheet_refreshrefresh函数没有考虑这些情况。如果需要实现这样的功能,就要再编写其他的函数。

另外,在sheet_refresh函数里,需要刷新的图层只有一张,所以速度应该比较快。

在sheet_slide函数里,首先重写map,分别对应移动前后的图层,然后调用sheet_refreshsub函数。在移动前的地方,只针对上层图层移走之后而露出的下层图层进行重绘就可以了。在移动目的地处仅重绘了一张移动过去的图层。

■■■■■

最后是sheet_updown函数。

本次的sheet.c节选

  1. void sheet_updown(struct SHEET *sht, int height)
  2. {
  3. (中略)
  4. /* 下面主要是对sheets[]进行重新排列 */
  5. if (old > height) { /* 比以前低 */
  6. if (height >= 0) {
  7. /* 中间的图层也提高一层 */
  8. (中略)
  9. sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 +
  10. sht->bysize, height + 1);
  11. sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 +
  12. sht->bysize, height + 1, old);
  13. } else { /* 隐藏 */
  14. (中略)
  15. sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, 0);
  16. sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, 0, old - 1);
  17. }
  18. } else if (old < height) { /* 比以前高 */
  19. (中略)
  20. sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize,
  21. height);
  22. sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize,
  23. height, height);
  24. }
  25. return;
  26. }

在调用sheet_refreshsub函数之前,先执行sheet_refreshmap来重做map。

■■■■■

通过这些修改,闪烁现象真能消失吗?速度会变快吗?

感觉速度好像稍稍变快了些,而且鼠标不再闪烁了。我们成功了!

8 消除闪烁(2)(harib08h) - 图2

即使这样,鼠标也不闪啦。

哎呀,不知不觉居然都已经这么晚了,今天我们就到这里吧,明天见!