この記事はMSX Advent Calendar 2015の7日目の記事です。
8bit,16bit CPU時代のゲーム機で流行ったゲームエフェクトにラスタースクロールというものがありました。水平走査線(画面の横1ピクセルライン)毎にスクロール位置を変えることで、画面がうねるような動きを表現します。ハード性能が向上すると、スクロールだけでなく拡大縮小のスケールを変えるなど、より凝った表現が行われるようになりました。(Google画像検索)
ラスタースクロールについては、Wikipediaで非常に詳しく説明されています。
このラスタースクロールをMSXで実装してみました。ラスタースクロールの基本ロジックは、Wikipediaにもある通り次のようになります。
- ビデオチップが1ライン分の描画(VRAM読み込み)を終え、水平帰線期間(水平ブランキング期間)に入る。このとき、水平同期(HSYNC)信号が出される。
- CPUは、HSYNC信号で割り込み発生させる。
- プログラムは、HSYNC割り込み処理で横方向のスクロール位置を設定する(次に描画するラインのスクロール位置) 。
- 1~3の処理をスクロール位置を変えながら全ラインで実行する。
MSX2のビデオチップV9938では、コントロールレジスタ#19にHSYNC割り込みを発生させる走査線番号を指定することができます。これを使えばラスタースクロールを実現できそうですが、残念ながらこの方法では上手く行きません。
CPU(Z80)がHSYNC割り込みを受けると、アドレス038Hに実行場所が移ります。 ここから、MSXのシステムプログラムが実行されるため、ユーザプログラムに制御が移るまでにかなりの時間を要してしまいます。ユーザプログラムに制御が移るころには数ラインの描画が進行してしまい、スクロール位置の変更が間に合わない状況となります。
step3でスクロール位置設定を行った後に次の割り込み位置をR#19へ設定しますが、上述の状況ですので1ラインごとに割り込み処理を行わせることができません。こちらで試した結果では6ライン程の間隔で設定すれば動作させることができました。
1ラインのHSYNC毎にスクロール位置を変えるには、HSYNCを自前で検出する方法があります。水平帰線期間中は、V9938のステータスレジスタ#2のbit 5が1になります。このbitを監視して、HSYNCを検出することができます。(ただし、CPUを占有します)
これを実装したプログラムは次のようです。
TITLE Raster Scroll .Z80 ASEG RG0SAV EQU 0F3DFH RG18SA EQU 0FFF1H RG19SA EQU 0FFF2H RG25SA EQU 0FFFAH VDPIO EQU 099H HKEYI EQU 0FD9AH ORG 09200H START: ; Set IE1(Interrupt Enble1) DI LD A,(RG0SAV) OR 010H LD (RG0SAV), A OUT (VDPIO),A LD A,080H OUT (VDPIO),A ; Set R19(Interrupt line register) LD A,0 LD (RG19SA),A OUT (VDPIO),A LD A,093H OUT (VDPIO),A ; Set R25 LD A,(RG25SA) OR 03H LD (RG25SA),A OUT (VDPIO),A LD A,099H OUT (VDPIO),A ; Set H.KEYI LD HL,HKEYI LD DE,HKEYISV LD BC,5 LDIR LD HL,HKEYISET LD DE,HKEYI LD BC,5 LDIR EI RET ONKEYI: DI LD C,VDPIO LD H,093H LD A,(L0OFFSET) INC A LD (L0OFFSET),A LD L,A LD B,0 ; Read S1 LD A,01H OUT (C),A LD A,08FH OUT (C),A IN A,(C) ; Check FH Flag RRCA JR NC,ENDKEYI ; Read S2 LD A,02H OUT (C),A LD A,08FH OUT (C),A ; Wait HSYNC(HR:1->0->1) L1: IN A,(C) AND 020H JR NZ, L1 L2: IN A,(C) AND 020H JR Z, L2 ; Set Scroll => R18(Display adjust register) LD A,1 ADD A,L AND 03FH LD L,A LD A,(HL) OUT (VDPIO),A LD A,092H OUT (VDPIO),A ; INC B LD A, 211 CP B JR C, ENDKEYI JR L1 ENDKEYI: XOR A OUT (C),A LD A,08FH OUT (C),A HKEYISV: DS 5 HKEYISET: JP ONKEYI DS 2 L0OFFSET: DB 0FFH ORG 09300H HSCROLL: DB 00H,01H,01H,02H,03H,03H,04H,04H,05H,05H,06H,06H,06H,07H,07H,07H DB 07H,07H,07H,07H,06H,06H,06H,05H,05H,04H,04H,03H,03H,02H,01H,01H DB 00H,0FH,0FH,0EH,0DH,0DH,0CH,0CH,0BH,0BH,0AH,0AH,0AH,09H,09H,09H DB 09H,09H,09H,09H,0AH,0AH,0AH,0BH,0BH,0CH,0CH,0DH,0DH,0EH,0FH,0FH END START
横方向のスクロール位置は、画面のAdjust機能(テレビ出力用に画面の位置を-8~+7の範囲で調整する機能)を用いています。 R#18に調整値を設定します。
実行結果は、次のようです。
画面の上部がズレているのは次の理由からです。Line 0のHSYNC割り込みを契機にラスタースクロールの処理を開始していますが、実際に処理が開始されるまで数ライン分の遅延があるため、ラスタースクロールの開始ラインが下にズレてしまったと考えられます。上部の数ラインは、一つ前のフレームの設定が反映されています。
ラスタースクロール開始の契機をVSYNC(垂直帰線期間)にすれば、このズレは抑えられると思います。プログラムを試行錯誤で作った名残&時間切れで、このような状態になっています。
MSX2+からは、ビデオチップ(V9958)で横方向スクロールをサポートしました。 ライン毎のスクロール位置指定に、このハードウェアスクロールを使ってみました。
ハードウェアスクロールは、R#26に8ピクセル単位のスクロール位置、R#27に1ピクセル単位のスクロール位置(0~7)を指定します。 残念ながらR#27を指定すると、画面が乱れてしまいました。これは、今回使ったエミュレータの問題か、V9958の性能面での問題かは分かっていません。
R#26のみ設定した場合は、期待通りに動作しました。実行画面は次のようになります。