自作OSに挑戦する日記 11日目

 「30日でできる!OS自作入門」を読んで分かったことや、とりあえず書いておきたいことなどを書いていきます。
 この本はChapterが1から30まであるので、各チャプター毎に1記事書いていきます。

Chapter 11 「ウィンドウを出す」

今回やった内容

  • ウィンドウを表示させてみる
  • sheet_refreshの高速化

環境

作業記録

 まずはウィンドウの土台を作ります。閉じるボタンの表示はマウスの時と同じように、ウィンドウ本体は図形を組み合わせて作ります。

void make_window(unsigned char *buf, int width, int height, char *title){
    // 閉じるボタン
    static char close_button[14][16] ={
        "OOOOOOOOOOOOOOO@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQ@@QQQQ@@QQ$@",
        "QQQQQ@@QQ@@QQQ$@",
        "OQQQQQ@@@@QQQQ$@",
        "OQQQQQQ@@QQQQQ$@",
        "OQQQQQ@@@@QQQQ$@",
        "OQQQQ@@QQ@@QQQ$@",
        "OQQQ@@QQQQ@@QQ$@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQQQQQQQQQQQ$@",
        "O$$$$$$$$$$$$$$@",
        "@@@@@@@@@@@@@@@@"
    };

    // ウィンドウ本体
    boxfill8(buf, width, COL8_C6C6C6,           0,           0, width - 1,  0         );
    boxfill8(buf, width, COL8_FFFFFF,           1,           1, width - 1,  1         );
    boxfill8(buf, width, COL8_C6C6C6,           0,           0, 0,          height - 1);
    boxfill8(buf, width, COL8_FFFFFF,           1,           1, 1,          height - 2);
    boxfill8(buf, width, COL8_848484,   width - 2,           1, width - 2,  height - 2);
    boxfill8(buf, width, COL8_000000,   width - 1,           0, width - 1,  height - 1);
    boxfill8(buf, width, COL8_C6C6C6,           2,           2, width - 3,  height - 3);
    boxfill8(buf, width, COL8_000084,           3,           3, width - 4,  20        );
    boxfill8(buf, width, COL8_848484,           1,  height - 2, width - 2,  height - 2);
    boxfill8(buf, width, COL8_000000,           0,  height - 1, width - 1,  height - 1);
    putstr8(buf, width, 24, 4, COL8_FFFFFF, title);

    // 閉じるボタン描画
    for(int y = 0; y < 14; ++ y){
        for(int x = 0; x < 16; ++ x){
            char c = close_button[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) * width + (width - 21 + x)] = c;
        }
    }
}

 このmake_window関数をmainから呼ぶようにして、一緒に文字も描画させました!実行するとこのようになります〜!

 OS度が更に増しました…。


 今のままだとウィンドウが背景に描かれているような感じになっているので、10日目で作ったシートに描画してあげるようにします。

// ウィンドウ描画
make_window(buf_window, 160, 52, "counter");
sheet_slide(sheet_window, 80, 72);
sheet_updown(sheet_window, 1);

// マウス描画準備
init_mouse_cursor8(buf_mouse, 99);
sheet_slide(sheet_mouse, mouse_x, mouse_y);
sheet_updown(sheet_mouse, 2);

 マウスが隠れないように、シートの重なり優先度を適切に変更してあげます。
 これで、ウィンドウの表示の実装が終わりました!


 ウィンドウの表示が出来るようになりましたが、シートが増えて描画する数が増えたのでマウスの動きが多少もっさりしてきました。…ということでシートの更新を行う関数の最適化・高速化を行います。
 具体的には、

  • 更新するシートより上に描画しているシートしか更新しないようにする
  • 他のシートが上にある座標は再描画しない

…という2つの方法を用います。  この2つの方法を適用したコードは以下のようになりました。

// SHEET_CTL初期化
struct SHEET_CTL *sheet_ctl_init(struct MEMMAN *memman, unsigned char *vram, int width, int height){
    // 構造体初期化(メモリ確保も同時に)
    struct SHEET_CTL *sheet_ctl = (struct SHEET_CTL *) memman_alloc_4k(memman, sizeof(struct SHEET_CTL));
    if(sheet_ctl == 0){
        return 0;
    }

    // 描画マップ初期化
    sheet_ctl->g_map = (unsigned char *) memman_alloc_4k(memman, width * height);
    if(sheet_ctl->g_map == 0){
        memman_free_4k(memman, (int)sheet_ctl, sizeof(struct SHEET_CTL));
        goto err;
    }

    // 構造体設定
    sheet_ctl->vram = vram;
    sheet_ctl->width = width;
    sheet_ctl->height = height;
    sheet_ctl->top = -1;
    for(int idx = 0; idx < MAX_SHEETS; idx ++){
        sheet_ctl->sheet_data[idx].flags = 0;
        sheet_ctl->sheet_data[idx].sheet_ctl = sheet_ctl;
    }

err:
    return sheet_ctl;
}

