GB は元々8x8ドットのタイルを20x18個敷き詰めて1画面を構成するタイルマップなのですが
GBLibrary では、これを1ドット単位のビットマップのように扱います。
白黒GBでは、全画面をタイルで埋めようにも256個のタイルしか使えないので20x18=320個を
カバーすることが出来なかったのですが(ただし特殊なやり方で可能)、GBC になって、
さらに256個増えたので、20x18の全てに1つ1つタイルを敷き詰め、各タイル8x8ドットを
1ドット単位でアクセスすれば、全画面160x144ドットをビットマップとして扱えるようになりました。
さて、そうなると全画面を書き換えた場合にどのくらいの転送量が発生するかですが、
パレット固定4色に制限すると、1ドット=2ビットなので、160x144x2=46080ビット=5760バイト
の転送をしなければなりません。
LD BC,5760 / 2
DEC BC
INC B
INC C
LD SP,BUFFER+5760
LD HL,VRAM+5760
LOOP:
DEC HL
POP DE
LD [HL],E
DEC HL
LD [HL],D
DEC C
JR NZ,LOOP
DEC B
JR NZ,LOOP |
普通に転送するとこんな感じでしょうか。実際はタイルエリアのバンク切り替えを挟まないと
いけないので、このままではダメですが。DI前提SP待避とか細々した制約もあります。
ループ部分だけ数えると 41,852clock、 GB は CPU クロックが 4MHz なので 4clock=1usとなり
10,463us=約10ミリ秒かかることになります。
・・・VRAM の構造とパレット配色を考えてループ展開するともう少し速くなるかも。
VRAM を安全に操作するには CRTC が VRAM にアクセスしていない間、V-Blank期間を使います。
この V-blank期間は 1.08ミリ秒しかありません。上記の方法では全然間に合わないので
CRTCの方を止めて、転送が終わってからCRTCを再度ONにするしかありません。
このため、液晶画面が一旦ブラックアウトして画面が出るまで少し見苦しいことになります。
LD A,$C0
LDH [rHDMA1],A
LD A,$00
LDH [rHDMA2],A
LD A,$08
LDH [rHDMA3],A
LD A,$00
LDH [rHDMA4],A
LD A,$7F
LDH [rHDMA5],A
WAIT:
LDH A,[rHDMA5]
BIT 7,A
JR Z,WAIT |
こちらは GBC から拡張された汎用 DMA 転送を使った転送です。
上の例では $C000 を転送元、$8800 を転送先として、2048バイト 転送します。
一度に転送できる最大が2048バイトで、転送単位が16バイト、さらに転送先が
VRAM 限定という条件はありますが、16バイトの転送に 8us しかかかりません。
5760バイトの転送が 360us で済むことになります。
うまくやれば動画なども出来そうです。
GBLibraryではバッファの方に文字フォントを書き込んでおいて、V-Blank期間に
GDMAで一気にVRAMに転送を行います。バッファへの書き込みの方が圧倒的に遅く
そのままでは書き込んでいる様子がVRAMに出てしまうので、書き込みが終わってから
転送を許可(=V-Blank割り込み許可)するようにしています。
と、ここまではエミュレータを使ってうまく動いていたのですが、いざ実機で
動かしてみると、画面が崩れまくって話になりません。
どうやら、360us で転送完了という数字が間違っている様子。
V-Blank 割り込みの直後に駆動しても、V-Blank 期間中に終わらず、CRTC の
VRAM アクセスとかぶってしまうために画面が崩れてしまうのだと思われます。
GBC の内蔵 RAM からの転送なので、メモリのアクセス速度に問題があるとは思えません。
あるいはフラッシュカートのメモリコントローラーに問題があるのかもしれません。
仕方ないので別の方法を考えます。
GDMA と同じレジスタを使った DMA 転送に HDMA というものがあります。
これは H-Blank期間にのみ転送を行うモードで、転送を開始すると、自動的に
H-Blankを監視して、CRTC のアクセスとかぶらないように転送してくれます。
これなら原理的に画面が崩れることはありません。
ただし、H-Blank 毎にしか転送しないために安全と引き替えに速度が犠牲になります。
H-Blank毎に16バイト転送なので、5760バイトを転送するためにスキャンライン 360 本分、
GB の1画面がスキャンライン144本なので、2.5画面分で転送完了します。
分かりやすく言うと60/2.5=24FPSということです。
書き換えの様子がギリギリ見えるかどうか、というタイミングですね。
文字フォントが10x10ドットの場合、1タイル8x8ドットのタイルを最大何枚使うか。
答えは横3枚*縦3枚の最大9枚です。上下左右に1ドットずつはみ出した時が最大になります。
160x144のX,Y座標から書き始めの左上のタイル番号を算出したら、文字フォントの
データを横1ライン分読み出し、横3枚分のタイルの位置に合わせてシフト演算を
行います。これを縦10ライン分行って、ようやく一文字表示できます。
タイルに書き込む際は OR演算で書き込まないと、タイルが被っている文字が
部分的に消えてしまうので注意が必要です。
フォントサイズが決まっている以上、文字を書く位置は毎回同じなので、XY座標から
VRAMアドレスと文字フォントのシフト値を事前計算し、テーブル化しておくと楽なのですが、
それだとつまらないので(?)力業で計算しています。
文字フォントは ナガ10 が 10x10、M+ が 10x11 なので、それぞれ 2*10バイト、2*11バイトを
1文字に要します。第一・第二水準漢字6877文字(多分)を GB の ROM に収めるには
ナガ10で 137,540バイト、M+ で151,294バイト必要ということになります。
GBのROMバンクメモリ1つが16,384バイト(16KB)なので9〜10バンクを漢字ROMとして使用する
ことになります。加えて半角フォントに1バンクを使います。
さらに、どの漢字がどの ROM に収まっているかを索引するテーブルを3バンク使用して、
格納バンク、アドレス下位、アドレス上位を収めておくことにしました。
このテーブルは Shift-JIS コードから簡単な計算でアドレスを求めることが出来ます。
文字コードが1バイトコード(半角)か2バイトコード(Shift-JIS全角)かを調べるには
以下の方法で1バイト目をテストします。
if ((Value1 ^ 0x20) - 0xa1 < 0x3c) IS_Shift_JIS
|
1バイト目と0x20で xor を取り、0xa1 を引いたものが 0x3c 以下ならShift-JISコードです。
割とポピュラーな方法のようですね。筆者は知りませんでした。
テキストはそのままROMに埋め込んでいます。(なのでシフトJISエンコードでないとダメ)
コンバーターが何をしているかというと、タイトルとコメントを文書毎につけるだけでなく、
文書の開始バンクとアドレスを記録して埋め込みます。
さらに、30x12 の1画面でテキストを表示するシミュレートを行い、毎ページの開始バンクと
アドレスをページインデックスとして記録します。
ページを送るだけなら GB 単体で出来るのですが、戻ったりジャンプしたりしたときに
どのようにレイアウトするかが大変面倒なので、インデックスが必要となるのです。
本当は、インデックス作成処理もGB側で出来れば良いのですが・・・。
というわけで、テキスト格納は以下のような配置になっています。
4000 1byte 全文書の数(0-127)
4001 1byte 空き
(文書情報 72バイト)
4002 2byte 1st文書のインデックス開始バンク
4004 2byte 1st文書のインデックス開始アドレス
4006 2byte 1st文書の終了アドレス + 1
4008 2byte 1st文書の総ページ数 - 1
400A 32byte 1stタイトル
402A 32byte 1stコメント
(文書の数だけ文書情報が続く)
(文書開始アドレス=(文書の数)*72+$4002)
404A 文書本体
(文書は複数を連結して順に格納)
(インデックス)
404B 1byte 1st文書の1ページ目格納バンク
404C 2byte 1st文書の1ページ目格納アドレス
(以下、2ページ目…最終ページ、続けて2nd文書の1ページ目インデックスと続く)
|
該当テキストの該当ページの検索は
1)該当テキストの文書番号から文書情報開始アドレスを計算
2)文書情報からインデックス開始バンクとアドレスを取り出す
3)インデックス開始アドレス+ページ*3を計算
4)そのアドレスから該当ページのバンクとアドレスを取り出す
5)バンクを切り替え、アドレスから文書本体を読み出す
という手順が必要になります。けっこう面倒です。