Day10

今天在稍微修改了一下内存管理之后,进入了处理图像叠加的问题上。

一些感想

按页(4KB)分配内存

虽然我们可能会多分配出去一些内存,但是这样可以有效减少内存碎片。
那么,这就涉及到了一个向上取整的问题。代码其实很简单

size = (size + 0xfff) & 0xfffff000;

相当于加上0xfff后再下取整,很好理解吧)

逐渐进化的叠加处理

为了绘制不同的窗口、图像,我们需要把这些图形放到不同的图层里头,不同的图层会有不同的高度(z轴),在绘制时候从下往上逐渐绘制即可。
具体的各种函数放到下面,现在来看一下,在每一次迭代中,图层是怎么重新绘制的

  1. 刷新时,重绘所有图层
    这是最暴力的写法,需要把所有图层所有地方都要画一遍。可想而知,没有很好地处理重叠造成的计算浪费。
  2. 刷新时,重绘改变部分
    例如鼠标移动,我们只需要重绘前后位置即可,这样可以节省很多像素的开销。
  3. 更改逻辑
    在第2个版本中,作者会穷举一个窗口的所有像素,然后检查这个像素是不是在另一个窗口中。
    我们可以这么优化:先计算出重叠部分,然后直接绘制这一个窗口就行了。

今天的代码比较多,但是技术含量不太高)

内存管理(续)

harib07a

我们对外分配内存的时候,按页分配内存,然后这个函数再使用我们之前写的分配内存函数

unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
    unsigned int a;
    size = (size + 0xfff) & 0xfffff000;
    a = memman_alloc(man, size);
    return a;
}

int memman_free_4k(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
    int i;
    size = (size + 0xfff) & 0xfffff000;
    i = memman_free(man, addr, size);
    return i;
}

叠加处理

harib07b

这里,我们需要引入图层的概念。因此,要引入图层的定义,接着是管理程序管理图层。
之后是管理图层的方法:初始化、申请、设定、调整高度、刷新显示屏、移动图层、释放图层函数。
一个个来
图层和图层管理的定义:

#define MAX_SHEETS        256
struct SHEET {
    unsigned char *buf;
    int bxsize, bysize, vx0, vy0, col_inv, height, flags;
};
struct SHTCTL {
    unsigned char *vram;
    int xsize, ysize, top;
    struct SHEET *sheets[MAX_SHEETS];
    struct SHEET sheets0[MAX_SHEETS];
};

其中,数组相当于是一个图层池,指针才是提供给每一个图层使用(并且排序)的。
初始化:

struct SHTCTL* shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize)
{
    struct SHTCTL *ctl;
    int i;
    ctl = (struct SHTCTL *)memman_alloc_4k(memman, sizeof(struct SHTCTL));
    if(ctl == 0)
    {
        goto err;
    }
    ctl->vram = vram;
    ctl->xsize = xsize;
    ctl->ysize = ysize;
    ctl->top = -1; // 1个sheet都没有
    for(i = 0; i < MAX_SHEETS; ++i)
    {
        ctl->sheets0[i].flags = 0; // 标记为未使用
    }
err:
    return ctl;
}

给管理结构体分配内存。
申请:

#define SHEET_USE 1
struct SHEET* sheet_alloc(struct SHTCTL *ctl)
{
    struct SHEET *sht;
    int i;
    for(i = 0; i < MAX_SHEETS; ++i)
    {
        if(ctl->sheets0[i].flags == 0)
        {
            sht = &ctl->sheets0[i];
            sht->flags = SHEET_USE; // 标记为正在使用
            sht->height = -1; // 隐藏
            return sht;
        }
    }
    return 0; // 所有SHEET都处于正在使用状态
}

设定:

void sheet_setbuf(struct SHEET *sht, unsigned char *buf, int xsize, int ysize, int col_inv)
{
    sht->buf = buf;
    sht->bxsize = xsize;
    sht->bysize = ysize;
    sht->col_inv = col_inv;
}

调整图层,其实是把一个图层改变高度后,插入到已经有序的图层之中。

