30日OS自作入門10日目(Win10)

初めに

あーねんまつ、マイペースに続けていきます

目次

重ね合わせ処理

メモリ管理の続き(harib07a)

メモリ管理をするために実装した関数と構造体等をmemory.cのファイルに整理していきます。(もちろんMakefileやbootpack.h等にも)

その際に、1バイト単位でメモリの管理を行っていくと穴だらけになりやすいので、0x1000 = 4KBで管理するようにするみたいです。ですが、要求されるメモリのサイズは、1バイト基準で要求されるので、16進数の切り下げと切り上げを関数で実装してあげる必要があります。

そこで、本に載っている方法が、切り捨ての場合は、下位3バイトをAND演算を使用し、強制的に0にすることみたいです。また、切り上げは、下位3バイトが0になってしまった場合をif文で除外し、切り捨ての処理を行った後に0x1000を加算してあげる操作を行うことで実現できるみたいです。

しかし、コードに実装している方法は、本では違っており、裏技のような手法を使用されています。

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

こういうのをアルゴリズムっていうんですかね?ほんとすごい…

/* メモリ確保を4KBごとに */
unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
  unsigned int a;
  size = (size + 0xfff) & 0xfffff000; /* サイズをAND命令を使い切り上げ、切り下げをする */
  a = memman_alloc(man, size); /* 切り上げ下げを行ったサイズで確保 */
  return a;
}

/* メモリ開放を4KBごとに */
int memman_free_4k(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
  int i;
  size = (size + 0xfff) & 0xfffff000; /* サイズをAND命令を使い切り上げ、切り下げをする */
  i = memman_free(man, addr, size); /* 切り上げ下げを行ったサイズで開放 */
  return i;
}

こんな感じで、確保と解放の二つに実装します。
その後、整理して実行すると
f:id:No000:20191217174459p:plain

重ね合わせ処理(harib07b)

マウスの重ね合わせ処理のコードを書いてゆくみたいです。

イメージとしては、透明な下敷きを複数枚用意し、その上に重ね合わせるために分けたい描画をのせます。その後、それを、イラストソフトのレイヤーのように、重ねるように描画していく処理です。ただ、レイヤーの層を上や下に行くのに加え、そのレイヤーで上下左右に動かす関数も実装しなければなりません。

まず、下敷きを管理する構造体は以下のようになっています。

/* 下敷き1枚の情報 */
struct SHEET {
    unsigned char *buf;
    int bxsize, bysize, vx0, vy0, col_inv, height, flags;
    /* boxsize:下敷きの大きさx, bysize:下敷きの大きさy, vx0:下敷きの位置x, vy0:下敷きの位置y */
    /*col_inv:透明色の番号, height:下敷きの高さ, flags:下敷きの設定情報 */
};

flagsの情報というのは、使用中か未使用かということです。

そして、下敷き一枚一枚を管理する構造体が次のようになります。

/* 各種下敷きの管理情報 */
struct SHTCTL {
    unsigned char *vram; /* vramのアドレス */
    int xsize, ysize, top; /* xsize:全体画面の大きさx, ysize:全体画面の大きさy, top:1番上の下敷きの高さ */
    struct SHEET *sheets[MAX_SHEETS]; /* SHEETのデータを並び変えるためにアドレスを管理 */
    struct SHEET sheets0[MAX_SHEETS]; /* SHEETのデータを256枚分用意 */
};

シートは256枚分用意されており、また、画面全体の情報もいちいち確認しに行かなくていいように載せているみたいです。

今度はこれらのメンバの初期化とsheetのメモリ確保をメモリ管理を利用して行う関数を書きます。

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)); 
  /* メモリを確保する(sizeofで構造体の必要なメモリを計算) */
  
  /* (struct SHTCTL *)はポインタに型が違うものを代入しようとしていると警告されることへの対策 */
  if (ctl == 0) {     /* メモリが確保できなかった場合 */
    goto err;         /* エラー処理を行う */
  }
  ctl->vram  = vram;  /* vramのアドレスを渡す */
  ctl->xsize = xsize; /* 画面サイズxの情報を渡す */
  ctl->ysize = ysize; /* 画面サイズyの情報を渡す */
  ctl->top   = -1;  /* シートは1枚もない */
  for (i = 0; i < MAX_SHEETS; i++) {  /* シートの最大数まで処理を行う */
    ctl->sheets0[i].flags = 0; /* 未使用マークを添付 */
  }
