第1章 概要
VDPマクロとは
VDPマクロは1/60秒のタイマー割り込みを利用してVDPコマンドを発行する、簡易マクロ言語です。画面のフェードイン・アウトやスクロールなどの効果を、BASICのプログラムと並列実行することができます。
可能なことと不可能なこと
VDPレジスタを操作してできることなら大抵のことは可能です。
- パレットの変更
- MSX2の縦スクロール、MSX2+の横スクロール
- VRAM構成の変更
上記はほんの一例に過ぎませんが、これらの操作がすべてバックグランド処理として実行される点に注目してください。VDPマクロデータを用意しておけばあとはBASIC上から簡単な命令を一度実行するだけでダイナミックな演出が実現できます。
ただしVRAMへの書き込みはできません。MSXには「割り込み中にVRAMのアドレスカウンタを変更してはいけないという暗黙のルールがあるため、はじめからVDPマクロにはこの機能が実装されていません。これはVDPマクロがスプライトの移動を行えないことを意味します。
もう一つVDPを語る上で避けて通れないのがVDPコマンド群です。おもにビットマップモードと呼ばれるSCREEN5以上の画面モードで領域のコピーや線分の描画などを行いますが、VDPマクロからの使用についてはあまりお勧めできません。VDPコマンドを発行するには現在実行中のVDPコマンドが終了するのを待たなければなりませんが、VDPマクロにそうした機能はありません。この危険性を十分に承知した上であえて使用する以外は手を出さないほうが無難です。
ご注意
VDPコマンドというものは非常にデリケートで、一定の手順を踏まないと絶対に実行されません。一般の処理でVDPコマンドを発行していた場合、タイマー割り込み中に新たなVDPコマンドを発行すると本来の処理が実行できなくなることがあったり、ましてやVDPのステータスレジスタの読み出し中に割り込みがかかった状態で別のVDPコマンドを発行したりすると即暴走につながります。よってタイマー割り込みでVDPコマンドを発行しないのが無難とされています。
しかしここで重要なのは、誤動作に繋がる状況とは、基本的にVDPコマンドの手順が狂った場合です。
- ステータスレジスタの読み出し
- LINEやBOXなど、描画命令
- COPYなどのブロック転送
このような類のVDPコマンドはVDPの「コマンドレジスタ(R#32~R#46)」を用いてVDPへアクセスしますが、レジスタを常にインクリメントしながら連続してデータを書き込まなければならないので、タイマー割り込みで別のコマンドを発行するとそれまでのコマンドの手順が狂い、本来の処理が実行できなくなる可能性が非常に高くなります。
第2章 DMシステム2からの利用方法
VDPマクロデータを配置する
VDPマクロのデータを任意のメモリ(RAM)へ配置します。CALL LOAD などでファイルから転送したり、プログラムでデータを作成するのも良いでしょう。
CALL LOAD ("ファイルネーム") ← ファイルの読み込み
BLOAD "ファイルネーム" ← BSAVE形式ファイル
ex.)
_LOAD("sample.bin",&H3000) ← 3000hへロード
BLOAD "sample.bin" ← BSAVE形式ファイルをロード
マクロデータを配置するエリアはあらかじめ“CLEAR”文で宣言しておく必要があります。
ex.)
CLEAR 200,&HC000 ← 0C000h以降をBASICが使わない
VDPマクロのデータの作成方法については、「第3章 VDPマクロを組む」をご覧ください。
パレットテーブルの設定
VDPマクロでパレットを変更するには、パレット機能と同じく、RAM上に配置したパレットテーブルの先頭アドレスを宣言する必要があります。パレット機能を利用しない場合は省略できます。
CALL SETPLT (address)
ex.)
_SETPLT(&HD000) ← D000hを宣言
VDPマクロの実行
VDPマクロを実行します。
CALL VMON (address)
VDPマクロはハードウェアを直接操作できるマクロ言語ですので、ちょっとしたデータミスでも暴走の危険性があります。必要なデータはあらかじめディスクに保存してからVDPマクロを実行するように心がけてください。
ex.)
_VMON(&HC800) ← C800hから始まるVDPマクロを実行
VDPマクロの強制終了
VDPマクロの実行を強制的に終了させます。
CALL VMOFF
CALL SYSON を実行した際にも、VDPマクロは強制終了します。
VDPマクロの終了待ち
VDPマクロが内部的に終了したかチェックします。VDPマクロを利用した的確な時間待ちに便利です。
CALL VMWAIT
第3章 VDPマクロを組む
仕様
VDPマクロは1バイト(0~255)の数値にそれぞれ意味があり、内容は次の通りです。
VDPマクロ コード一覧
オフセット |
内容 |
nの最大値 |
00H+n |
パレット変更 ※_CHGPLT(n)相当 |
0~63 |
40H+n, <data> |
VDP(n) に <data> を書き込む |
0~46 |
6FH, <data> |
マクロ変数に <data> を代入する |
70H+n |
ループ指定 |
0~14 0:無限ループ |
7FH |
ループ終了 |
80H+n |
パレット変更・一気モード ※_CHGPLT(128+n)相当 |
0~63 |
0C0H, <data> |
<data>/60秒のウェイト ※_WAIT(<data>)相当 |
0C1H+n |
n/60秒のウェイト ※_WAIT(n)相当 |
0~59 |
0FDH, <下位>,<上位> |
<address> にあるVDPマクロをスレッド番号2で実行 |
0FEH, 00H |
パレット変更 ※_CHGPLT(マクロ変数)と同等 |
0FEH, 40H+n, <data> |
VDP(n)に <data> を加算する(<data> が0のときはマクロ変数の値を加算する) |
0~27 |
0FEH, 6FH, <data> |
マクロ変数に <data> を加算する |
0FEH, 80H |
パレット変更・一気モード ※_CHGPLT(128+マクロ変数)と同等 |
0FFH |
VDPマクロの終了 |
書式は基本的に一つ以上の「コマンド」と「ウェイト」の繰り返しです。「ウェイト」が指定された時点でそのタイマー割り込みでのコマンド発行を終了し、通常の処理へ復帰します。
パレット関連については、DMシステム2のパレット機能に準じています。CALL CHGPLT の「通常モード」と「一気モード」の数値を、そのままVDPマクロで割り当ててあります。
マクロデータを作成する
ここではさらに分かりやすく、マシン語のアセンブルリストを使って機能の紹介をします。
主なラベル
CHGPLT EQU 0 ;+n(0~63, 128~191) パレット
VDP EQU 40h ;+n(0~46), xx VDPレジスタ
LOOP EQU 70h ;+n(0~14) ループ開始
LOOPED EQU 7Fh ; ループ終了
WAIT EQU 0C0h ;,xx ウェイト
WAIT1 EQU 0C1h ;+n (0~59) ウェイト
NEWMCR EQU 0FDh ;0FDh,xx,xx 新規マクロ
EXTCMD EQU 0FEh ;0FEh,xx,xx 機能拡張
MCREND EQU 0FFh
アセンブル作業がよく分からないという方は、VDPマクロエディタなどをご利用ください。
VDPレジスタを変化させる
それでは、実際にVDPマクロを組んでみましょう。まずは簡単なところから、画面を意味も無く広げたり狭めたりしてみます。
VDPのモードレジスタ3(R#9)のbit7には、画面表示サイズを設定するフラグがあります。縦192ライン(0)にするか、縦212ライン(1)にするかで、画面表示が変化します。
モードレジスタ3(R#9)
b7 b6 b5 b4 b3 b2 b1 b0
LN 0 S1 S0 IL EO NT DC
~~ ~~ ~~ ~~ ~~ ~~ ~~ bit0 DLCLK端子 (0:出力 1:入力)
| | | | | +--- bit1 画面周波数 (0:NTSC 1:PAL)
| | | | +------ bit2 Even field/Old field (0:交互 1:同じ絵)
| | | +--------- bit3 Interlace (0:off 1:on)
| | +------------ bit4 同期モード選択
| +--------------- bit5 同期モード選択
|
+--------------------- bit7 画面表示 (0:192ライン 1:212ライン)
VDPマクロをアセンブルリストで表記すると、こうなります。
DEFB LOOP+0 ;永久ループを指示
DEFB VDP+9, 0 ;R#9 に 0を書く → 192ラインモード
DEFB WAIT1+5 ;6/60秒の時間稼ぎ
DEFB VDP+9, 128 ;R#9 に 128を書く → 212ラインモード
DEFB WAIT1+5 ;6/60秒の時間稼ぎ
DEFB LOOPED ;ループ終了
それでは、簡単なBASICプログラムを組んでみましょう。
100 CALL SYSON:CLEAR 200,&HD000
110 '
120 DATA 70 :'LOOP+0
130 DATA 49,00 :'VDP+9, 0
140 DATA C6 :'WAIT1+5
150 DATA 49,80 :'VDP+9, 128
160 DATA C6 :'WAIT1+5
170 DATA 7F :'LOOPED
180 '
190 FOR I=0 TO 7:READ A$:POKE &HD000+I,VAL("&H"+A$):NEXT
200 CALL VMON(&HD000):'マクロ実行
VDPマクロの実行結果
画面が上下に揺れていますか?その状態でカーソルが動くのはもちろん、“LIST”も“FILES”も実行できます。画面が揺れている以外は普通の状態と一緒です。その状態でスクリーンモードをいじってみたり、リスト中のウェイトを変えてみたりすると面白いと思います。
このVDPマクロは永久ループです。マクロを止めるには CALL VMOFF または CALL SYSON を実行してください。
パレット操作
パレットのフェードインをVDPマクロで自動運転するマクロを紹介します。パレット操作に関してはDMシステム2がパレット専用のルーチンを用意していますので、これをVDPマクロで利用します。
DEFB CHGPLT+128+0 ;パレット#0にしておく
DEFB LOOP+7 ;7回ループ
DEFB CHGPLT+1 ;パレット#1へRGB各輝度を1段階ずつ変化
DEFB WAIT1+9 ;10/60秒の時間稼ぎ
DEFB LOOPED ;ループ終了
DEFB MCREND ;VDPマクロ終了
この場合、すべてのコマンドが1バイトなので、計6バイトでフェードインの自動運転が実現することになります。
マクロに「ウェイト」が無いと、一瞬のうちにパレットが7段階変化してしまい、フェードインの効果が出ませんので、コマンドの最後に必ず「ウェイト」を通るようにします。
こういった「永久ループしない処理」の場合、マクロ終了コードを付けないとループ後のメモリの内容をそのままVDPマクロと解釈してしまい勝手な数値が次々とVDPへ書き込まれてしまいます。画面が正常を保てないばかりか、場合によっては暴走の危険性もあります。VDPマクロの終了コードも必ず付けてください。
BASICのプログラムでは、こんな感じで組むと良いのではないでしょうか。
100 CALL SYSON:CLEAR 200,&HD000
110 CALL LOAD("A.PL5",&HD100):CALL SETPLT(&HD100) :'loading palette table at 0D100h
120 '
130 DATA 80 :'CHGPLT+128+0
140 DATA 77 :'LOOP+7
150 DATA 01 :'CHGPLT+1
160 DATA CB :'WAIT1+9
170 DATA 7F :'LOOPED
180 DATA FF :'MCREND
190 '
200 FOR I=0 TO 5:READ A$:POKE &HD000+I,VAL("&H"+A$):NEXT
210 CALL VMON(&HD000):'マクロ実行
マクロ変数
VDPマクロはスレッド毎にひとつの変数を持つことができます。このマクロ変数を使いこなすと、より汎用的で使いやすいVDPマクロを記述できるようになります。
「パレット操作」で作成したフェードインマクロをマクロ変数を使って書き直してみます。
DEFB LOOP+7 ;7回ループ
DEFB EXTCMD, CHGPLT ;パレット#<マクロ変数> へRGB各輝度を1段階ずつ変化
DEFB WAIT1+9 ;10/60 秒の時間稼ぎ
DEFB LOOPED ;ループ終了
DEFB MCREND ;VDPマクロ終了
このVDPマクロを実行するには、マクロ変数の初期値として目的のパレット番号を渡します。C000hに配置したときの使用例は次のようになります。同じVDPマクロがマクロ変数の値を変えるだけで異なった動作をするところがポイントです。
ex.)
_VMON(&HC000,1) ← パレット#1へフェードイン
_VMON(&HC000,0) ← フェードアウト
_VMON(&HC000,2) ← パレット#2へフェードイン
マクロ変数に代入する
VDPマクロ用の変数に値を代入してVDPマクロを実行します。
CALL VMON(実行開始アドレス, 数値)
VDPレジスタを相対的に変化させる
VDPレジスタの現在値に対して好きな値を足したり引いたりすることができます。たとえばハードウェア縦スクロールの例を考えてみましょう。「VDPレジスタを変化させる」で紹介した方法だと、10ドットスクロールさせるためには似たようなコマンドを10個並べなくてはなりませんでした。
DEFB VDP+23, 0 ;R#23に0を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 1 ;R#23に1を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 2 ;R#23に2を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 3 ;R#23に3を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 4 ;R#23に4を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 5 ;R#23に5を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 6 ;R#23に6を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 7 ;R#23に7を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 8 ;R#23に8を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB VDP+23, 9 ;R#23に9を書く
DEFB WAIT1+0 ;1/60秒の時間稼ぎ
DEFB MCREND ;VDPマクロ終了
VDPレジスタの値に加算ができればこんなに短いVDPマクロで済みます。
DEFB VDP+23, 0 ;R#23に0を書く
DEFB LOOP+9 ;9回ループ
DEFB WAIT1+0 ;ウェイト 1/60秒
DEFB EXTCMD, VDP+23, 1 ;R#23に1を加算する
DEFB LOOPED ;ループ始点へジャンプ
DEFB MEREND ;VDPマクロ終了
とくにサンプルは示しませんが、さらに改良すればマクロ変数を組み合わせてスクロール幅を可変にしたりもできます。応用次第で可能性はいくらでも広がるでしょう。
ちょっとマニアックな活用方法
同時に2つのマクロを実行する
VDPマクロはマクロ内でもう一つ新たなマクロを起動することで、2つのマクロを並列実行することができます。パレットアニメーションしながらスクロールを同時に行うなどといったことが実現可能です。
最初に実行したマクロはスレッド番号0、新たなマクロはスレッド番号1となります。2つのマクロを並列実行している状態で、どちらか一方のマクロだけを終了させるには、以下の命令を実行します。
CALL VMOFF(終了するマクロのスレッド番号)
ON STRIG GOSUB 命令と併用する
DMシステム2の CALL DMMON を実行すると、デバイス入力機能でトリガ入力を検知した瞬間、特定のVDPマクロを実行することができます。
CALL DMMON(<VDPマクロの開始アドレス>)
だったら、こんな命令を実行しないまでも “ON STRIG GOSUB”のGOSUB先で新しいVDPマクロを実行すればいいじゃん、と疑問を持つかもしれません。
しかし“CALL DMMON”によるキー検知は、BASIC内の計算中やマシン語ルーチン内でも有効です。例えば、巨大なBPE圧縮データをメモリへ展開している間にトリガ入力があると、BPE展開しながら新しいVDPマクロを実行できる、というわけです。
その際、既にマクロが実行されていると、それまでのマクロを強制中断して新たなマクロを実行します。
100 CLEAR 200,&HC000 :'VDPマクロ領域を確保
110 _LOAD("macro.bin",&HC000) :'マクロデータをロード
120 _DMMINI :'デバイス入力初期化
130 _DMMON(&HC000) :'トリガ検知後 C000h から実行
140 ON STRIG GOSUB 200:STRIG(0) ON :'「仕掛け」を作る
150 '処理例
160 _EXT(@65536!,@0) :'BPE展開中でもトリガ検知が可能
200 STRIG(0) OFF:_DMMOFF :'トリガ検知を無効にする
210 'トリガ入力後の処理
デバイス入力機能 の「ON STRIG GOSUB命令との連動」も合わせてご覧ください。
第5章 補足
画面が崩れて困ったとき
VDPマクロによってVDPへ与えた数値が不正な場合、画面がぐちゃぐちゃになって見えなくなることがあります。最悪の場合はマシンが暴走してしまい、リセット以外に方法が無いかもしれません。
もしPAUSEキーやCAPSキーを押してみてまだ反応があるようなら、あわてず次の手順で画面を復帰させてください。
- CALL SYSON でDMシステム2を初期化する
- “SCREEN 0”など、SCREEN命令を実行する
VDP操作による画面環境の変化について
VDPのレジスタへ数値を直接代入することにより、それまで続いていたMSXの環境を変化させることがあります。特にモードレジスタ(R#1、R#8、R#9)へ数値を代入することで、思わぬ変化を起こします。
DMシステム2のインフォメーションエリアに存在する INTERL (4300h+10) は、インターレースのON・OFFを設定するエリアで、DMシステム2の漢字表示機能などで利用しています。しかしVDPのモードレジスタ3(R#9)に何かしらの数値を書き込むことで、それまでのインターレース環境が不定(ほとんどの場合OFFにセット)になります。
DMシステム2では何かしらの漢字表示を行うと、インターレース環境が復帰します。
残念ながらVDPマクロでは特定のビットをマスク(保護)して数値を書き込むことはできません。事前にビット計算して「自己書き換え」のような方法で解決するか、はなから無視するくらいしか方法が無いでしょう。
以上の理由から、モードレジスタへアクセスした際は、できるだけ元の状態に戻すように努力してください。