EGC のブロック転送

タグ: #PC-98 

HR11 初出記事で、HR11 では EGC よりも GRCG を多用していること、EGC は 2 倍表示中の表示範囲変更の際のブロック転送にしか使っていないことを書いた。 もともとは、全部 GRCG で処理できるならばそうしてしまいたい気持ちはあった。 EGC の使い方に関する情報はあまり多くなく、自分でもその機能の理解には曖昧なところがあり、EGC を使った処理を書くならば絶対に実験から着手せねばならないことが想定されたからだ。

そういったことを頭の隅に置きながら実装を進めたが、やはり 386 や 486 にとってハイレゾの VRAM は広大だった。 また、プレーン方式であるため読み書きの位置がバイトまたはワード境界に乗らない場合にはシフト演算も必要となり更に処理時間が嵩んでしまう。 幾らか覚悟はしていたが、EGC を利用したブロック転送ルーチンの実装は必至のようだった。

で、参考資料 1 2 3 を見ながら実装してみたところ、今回のブロック転送の目的である 2 倍表示中の表示範囲変更では転送に際する VRAM データ加工は不要なので、ROP の設定は大したことなかったが、転送開始位置と転送回数の設定がなかなか上手くいかなかった。 数日苦しんだ結果、転送の設定についてはどうやら以下の 2 点に注意すれば大丈夫なようだとわかった。

  • 転送先の領域全体をカバーするワード数が転送元よりも大きい場合は、その分余計に転送が行われるようにする。
  • 最初のリードで得られるビット数が、最初のライトで書き出されるビット数より少ない場合は、その分余計に読み込みが行われるようにする。

つまり、以下のようにする。 転送にはストリング命令を使うことを想定している。

  1. 転送方向に依らない事前の共通の計算。
    1. src_bit_pos = 転送元ビット位置;
      dst_bit_pos = 転送先ビット位置;
      n_xfer_bits = 転送ビット数;
    2. 転送ワード数を、src 全体を読み取るのに必要なワード数で初期化。
      n_xfer_words = ((src_bit_pos & 0x0f) + n_xfer_bits + 15) / 16;
    3. src 領域と dst 領域のワード境界の位置を求める(バイト単位の偶数値での表現であることに注意)。
      src_word_pos = (src_bit_pos / 8) & ~1;
      dst_word_pos = (dst_bit_pos / 8) & ~1;
  2. DF = 0 で転送する場合。
    1. ビットアドレス計算。
      src_bit_addr = src_bit_pos & 0x0f;
      dst_bit_addr = dst_bit_pos & 0x0f;
    2. src 及び dst の転送で最後に転送されるワード位置の計算。
      src_last_word = (src_bit_addr + n_xfer_bits - 1) / 16;
      dst_last_word = (dst_bit_addr + n_xfer_bits - 1) / 16;
    3. 転送位置及び転送回数の調整。
      1. src_bit_addr == dst_bit_addr の場合は追加処理不要。
      2. src_bit_addr < dst_bit_addr の場合は n_xfer_word += (dst_last_word - src_last_word); する。
        最初のリードで dst に書き出されるビット数以上読み込める場合、dst 末尾がワード境界をはみ出すことがある。
      3. src_bit_addr > dst_bit_addr の場合、最初のリードで得られるビット数が最初の書き出しに必要なビット数より小さい。
        dst_word_pos -= 2; して、事前に 2 回リードしてデータが揃ってから dst に書き出されるよう、dst を 1 ワード減らす。
        さらに n_xfer_words += (dst_last_word + 1) - src_last_word; する。この 2-c-iii のケースでは、転送中は dst が src よりも 1 ワード遅れて書かれていくので追加の書き出しが必要な場合がある。 即ち、dst 冒頭位置を 1 ワード減らしたので、dst 側は (dst_last_word + 1) 回目の転送を最後の転送としなくてはならないが、n_xfer_word を src 全体を読み取るのに必要なワード数として求めているので、src 側の最後の転送である src_last_word 回目の転送が dst 側にとって足りないならば転送回数を増やす必要がある。
    4. src_word_pos から dst_word_pos に、高位のアドレス方向に n_xfer_word 回ワード単位で転送する。
  3. DF = 1 で転送する場合。
    1. ビットアドレス計算(転送領域末尾から、その直前のワード境界までの距離)。
      src_bit_addr = (src_bit_pos & 0x0f) ? 16 - (src_bit_pos & 0x0f) : 0;
      dst_bit_addr = (dst_bit_pos & 0x0f) ? 16 - (dst_bit_pos & 0x0f) : 0;
    2. src 及び dst の転送で最後に転送されるワード位置の計算。
      src_last_word = (src_bit_addr + n_xfer_bits - 1) / 16;
      dst_last_word = (dst_bit_addr + n_xfer_bits - 1) / 16;
    3. 転送位置及び転送回数の調整。
      1. src_bit_addr == dst_bit_addr の場合は追加処理不要。
      2. src_bit_addr < dst_bit_addr の場合は n_xfer_word += (dst_last_word - src_last_word); する。
      3. src_bit_addr > dst_bit_addr の場合、dst_word_pos += 2; して、2回のリードで dst に書き出すデータが揃ってから dst に書き出されるよう dst を 1 ワード増やす。
        さらに、転送中 dst が 1 ワード遅れて書かれるので追加の書き出しが必要な場合のために n_xfer_words += (dst_last_w + 1) - src_last_word; する。
    4. src_word_pos + n_xfer_words * 2 - 2 から dst_word_pos + n_xfer_words * 2 - 2 に、低位のアドレス方向に n_xfer_word 回ワード単位で転送する。

上記の処理は 1 ライン分の転送なので、これを縦方向に反復すればブロック転送が実現できる。

2-c-ii は割と直ぐ気づいたものの、2-c-iii を把握するにはちょっと時間がかかった。 多分合っている筈……なのだが、動くようになるまで割と紆余曲折したのもあって微妙に自信がない。 とは言えど、纏めてしまえばまあ納得の挙動という感じで何だかあっけないような気もするのだった。

  1. PC98 DOSプログラミング ハードウェアバリバリ編 (Internet Archive)

  2. 誘うPC98互換機(5) GDCだけではなくEGCもある

  3. EGCのエミュと実機の違い


投稿一覧に戻るトップに戻る