err:  /* エラーならここへショートカット */
  return ctl; /* 構造体を返す */
}

ここで、まず全体の情報の構造体が占めるメモリを確保します(確保できなかったら、そのまま構造体を返す)。その後、画面サイズやVRAMの情報を記録し、シートの上限を-1にリセット(-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; /* 全てのシートが使用中だった */
}

シートを0から、未使用のシートが見つかるまで検索をかけます。見つかったら、使用中とする各種情報を入れ、確保したシートの情報を持った構造体をポインタ型として、アドレスを返します。

下敷き一枚の情報をセットする関数

/* 要求された下敷き等の情報を構造体のメンバに入れる */
void sheet_setbuf(struct SHEET *sht, unsigned char *buf, int xsize, int ysize, int col_inv)
{
  sht->buf = buf;           /* バッファを設定する */
  sht->bxsize = xsize;      /* xの大きさ */
  sht->bysize = ysize;      /* yの大きさ */
  sht->col_inv = col_inv;   /* 透明色の設定 */
  return;
}

バッファ・大きさ・透明色を引数からセット

下敷きの高さを調整する関数

/* 下敷きの高さを設定する関数 */
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) {            
  /* 下敷きの高さが-1より低くなったら */
    height = -1;                
    /* 下敷きの高さを-1に合わせる */
  }
  sht->height = height; /* 高さを設定 */

  /* 以下は主にsheets[]の並び替え */
  if (old > height) {       
  /* 以前よりも低くなる */
    if (height >= 0) {      
    /* 高さが0以上であれば */
      /* 間のものを引き上げる */
      for (h = old; h > height; h--) {    
      /* 設定前の高さから、設定後の高さまで */
        ctl->sheets[h] = ctl->sheets[h - 1];  
        /* 1個下のメンバに更新 */
        ctl->sheets[h]->height = h;     
        /* 1個下という高さの情報を現在の高さに変更 */
      }
      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;   
          /* 1個上という高さの情報を現在の高さに変更 */
        }
      }
      ctl->top--; 
      /* 表示中の下敷きが一つ減るので一番上の高さが減る */
    }
    sheet_refresh(ctl); 
    /* 新しい下敷きの情報に沿って画面を描き直す */
  } else if (old < height) {    
    /* もし高さが、設定前より高くなるのなら */
    if (old >= 0) {   
    /* 設定前の高さが0以上(表示)だったら */
      /* 間のものを押し上げる */
      for (h = old; h< height; h++) { 
      /* oldから設定後の高さまでを */
        ctl->sheets[h] = ctl->sheets[h + 1];  
        /* 一個上のメンバに更新 */
        ctl->sheets[h]->height = h;   
        /* 1個上という高さの情報を現在の高さに変更 */
      }
      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 + 1;   
        /* 高さの情報を一個下からhの高さへ */
      }
      ctl->sheets[height] = sht;  
      /* 用意できた高さへデータを挿入 */
      ctl->top++; 
      /* 表示中の下敷きが1つ増えるので、一番上の高さが増える */
    }
    sheet_refresh(ctl); 
    /* 新しい下敷きの情報に沿って画面を描きなおす */
  }
  return;
}

表を上下して下敷き一枚の構造体のデータを入れ替えたのち、データ自体の高さを入れ替えに対応させているイメージ

画面を描画しなおす関数