// 描画マップ更新
void sheet_refresh_map(struct SHEET_CTL *sheet_ctl, int vram_x0, int vram_y0, int vram_x1, int vram_y1, int s_height){
    unsigned char *vram = sheet_ctl->vram;

    // シート全てで…
    for(int h_val = s_height; h_val <= sheet_ctl->top; h_val ++){
        struct SHEET *sheet = sheet_ctl->sheets[h_val];
        unsigned char *buf = sheet->buf;

        // メモリ番地を引き算してシートIDを取得する
        int sheet_id = sheet - sheet_ctl->sheet_data;

        // 描画対象が画面外だった場合修正する
        if(vram_x0 < 0) vram_x0 = 0;
        if(vram_y0 < 0) vram_y0 = 0;
        if(vram_x1 > sheet_ctl->width) vram_x1 = sheet_ctl->width;
        if(vram_y1 > sheet_ctl->height) vram_y1 = sheet_ctl->height;

        // 各シートのバッファを基準とした座標を逆算する
        int buf_x0 = vram_x0 - sheet->vram_x;
        int buf_y0 = vram_y0 - sheet->vram_y;
        int buf_x1 = vram_x1 - sheet->vram_x;
        int buf_y1 = vram_y1 - sheet->vram_y;

        // シートの外に出てしまった場合は修正する
        if(buf_x0 < 0) buf_x0 = 0;
        if(buf_y0 < 0) buf_y0 = 0;
        if(buf_x1 > sheet->buf_width) buf_x1 = sheet->buf_width;
        if(buf_y1 > sheet->buf_height) buf_y1 = sheet->buf_height;

        // 指定範囲だけマップ更新
        for(int y = buf_y0; y < buf_y1; y ++){
            for(int x = buf_x0; x < buf_x1; x ++){
                int vram_x = sheet->vram_x + x;
                int vram_y = sheet->vram_y + y;

                char color = buf[y * sheet->buf_width + x];
                if(color != sheet->col_inv){
                    sheet_ctl->g_map[vram_y * sheet_ctl->width + vram_x] = sheet_id;
                }
            }
        }
    }
}

 描画マップを新しく作成し、それぞれに座標において一番上にいるシートを記録しておきます。

// 描画マップ更新
void sheet_refresh_map(struct SHEET_CTL *sheet_ctl, int vram_x0, int vram_y0, int vram_x1, int vram_y1, int s_height){
    unsigned char *vram = sheet_ctl->vram;

    // シート全てで…
    for(int h_val = s_height; h_val <= sheet_ctl->top; h_val ++){
        struct SHEET *sheet = sheet_ctl->sheets[h_val];
        unsigned char *buf = sheet->buf;

        // メモリ番地を引き算してシートIDを取得する
        int sheet_id = sheet - sheet_ctl->sheet_data;

        // 描画対象が画面外だった場合修正する
        if(vram_x0 < 0) vram_x0 = 0;
        if(vram_y0 < 0) vram_y0 = 0;
        if(vram_x1 > sheet_ctl->width) vram_x1 = sheet_ctl->width;
        if(vram_y1 > sheet_ctl->height) vram_y1 = sheet_ctl->height;

        // 各シートのバッファを基準とした座標を逆算する
        int buf_x0 = vram_x0 - sheet->vram_x;
        int buf_y0 = vram_y0 - sheet->vram_y;
        int buf_x1 = vram_x1 - sheet->vram_x;
        int buf_y1 = vram_y1 - sheet->vram_y;

        // シートの外に出てしまった場合は修正する
        if(buf_x0 < 0) buf_x0 = 0;
        if(buf_y0 < 0) buf_y0 = 0;
        if(buf_x1 > sheet->buf_width) buf_x1 = sheet->buf_width;
        if(buf_y1 > sheet->buf_height) buf_y1 = sheet->buf_height;

        // 指定範囲だけマップ更新
        for(int y = buf_y0; y < buf_y1; y ++){
            for(int x = buf_x0; x < buf_x1; x ++){
                int vram_x = sheet->vram_x + x;
                int vram_y = sheet->vram_y + y;

                char color = buf[y * sheet->buf_width + x];
                if(color != sheet->col_inv){
                    sheet_ctl->g_map[vram_y * sheet_ctl->width + vram_x] = sheet_id;
                }
            }
        }
    }
}

 そして、再描画を行い際には描画マップをもとにして最低限の描画を行うようにします。これで、再描画がかなり早くなりました。    実行してみると、確かに前よりマウスの動きが俊敏になった…(?)ような気がします。  


 カウンタ変数を作成して、メインループが実行されるたびに加算されていくようなウィンドウを作成しました。こうしてみると確かに描画が早い…

まとめ

 11日目終了です!ウィンドウが描画できるようになりました!ウィンドウが1つ描画されるだけでかなりOSっぽさが増しますね…。  後半は裏の方を変更していきましたが、かなり変数が増えてきたので一度整理しておきたいところです。