Gigamix DM-SYSTEM2

VDPマクロ機能

updated:

第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マクロエディタ ver.1.01
Download VMED101.LZH (9KB)

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):'マクロ実行
MSXのBASIC画面がガクガクして止まらないDMシステム2のVDPマクロ https://gigamix.jp/ds2/ds2vm.html — Takashi Kobayashi (@nf_ban) 2021年08月13日
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マクロでは特定のビットをマスク(保護)して数値を書き込むことはできません。事前にビット計算して「自己書き換え」のような方法で解決するか、はなから無視するくらいしか方法が無いでしょう。

 以上の理由から、モードレジスタへアクセスした際は、できるだけ元の状態に戻すように努力してください。

[Back]トップページへ