バッファに新しい行の文字列画像が出来たら、後はそれを画面にコピーするだけです。ただ、そのコピーの前に現在表示済みの画像も1ラインコピーして上げる処理が必要です。コピー元がバッファか画面上かの違いだけで、やってることは同じです。そのため、ここを如何に高速に処理するかが重要になります。今回は PC-8001mk2 に特化した説明になりますのでご了承ください。
コピー元となる新しい行の表示に関しては以下をご確認ください。
- 1ラインコピー
コピーのペースとなるラインコピーを作ります。細かい判定を入れると逆に遅くなる可能性がありますので、今回は端数の長さは余分のコピーして対応する考え方とします。Z80 で最も高速に動作する命令がスタック操作です。コピー元から POP してデータを受け取り、コピー先に PUSH してデータを書き込みます。
PC-8001mk2 の GVRAMアクセスは、走査線が表に出ている時は CPU が強制停止するという特性があります。垂直ブランキングの時間はそれなりにありますが、水平ブランキングの時間はとても短いです。正確なサイクル数は正直不明ですが、実験により 44clk は間違いなく安全圏で、45clk から怪しくて、48clk だと間違いなく CPU が待たされてました。そのため、44clk に収めたいのです。
調べるとちょうど PUSH が 11clk でした。つまり最大で 4回描画で実行することが出来ます。画面情報の読み込みは停止しない…という前提で組み立ててます。実は止まってるかもしれません。この辺りは本当に分からない世界です。メモリウエイトを加味すると PUSH は 3回までという考え方もできます。この辺りは厳密な判定方法がないので正直不明なままです…
さて、ラインコピーをどのように実装したかのプログラムを紹介します。
HL アドレスから DE アドレスに Acc 回数コピーを実行します。コピーの単位は 8バイトです。そのため、最も効率の悪い転送数は 9 とか 17 のような 8 の倍数に +1 した数となります。8バイトを転送するために、全てのレジスタはフラグも含めて全て使い切ります。そのため、プログラム書き換え前提の処理系となっています。
POP はデータの受け取り(.get)、PUSH はデータの書き出し(.put)となります。PUSH は格納アドレスがデクリメントされますから、事前に +8 しています。最初に POP を 4回実行して全てのレジスタに値を取り込みます。そして、取り出し先アドレスである SP の値はプログラム書き換えで退避します。続いて SP に書き込み先のアドレスを設定して、一気に PUSH で書き換えています。ストアとロードしか実行していませんので、フラグは壊されず、POP して PUSH が出来るようになっています。
- 1ラインスクロール
次に画面全体をスクロールアップする処理を実装します。
PosTbl には各ライン毎のコピーサイズと左端の座標が格納されています。それを最初に上側に書き換えます。LDIR 使えばお手軽だとは思うのですが、今回はここも先に作成した1ラインコピーを呼び出してコピーしています。LDIR より高速に動作するためです。実は最初にこのテーブル参照はリングバッファにしていたのですが、全部で199回も毎回リングからはみ出したかどうかの判定をしてて、結局遅いと思ったので最初にまとめてずらすことにしました。バグが出にくいというメリットもありましたので…
※ 作成中バグが出まくってて嫌になったという噂もあります^^;
※1人分で気軽にお湯を使いたい時に便利。一気に電気を使うので、タコ足配線だけは厳禁。
続いて画面の内容コピーです。HL にライン情報テーブルアドレスが、DE にコピー先アドレスが、そして IYH にコピーカウンタが格納されています。最初にコピーカウント数を取り出します。ゼロならコピーの必要がないので次のラインに処理を移行します。コピーが確定したら、コピー回数は重要なので一旦裏に退避します。続いて左端座標をテーブルから取り出します。これをコピー先アドレスに加算して、差分のアドレスを作ります。このアドレスに VRAM の横幅を加算するとコピー元になります。この情報を元に1ラインコピーを呼び出すと、その行がスクロールアップするわけです。
続いて画面の内容コピーです。HL にライン情報テーブルアドレスが、DE にコピー先アドレスが、そして IYH にコピーカウンタが格納されています。最初にコピーカウント数を取り出します。ゼロならコピーの必要がないので次のラインに処理を移行します。コピーが確定したら、コピー回数は重要なので一旦裏に退避します。続いて左端座標をテーブルから取り出します。これをコピー先アドレスに加算して、差分のアドレスを作ります。このアドレスに VRAM の横幅を加算するとコピー元になります。この情報を元に1ラインコピーを呼び出すと、その行がスクロールアップするわけです。
あとは、行の次に移動させてから、行カウンタを減算させて、全ての行をコピーすると、画面全体が上に1ドットズレます。
- 新しい文字をコピーする
ここの処理はプログラム書き換えにより、常時コードが書き換わっています。まず、.value とあるのが、新しい行の左端座標とコピー範囲を示す情報となります。それを新しい行のコピー毎に、ライン情報の最下行に書き込んでいます。コピー回数がゼロというのは改行だけとなります。その場所は画面の更新を行う必要はありませんから、すぐに .skip に移動しています。
.src はコピー元アドレスです。ダブルバッファですので、どちらからコピーするのかが毎回変わりますし、1ラインコピーする毎に処理は一旦メインに戻りますから、次に呼び出されたときに続きからコピーできるように、この .src は毎回書き換えられています。.dst はコピー先です。コピー先は画面最下行となりますが、文字列の長さが変わる時には更新されますが、行全てが書き換わるまでは値は変化しません。
これで、コピー元アドレスとコピー先アドレス、そしてコピー回数が揃いましたので、先に作成した1ラインコピーを呼び出しています。最後にコピー完了の確認をします。1行は縦12ドットとなっています。そのため、ローカルワーク .cnt を +1 してその値が 12(定数定義で Strings.height)より小さければ、コピー継続なので、そのまま処理を終了しています。
- ダブルバッファを切り替える
文字のコピーが全て完了したら、バッファの切り替えと初期化を行います。
せっかく HL にアドレスが残ってるので、最初に行カウンタを 0 に戻します。次に現在のバッファクリアアドレス Erase1Quarter.edit + 1 を参照して、現在設定されているバッファを判定して、切り替え先を HL, DE, BC レジスタに設定しています。HL は新しく文字を描画する先のバッファの最終アドレス、DE は次のコピー元となるバッファのアドレス、BC は文字を描画する先のアドレスです。HL と BC は似たようなアドレスとなりますが、処理を高速化するため、アドレスをずらした後のアドレスも受け渡すようにしています。
まず HL と BC を先に設定します。描画先アドレスは全てプログラム書き換えとしています。これは、毎回ワークからのロードだと遅いため、即値としていたりします。次の DE と HL を入れ替えて、コピー元バッファに左側の座標オフセットを加算して、正しいコピー元アドレスとして .src のプログラム書き換えをします。同じく左側の座標を画面最下位ラインアドレスに加算して、コピー先アドレスを作成して .dst を書き換えます。
最後にコピー回数です。.width には文字列の作成中に追加された文字数が格納されています。1ラインコピーは 8単位なので、その文字数は 1/8 する必要がありますが、端数は桁上りが必要です。そのため、最初に 7 を足してから 1/8 の計算をしています。もし、文字数が 8 できっちり割り切れる数だった場合は、1/8 の過程で +7 増減分は消え去ります。もし、8 で割り切れない場合は、桁上りが発生するので 1/8 すると +1 として残るというわけです。
この計算結果を L、左側の座標が C に残っているので H に格納後、.value に保存(プログラム書き換え)すると、次回からの文字情報テーブルの設定値として使用されます。最後に状態遷移先をまたバッファのクリアに書き換えて処理を終了します。
※ お気に入りはホコリがかからないようにして愛でたいですよねぇ…
Winds&Jungle
※ お気に入りはホコリがかからないようにして愛でたいですよねぇ…
コメント