void sheet_refresh(struct SHTCTL *ctl)
{
  int h, bx, by, vx, vy;  /* h:, bx:, by:, vx:, vy: */
  unsigned char *buf, c, *vram = ctl->vram; /* buf:, c:, vram: */
  struct SHEET *sht;  /* シート一枚分のデータ */
  for (h = 0; h <= ctl->top; h++) { /* 下から一番上の下敷きまでを */
    sht = ctl->sheets[h];   /*  1枚ずつシートの情報をセットし*/
    buf = sht->buf; /* バッファに画素の情報を格納 */
    for (by = 0; by < sht->bysize; by++) {
      vy = sht->vy0 + by;   /* yの下敷きのサイズを設定 */
      for (bx = 0; bx < sht->bxsize; bx++) {
        vx = sht->vx0 + bx; /* xの下敷きのサイズを設定 */
        c = buf[by * sht->bxsize + bx]; /* 描画する画素のデータの取得 */
        if (c != sht->col_inv) {  /* 透明色以外を */
          vram[vy * ctl->xsize + vx] = c; /* vramに書き込む */
        }
      }
    }
  }
  return;
}

画面の情報を、下敷きを含めて計算し、バッファから、1枚ずつしたから描画してゆく

レイヤー内での上下左右の重ね合わせ処理

void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0)
{
  sht->vx0 = vx0; /* 移動したx座標の情報を格納 */
  sht->vy0 = vy0; /* 移動したy座標の情報を格納 */
  if (sht->height >= 0) { /* もしも表示中なら(下に描画があれば) */
    sheet_refresh(ctl); /* 新しい下敷きの情報に沿って画面を描き直す */
  }
  return;
}

移動した情報を更新し、背後の描画があれば描画しなおす

使い終わった下敷きをリセットする関数

void sheet_free(struct SHTCTL *ctl, struct SHEET *sht)
{
  if (sht->height >= 0) { /* 表示中なら */
    sheet_updown(ctl, sht, -1);  /* 表示中ならまず非表示にする */
  }
  sht->flags = 0; /* 未使用マークを添付 */
  return;
}

表示中なのかを判定し、表示中なら非表示にする。その後、未使用マークの添付

その後Harimainを、重ね合わせ処理に対応させ(ソースコード見ればわかる)実行してみると
f:id:No000:20191218174450p:plain

少し、重なるときちらっとするけど消えない!

重ね合わせ処理の高速化(1)(harib07c)

マウスの描画はたった256画素だけでいいのに、毎回64000画素を書き換えているので遅くなっているということで…。あとは、文字も最小限の画素の書き換えで大丈夫。ということから、指定箇所だけ描画しなおす関数を作るみたいです。

sheet_refreshをベースにsheet_refreshsubを作成します。

if (vx0 <= vx && vx < vx1 && vy0 <= vy && vy < vy1) {   /* 描画できる範囲を指定 */

この関数により、指定のブロックのみを描画しなおしすることができます。

sheet_slideも移動前と移動後での描きなおしをsheet_refreshsubで設定する

画面全体を描画しなおす関数のsheet_refreshは、sheet_refreshsubを利用して

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); 
    /* 画面全体を対象としてsheet_refreshsubを実行 */
  }
  return;
}

全体を対象の引数を渡してる

レイヤー変更のsheet_updownもsheet_refreshsubをベースに修正を行います。

bootpackも関数sheet_refreshの引数を指定することと、再描画のタイミングを一か所追加する(bootpack.hのsheet_refreshの引数が変わっているので注意)。

実行すると

重ね合わせ処理の高速化(2)(harib07d)

まだ、物足りない速さのようです。確かに、遅いってこともありませんけど描画をまたぐ際に少しチラってしたような気がします。

この原因が、if文が入れ子として、いくつも連なっていることが望ましくないようです。

よって修正する方法は、下敷きの中でも書き換える場所だけを書き換えるようにするといった方式にするようです。

   /* vx0~vy1を使って、bx0~by1を逆算する */
    bx0 = vx0 - sht->vx0; /* bx0のデータが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; }

このコードで、右下を基準として小さくするか、左上を基準にして小さくするかを条件分岐させることで実現されています。

実行すると

ぬるぬるだー!

終わりに

次は、ウィンドウです。最近色々ありモチベがあげあげですね!

C言語

sizeof()

渡された変数のサイズを計算し返す。

&&

&&: かつ
|| :または