見出し画像

VDPを直接操作してみる

いままでの記事では、BIOSを使ってVRAMを操作していました。
だいぶ前の記事で「VRAMの操作は重くなる」というようなことを説明していましたが、画面表示を高速に行うためにBIOSを使わずにVRAMを操作することがMSXでは許可されています。

VDPポートについて

VDPを扱う場合に必要なものはVDPポート番号です。
VDPポートには次のようなものがあります。

表中のnの値は MAIN-ROMの0006H番地に、n'の値は0007H番地にそれぞれ入ってます。これは拡張性のためにそうしてあるだけで通常はどちらも98Hが入ってます。WebMSXでプログラミングするのであればどちらも98Hだと思って間違いないです。そのためポート番号は以下のようになります。

ポート#0 98H
ポート#1 99H
ポート#2 9AH
ポート#3 9BH

VRAMに書き込みを行う場合は、HLレジスタに書き込みたいVRAMアドレスをセットして、ポート#1に対して「これからHLレジスタにセットしてあるVRAMに書き込みを行うよー」と宣言してから、ポート#0に対して書き込みたい値を放り投げていきます。書き込むたびにVRAMアドレスは1ずつ自動的にインクリメントされていきます。

OUT命令

Z80ASMでポートを操作する場合は、OUT命令を使います。出力する場合、そのデータは必ずAレジスタ(アキュムレータ)でなければなりません。

; ポート番号#1に対して、VRAMの1800Hに書き込みを行うよ!と宣言するコード

LD HL, 1800H
LD A, L       ; VRAMアドレスの下位8ビットを出力する
OUT (99H), A
NOP
NOP
NOP
LD A, H       ; VRAMアドレスの上位8ビットを出力する
OR 40H        ; 上位8ビットのうち第6ビットを1にするとWRITEモードになる(重要)
OUT (99H), A
NOP
NOP
NOP
 
; OUT (C), Aという命令を使うこともできる
; Cレジスタにはポート番号をセットする

LD A, 99H
LD C, A
LD HL, 1800H
LD A, L       ; VRAMアドレスの下位8ビットを出力する
OUT (C), A    ; Aレジスタの値をCレジスタに格納されているポート番号に出力する
NOP
NOP
NOP
LD A, H       ; VRAMアドレスの上位8ビットを出力する
OR 40H        ; 上位8ビットのうち第6ビットを1にするとWRITEモードになる(重要)
OUT (C), A    ; Aレジスタの値をCレジスタに格納されているポート番号に出力する
NOP
NOP
NOP
 
; ポート番号#0に対して、値を書き込むコード(事前に書込み宣言をしておく必要がある)
LD A, $40
OUT (98H), A
NOP
NOP
NOP

; ポート番号#0に対して、値を書き込むコード(事前に書込み宣言をしておく必要がある)
; OUT (C), Aを使う場合
LD A, 98H
LD C, A
LD A, $40
OUT (C), A
NOP
NOP
NOP
 
; ポート番号#0に対して、値を書き込むコード(事前に書込み宣言をしておく必要がある)
; OUTIを使う場合
; OUTIはHLレジスタに格納されているアドレスの値を順に出力する
; 出力の都度HLレジスタはインクリメントされる
; Bレジスタに格納されている値は出力ごとにデクリメントされ
; 0になるとZフラグがたつ

; HLレジスタに格納されているアドレスから10バイト分を出力する例
LD A, 98H
LD C, A
LD  B, 10
LD HL, WK_STRINGBUFFER

LOOP:
OUTI  ; HLレジスタに格納されているアドレスの値を出力し、HLをインクリメント
NOP
NOP
JP NZ, LOOP

からぶり命令(NOP)とクロック数

NOPはなにもしない「からぶり命令」です。でも、からぶりといっても4クロック数を消費します。生きてる以上は脈をうってるみたいなイメージですね。OUTを行ったあとは最低でも21クロックを待たないとVDPが処理をとりこぼすらしく、からぶりでクロック数をかせいでる。というイメージになります。

OUT (n), A は11クロックなのでNOPを3回続けることで23クロックの間をあけています。
OUT (C), A は12クロックなのでNOPを3回続けることで24クロックの間をあけています。
OUTIは16クロックなのでNOPを2回続けることで24クロックの間をあけています。

「BIOSを使ったVRAMの処理はNOPが大量にあるため遅いらしい」という話です。

使い方はわかったけど・・どういう時に使うの??

OUTを使ってVDPを操作することで高速にVRAMを操作することができるようになります。MSX1で高速スクロールとか実現できるみたいです。

また、VDP使うと高速なのでスプライトの表示制限である並行に5枚以上スプライトが並ぶと表示順位の低い(=スプライト番号の大きい)5枚目以降が消えてしまう仕様を(なんちゃって)な感じで突破できるようになります。
高速にスプライトの表示順位を入れ替えて(見た目はちらつくけど)完全にに消えて見えなくなるよりかはマシにする。
という手法です。とりあえず、どんな見た目になるかだけはTwitterにあげときます。チラつきますのでポケモンパニックにご注意ください。

この例では、横に平行に8枚のスプライトを表示しています。
スプライト2枚で1体を表現しているため
通常であれば横から3体目以降が消えて見えなくなりますが、完全に消えないように表示順を入れ替えて表示しています。
動画の途中でPAUSEをかけると横に4枚しかスプライトが表示されていないのがわかると思います。
1/60秒ごとで表示順を入れ替えているので人間の見た目としては消えていない。という錯覚技法となっています。
この表示ロジックは少年時代から「どうやって実現してるんだろう?」とすごく気になっていたので、今回、自分で作れることができてすがすがしい気持ちです(気分はサイコー!)

サンプルコードはGitHubに置いてます。
https://github.com/sailorman-msx/games/tree/main/src/sample016

今回はKONAMI方式(素数方式)と呼ばれる手法で、1/60秒ごとにスプライト番号#0-#31までを素数を使った計算でシャッフルして表示し、(ちらつくけど)消えるよりはマシ。ということを実現しています。シャッフルの方法については、HRA!さんの文書を参考にさせていただきました。感謝、感謝でございます。

いやあ、MSXって奥が深いですね!
次回は、今回のシャッフル処理について説明します。

では、また!ノシ




セーラー服が似合うおじさんです。猫好き、酒好き、ガジェット好き、楽しいことならなんでも好き。そんな「好き」をつらつらと書き留めていきます。