Day11

今天其实还是继续在搞显示方面的问题。

一些感想与问题

改来改去的。。

上一节刚定义好各种SHTCTL,这一节觉得用起来太麻烦了,又往里头加了点东西。。所有显示函数大改。。。emmmm失去耐心

画面外支持

首先是支持图层的部分到画面外,这样做很简单,只要我们在绘制的时候,对于图层边界以及显示边界取个min即可。

图层指定管理它的图层管理

听起来有点绕口。其实把图层管理理解为树根,图层理解为子节点的话,就是添加了子节点到树根的指针。这样做更方便对图层直接指定图层管理了。

应对重绘带来的闪烁

当我们重绘需要进行的比较快的时候,有可能各个叠加的图层之间还没画好。于是这样解决

  1. 重绘一定高度的图层
    我们没必要从最底层开始重绘各个图层,我只需要画被改变的图层,以及其高度以上的图层即可。毕竟下面的都显示不出来。
    但是其实这样只是避免了底层的闪烁。假设该图层不是最高层,还是会因为不断重绘造成该层和高层之间的闪烁。
  2. 设置每一个像素对应的图层表
    我们对应每个像素是要由哪个图层来绘制。于是有更改的时候,先在表里写好每个像素对应的图层(这时不涉及绘制,不会有闪烁),重绘的时候,判断一下该像素应不应该被这个图层重绘即可。
  3. 有没有其它的改进策略?
    看到作者这里用了一个map来预先储存要绘制的信息,我们也很容易想到双缓冲技术——其实这里也有点像,但是仔细看sheet_slide()这个函数的话,其实还是一层层的重绘,但是是因为每个位置只会对应一个重绘图层,不会造成闪烁。

以上的每一步,伴随的都是各种函数的大改,看起来会有点麻烦。。

鼠标显示问题

harib08a

其实是我们之前设置的问题,现在只要鼠标的左上角还在屏幕里就行。

if(mx > binfo->scrnx - 1)
{
    mx = binfo->scrnx - 1;
}
if(my > binfo->scrny - 1)
{
    my = binfo->scrny - 1;
}

但是我们只是改了位置限制,还没有改重绘函数

实现画面外的支持

harib08b

在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;
    if(vx0 < 0) vx0 = 0;
    if(vy0 < 0) vy0 = 0;
    if(vx1 > ctl->xsize) vx1 = ctl->xsize;
    if(vy1 > ctl->ysize) vy1 = ctl->ysize;

改的其实就这4个if

shtctl的指定省略

harib08c

为了使得我们更改图层的时候,能更方便地同时更改图层管理,我们在SHEET里加上了指向SHTCTL的指针。
所以,init、updown、refresh、slide、free都需要修改一下。。因为不用再指定shtctl了
代码就省略了吧。。没什么技术含量。
然后主函数里也可以顺便不用指定shtctl了。

显示窗口

harib08d

我们申请个图层来管理一个窗口,在buf里写上各种矩形,还有一个“X”:

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;
}

于是,我们在主函数里也进行了一些修改:申请图层、申请图层缓冲区、绘制图层、调整图层位置和高度。(代码略)

小实验

harib08e

如果把不同图层放到不同高度,就可以做到比如,鼠标被窗口覆盖的效果。

高速计数器

harib08f

原来是不断累加一个数啊。。之后把数绘制到窗口里。
方法本来很简单,但是会引出绘制时的闪烁问题,接下来两个标题都是来解决这个问题的。

消除闪烁(1)

harib08g

用的是之前说的第1个方法,为此我们又要改refresh_sub及其相关函数。。
对于其相关函数,我们只需要重绘对应图层高度及以上即可。
接下来要解决更高图层的闪烁

消除闪烁(2)

harib08h

引入了refresh_map:先把各个像素写好。
map的init就不写了,这里看一下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;
}

一个从低到高逐层往map里写入的过程。
那么,重绘函数就要先刷新map,然后再进行绘制啦。
看一下refresh_sub的判断:属于这个图层该绘制的,再绘制它

if (map[vy * ctl->xsize + vx] == sid) {
    vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];
}

之后是refresh_slide:
把一个高度为h的图层从S移动到T,S位置需要从高度0开始重绘到h - 1(确切地说,map需要从0检测到top,而重绘只需要从0重绘到h-1),T位置需要从高度h开始重绘到top(确切地来说,map需要从h检测到top,而重绘只需要仅绘制h就行了)
(S、T不用重绘更高层的原因是:毕竟如果更高重叠了,我们也肯定用不上重绘,本来就在那里嘛)

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);
}

然后是sheet_updown中,当我们把图层提高或者降低之后,在refreshsub之前需要添加refreshmap。

这两天都在搞各种图形的绘制。。看着代码是真的长。。。。