void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height)
{
    int h, old = sht->height; // 存储设置前的高度信息

    // 如果指定的高度过高或过低,则进行修正
    if(height > ctl->top + 1)
    {
        height = ctl->top + 1;
    }
    if(height < -1)
    {
        height = -1;
    }
    sht->height = height; //设定高度
    // 下面主要是进行sheets[]的重新排列
    if(old > height) // 比以前低
    {
        if(height >= 0)
        {
            // 把中间的往上提
            for(h = old; h > height; --h)
            {
                ctl->sheets[h] = ctl->sheets[h - 1];
                ctl->sheets[h]->height = h;
            }
            ctl->sheets[height] = sht;
        }
        else // 隐藏
        {
            if(ctl->top > old)
            {
                // 把上面的降下来
                for(h = old; h < ctl->top; ++h)
                {
                    ctl->sheets[h] = ctl->sheets[h + 1];
                    ctl->sheets[h]->height = h;
                }
            }
            ctl->top--; // 由于显示中的图层减少了一个,所以最上面的图层高度下降
        }
        sheet_refresh(ctl); // 按新图层的信息重新绘制画面
    }
    else if(old < height) // 比以前高
    {
        if(old >= 0)
        {
            // 把中间的拉下去
            for(h = old; h < height; ++h)
            {
                ctl->sheets[h] = ctl->sheets[h + 1];
                ctl->sheets[h]->height = h;
            }
            ctl->sheets[height] = sht;
        }
        else // 由隐藏状态转为显示状态
        {
            // 将已在上面的提上来
            for(h = ctl->top; h >= height; --h)
            {
                ctl->sheets[h + 1] = ctl->sheets[h];
                ctl->sheets[h + 1]->height = h;
            }
            ctl->sheets[height] = sht;
            ctl->top++; // 由于已显示的图层增加了1个,所以最上面的图层高度叠加
        }
        sheet_refresh(ctl); // 按新图层信息重新绘制
    }

}

刷新屏幕:

void sheet_refresh(struct SHTCTL *ctl)
{
    int h, bx, by, vx, vy;
    unsigned char *buf, c, *vram = ctl->vram;
    struct SHEET *sht;
    for(h = 0; h <= ctl->top; ++h)
    {
        sht = ctl->sheets[h];
        buf = sht->buf;
        for(by = 0; by < sht->bysize; ++by)
        {
            vy = sht->vy0 + by;
            for(bx = 0; bx < sht->bxsize; ++bx)
            {
                vx = sht->vx0 + bx;
                c = buf[by * sht->bxsize + bx];
                if(c != sht->col_inv)
                {
                    vram[vy * ctl->xsize + vx] = c;
                }
            }
        }
    }
}

非常朴素的办法。。
移动图层:

void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0)
{
    sht->vx0 = vx0;
    sht->vy0 = vy0;
    if(sht->height >= 0) // 如果正在显示
    {
        sheet_refresh(ctl); // 按新图层的信息刷新画面
    }
}

释放图层:

void sheet_free(struct SHTCTL *ctl, struct SHEET *sht)
{
    if(sht->height >= 0)
    {
        sheet_updown(ctl, sht, -1); // 如果处于显示状态,则先设定为隐藏
    }
    sht->flags = 0; // “未使用”标志
}

之后是很长的。。对主函数的修改,这里就不放上来了,大体上除了SHTCTL的初始化之外,就是鼠标在移动的时候调用重绘函数即可。

提高叠加处理速度(1)

harib07c

原理已讲,我们只重新画改变的地方看看。

void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1)
{
    int h, bx, by, vx, vy;
    unsigned char *buf, c, *vram = ctl->vram;
    struct SHEET *sht;
    for(h = 0; h <= ctl->top; ++h)
    {
        sht = ctl->sheets[h];
        buf = sht->buf;
        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];
                    if(c != sht->col_inv)
                    {
                        vram[vy * ctl->xsize + vx] = c;
                    }
                }
            }
        }
    }
}

然后修改slide:

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_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize);
        sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize);
    }
}

然后refresh,这样就不需要刷新全屏了:

void sheet_refresh(struct SHTCTL *ctl, struct SHEET *sht, int bx0, int by0, int bx1, int by1)
{
    if (sht->height >= 0) // 如果正在显示,则按新图层的信息刷新画面
    {
        sheet_refreshsub(ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1);
    }
    return;
}

然后updown也要改。。主函数也要改。这里就不写了。。

提高叠加处理速度(2)

harib07d

我们改进一下,直接算出重叠部分

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;
    for(h = 0; h <= ctl->top; ++h)
    {
        sht = ctl->sheets[h];
        buf = sht->buf;
        // 使用vx0~vy1,对bx0~by1进行倒推
        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;
                c = buf[by * sht->bxsize + bx];
                if(c != sht->col_inv)
                {
                    vram[vy * ctl->xsize + vx] = c;
                }
            }
        }
    }
}

emmm今天基本上都在讲绘制了,抛开代码量,本身逻辑还是挺好懂的。。