|
厚顔にも絵夢絶党で新日本電気製品の講座を始める私をお許しください。掟破りであることは重々存じております。しかしこれも新時代への布石、「花の色はうつりにけりないたずらに…………」と小野小町も詠んでおります。
このところストロボ・浦城氏に日曜日ごとに発破をかけられ、MS-DOSのマクロアッセンブラ(MASM)をほぼ使えるようになりました。片足をどぶに突っ込んだ心境の今日このごろです。この講座は、私の開き直りと受け取っていただいて結構です。
第1話:GRCGの悪用(1)
PC-9801シリーズのグラフィック制御は、GDC(μPD7220)とCPU(i8086 or V30 or 80286)の両方から(同時には無理ですが)アクセスできることは周知の通りです。
さて、ここでVM以降の機種では、GRCG(グラフィック・チャージャー)と呼ばれるグラフィック画面に関する特殊なハードウェアが追加されています。このGRCGは3つの機能を有しており、それぞれTDW(Tile Direct Write)モード、TCR(Tile Compare Read)モード、RMW(Read Modify Write)モードと名付けられています。今回、この中のTDWモードを使用した超高速タイルパターン塗りつぶしルーチンを紹介します。(今のところどういうわけかGRCGを使用したユーティリティーを見たことがないので………)
TDWモードというのは、予め専用のタイルレジスタにR,G,B,Iのタイルパターン(8bit)を設定しておき、CPUがグラフィックVRAMをアクセスしたとき、CPUがライトしたデータを無視して、予め設定しておいたタイルパターンを4画面同時に書き込むという機能を持ったモードです。当然CPUは単色画面1枚分のアクセス(640×400で32KB)をするだけで済みます。この機能を使えば、容易に理論上従来の4倍の高速化が可能になることがわかると思います。要するにTDWモードというのは、1画面分のアクセスで4画面(輝度画面[インテンシティー]がないときは3画面)分がアクセスできるんだと考えて差し支えありません。
|
GRCGの制御ポートを示します。
レジスタ名 |
PORT |
|
モードレジスタ |
7CH |
C M 0 0 I G R B |
タイルレジスタ |
7EH |
タイルパターン |
|
|
|
|
M:“0”でTDW、“1”でRMWモード。
I:“1”でIプレーンが無効。
G:“1”でGプレーンが無効。
R:“1”でRプレーンが無効。
B:“1”でBプレーンが無効。
|
|
|
TDWモードで使用するには、I/Oポートの7CHに80Hを出力したのちに、7EHにB→R→G→Iの順にタイルデータを出力すれば設定完了です。Iプレーンが無い場合は3回の出力のみで問題ありません。次回モードレジスタに書き込みが発生した地点でタイルレジスタが初期化されるからです。また、タイルレジスタは8bitですが、VRAMに対してワードアクセスすると、自動的に16bitに拡張されるので感心してしまいます。まあ、リストを見れば一目瞭然ですので、よくわからなければそちらの方を参照してみてください。 |
GRCGのTDWモードの使用方はこれで大体わかっていただけたと思います。ちなみにVM21以降の機種でもGRCGは当然使用できます。特にVX以降の機種はGRCGの強化版であるEGC(Enhanced Graphic Charger)が搭載されているので、GRCGの上位機能コンパチであるとともに、以上の機能がGDCからも使用できるようになっています(VM以前はGDCからは不可能)。
第2話:GDCの悪用(1)
GRCGを使用することにより、CPUから見た処理は約4倍は高速化したことになりますが、これで満足していたのでは欲がありません。今度はGDCをいじってみることを考えてみます。(※ただし、実際の実行速度は4倍にはなりません。見かけ上は4プレーン同時書き込みしているように見えますが、ハード的にはプレーンを切り替えて4回V-RAMメモリへの書き込みを行っているからです。)
ここにCSRFORM(カーソル・フォーム)コマンドがあります。このコマンドはテキストモードではカーソルの表示形態を定義するために用いるものですが、他に1行中のライン数を決定する機能も有しています。このライン数は1~32本までリニアに変更することができるのです。グラフィックGDCでは、通常この値は1本になっていますが、例えばこれを4本にすると、VRAM上の横1ラインがディスプレイ上では4ラインになって表示されます。つまり、垂直方向の解像度が4分の1になったことになります。ですから、PC-9801のグラフィック画面が640×100×8面(カラー)になったわけです。
ここまで書けば利用法は想像できますね。1行のライン数を最大の32本に設定すれば、垂直解像度は12.5になったことになりますから、13×40=520で、GRCGの4画面同時書き込み(TDWモード)と組み合わせることにより、わずか520ワードのライトで任意のタイルパターン(8bit)でカラー1画面が塗りつぶせることになります。ちなみに同じクロックでも、ノーマルなPC-9801F/Mのルーチンと比較して、8色モードで約90倍のスピードアップを実現します。
右にCSRFORMコマンドの実際の使用法について説明します。フォーマットを以下に示します。 “C”はコマンド(I/O PORT A2H)、“P1~P3”はパラメータ(I/O PORT A0H)をあらわします。これらを順に指定ポートに出力すればそれでおしまいです。また、P2以降は特に設定しなくても構いません(次のコマンドが設定された時点でキャンセルされます)。 |
|
以下にアッセンブルリストを掲げます。最初に簡単に述べましたが、単にグラフィック画面を指定パターンで塗りつぶすものです。しかしものは使いようで、MS-DOS上においてこのプログラムを走らせると、スクロールエリアを暗い青で、ファンクション表示エリアを暗い赤で塗ります。テキスト画面の方がプライオリティーが高いため、この上にテキストが表示されて見えます。これによって、好みもあるでしょうが非常に画面が華やかに、かつ、見やすくなります。短いプログラムですから、とりあえず打ち込んでみてください。そのときの速度も見物です。
|
;***********************************************************
;**
;** (グラフィックチャージャー使用バージョン)
;** バックグラウンドカラー設定ユーティリティー Version 1.0
;** -------- Programed by 杉原秀圭 --------
;** ___
;** +-+ 1987年 5月24日
;** |""|
;***********************************************************
;
CODE SEGMENT
ASSUME CS:CODE
;+--------------------------------------------------------+
;| イキュエイション |
;+--------------------------------------------------------+
GDCCMD = 0A2H ;グラフィックGDCコマンドポート
GDCPARA = 0A0H ;グラフィックGDCパラメタポート
MODEFF = 068H ;モードフリップフロップポート
DSPBANK = 0A4H ;表示バンク設定ポート
DRWBANK = 0A6H ;描画バンク設定ポート
GCCMD = 07CH ;グラフィックチャージャコマンドポート
GCTILE = 07EH ;グラフィックチャージャタイルポート
;+--------------------------------------------------------+
;| プログラム |
;+--------------------------------------------------------+
START:
MOV AL,0 ;
OUT DSPBANK,AL ;表示バンク=0
OUT DRWBANK,AL ;描画バンク=0
MOV AL,08H ;
OUT MODEFF,AL ;高解像度400ラインモード
MOV AL,0FH ;
OUT MODEFF,AL ;画面表示ON
MOV AL,2 ;
OUT MODEFF,AL ;カラーモード設定
BGS00:
IN AL,GDCPARA ;
XOR AL,20H ;GDC(μPD7220A)のFIFOに空きができる
TEST AL,22H ;と同時に垂直同期信号が発生するまで
JNE BGS00 ;ウエイト。
MOV AL,0DH ;GDC画面表示開始コマンド設定。
OUT GDCCMD,AL ;
MOV AL,088H ;グラフィックチャージャーにTDWモ
OUT GCCMD,AL ;ードを設定。
MOV AL,055H ;タイルパターン(青プレーン用)
OUT GCTILE,AL ;
MOV AL,0H ;タイルパターン(赤プレーン用)
OUT GCTILE,AL ;
MOV AL,0H ;タイルパターン(緑プレーン用)
OUT GCTILE,AL ;
MOV AX,0B000H ;
MOV DS,AX ;
MOV BX,0 ;
MOV CX,480 ;MS-DOSのスクロールエリアを
BGS01: ;指定パターンで塗りつぶす。
MOV [BX],AX ;
INC BX ;
INC BX ;
LOOPNE BGS01 ;
MOV AL,088H ;グラフィックチャージャーにTDWモ
OUT GCCMD,AL ;ードを設定。
MOV AL,0H ;タイルパターン(青プレーン用)
OUT GCTILE,AL ;
MOV AL,0FFH ;タイルパターン(赤プレーン用)
OUT GCTILE,AL ;
MOV AL,0H ;タイルパターン(緑プレーン用)
OUT GCTILE,AL ;
MOV CX,40 ;
BGS02: ;
MOV [BX],AX ;ファンクション表示エリアを指定パ
INC BX ;ターンで塗りつぶす。
INC BX ;
LOOPNE BGS02 ;
MOV AL,04BH ;CSRFORMコマンド設定。
OUT GDCCMD,AL ;
MOV AL,31 ;
OUT GDCPARA,AL ;
MOV AL,0H ;CSRFORMパラメータ設定。
OUT GDCPARA,AL ;
OUT GDCPARA,AL ;
MOV AL,00H ;グラフィックチャージャーモード
OUT GCCMD,AL ;を解除。
MOV AH,4CH ;MS-DOSに復帰。
INT 21H ;
;-------------------------------------
CODE ENDS
END START
|
|
|
|
開発はMIFES98のV1.0とMASMのV3.0です。仰々しくアッセンブラなんぞで作ってありますが、この程度であればBASICでもできます。逆にその方が「講座」という点からいけば意に沿うかもしれません。ま、TDWモードとGDCのCSRFORMコマンドが実感できれば十分だと思います。
いつになるかわかりませんが、次回はグラフィックチャージャのRMWモードを利用したスクロールゲーム用超高速グラフィックキャラクタPUTルーチンを紹介する予定でいます。まだこの方式を使用した市販ソフトはないようですが、クロック計算では既存の最適ルーチンを使用したものよりも速くなります。(8086だともっと比率が上がります。)
第3話:GRCGの悪用(2)
グラフィックチャージャ(GRCG)の概要についてはこれまで述べました。今回紹介するのはGRCGのRMW(リード モディファイ ライト)モードを利用したグラフィックキャラクタ表示ルーチンです。
RMWモードというのはその名の通り、読み込んで(Read)適当にいじくって(Modify)、書き込む(Write)という機能です。GRCGは、VRAMに対してライト(書き込み)された1バイトデータの各ビットが、“1”の部分は対応するタイルレジスタの値が、“0”の部分は書き込み前のVRAMのデータがそのまま、それぞれ(VRAMに)書き込まれます。具体例を下図に示します。
|
|
旧VRAMデータ |
|
|
|
描画データ |
|
|
|
タイルレジスタデータ |
|
|
|
新VRAMデータ |
|
|
|
|
RMWモードの動作 |
|
ご覧のように描画データのビットが“1”の部分はタイルレジスタのデータが、“0”の部分は旧VRAMデータが新VRAMデータとしてグラフィック画面に書き込まれます。この動作がたった1回のライト動作で各プレーン(B,R,G,I)に対して自動的に行われます。俗に言うところの4画面同時アクセスというやつです。
|
一見しただけでは何のための機能なのかピンとこないと思いますが、例えばBASICのLINE文を考えてみてください。BASICは無造作にカラーのラインを引いてくれますが、実際には既存のデータに余計な影響が及ばないように、(ラインを構成する)ドットを書き込みたい部分を含む1バイトをプレーンごとに取り込み、書き込むデータとORをとり、書き込み直すという途轍もなく面倒な作業をしているのです。これをGRCGを利用するならば、RMWモードを選択し、表示したいラインの色に合わせて各タイルレジスタに00HかFFHを予め設定しておき、あとはデータの重なりや色情報をまったく意識せずに描画することができます。ま、倍は速くなります。
もちろんこの機能に使用用途の制限などあるはずもなく、これをマウスカーソルやゲームのキャラクタ表示に使用しても誰も文句は言いません。
プログラムの詳細は、後ほどリストを追いながら説明していくことにします。
第4話:GDCの悪用(2)
キャラクタのPUTルーチンのめどがつくと、何かリアルタイムのゲームを作りたくなるのは、ごく自然な感情であると思われます。リアルタイムゲームといえばシューティングゲーム。シューティングゲームといえば画面スクロール。画面スクロールといえばハードウェアスムーススクロール……ときたもんだ。
最近の98のゲームを見ていても、他機種からの移植であったり、あるいは移植を考えたプログラム構成になっていたりで、ハードウェアスクロールを用いたものは稀有です。98(というかμPD7220)の持つスクロール機能は、画面を上下にしか分割できず、シューティングゲームに応用しようとするとプレイ画面が横長になってしまうという制限があります(下図に示します)。
|
スクロール画面1
このようには分割できない→
|
|
スクロール画面2
(もちろん固定画面にもなる。) |
|
|
|
通常はこれを回避するため、画面の右または左をインフォーメション画面として単一色で埋めてプレイ画面の形を整えます。しかしこれではインフォーメーション画面はキャラクタで表示することになり、画面全体が味気ないものになってしまいます。(初期の98専用ゲームやゼビウスがそれです。)このため、最近のように画面の凝ったものが流行るようになると、ますますこの機能は使用されなくなったようです。(発想力の不足だと思うのですが………)
そんなわけで、最近流行らない汎用ハードウェアスムーススクロールルーチンを考えます。これもリストを参照しながら説明することにします(後述)。
まず、キャラクタPUTルーチンのリストです。MASMで記述してあります。
|
;+--------------------------------------------------------------+
; <<グラフィックキャラクタORPUTサブルーチン1.>>
;
; SI=キャラクタデータオフセットアドレス
; DI=画面アドレス(0~1999)
; DS=キャラクタデータセグメント
; ES=A800H或いはB000H或いはB800H
;
; SP以外の全汎用レジスタ破壊。
;
; キャラクタパターンデータの先頭にはX座標バイト数とY座標ライン数及び
;1プレーン当たりのバイト数が格納されている必要がある。フォーマットは
;次の通り。
;
; DATATOP:DB X座標バイト数(1~80)
; DB Y座標ライン数(1~256)
; DW 1プレーン当たりのバイト数
; PTNTOP: D? キャラクタパターンデータ
; : 1:マスクパターン
; : 2:青プレーンデータ
; : 3:赤プレーンデータ
; : 4:緑プレーンデータ
;
;+--------------------------------------------------------------+
;
GRCG_MODE= 07CH ;グラフィックチャージャ・モードポート
GRCG_TILE= 07EH ;グラフィックチャージャ・タイルポート
;
ORPUT1:
PUSH DS ;DSを退避。
MOV DX,[SI] ;DL=X座標バイト数,DH=Y座標ライン数
MOV BP,[SI+2] ;BP=1プレーン当たりのバイト数。
XOR CX,CX ;CX=0
MOV AH,80 ;AHに次ライン先頭アドレス算出用オフセットを設定。
SUB AH,DL ;
ADD SI,4 ;データポインタ補正。
MOV AL,0C0H ;グラフィックチャージャにRMWモード設定。
OUT GRCG_MODE,AL ;
ORPUT101:
MOV CL,DL ;X座標バイト数設定。
ORPUT102:
MOV BX,BP ;BX=1プレーン当たりのバイト数。
MOV AL,[BX+SI] ;青パターンデータ1バイトをタイルレジスタに出力する。
OUT GRCG_TILE,AL ;
SHL BX,1 ;ベースアドレス更新(BX=BX*2)。
MOV AL,[BX+SI] ;赤パターンデータ1バイトをタイルレジスタに出力する。
OUT GRCG_TILE,AL ;
ADD BX,BP ;ベースアドレス更新
MOV AL,[BX+SI] ;緑パターンデータ1バイトをタイルレジスタに出力する。
OUT GRCG_TILE,AL ;
OUT GRCG_TILE,AL ;ダミー出力(明度タイルレジスタにライト)。
MOVSB ;1ライン分のデータを転送。
LOOP ORPUT102 ;
;
DEC DH ;全データ転送終了チェック。
JZ ORPUT103 ;
MOV AL,AH ;次ライン先頭アドレス算出。
MOV CL,AH ;
XOR AH,AH ;
ADD DI,AX ;
MOV AH,CL ;
JMP ORPUT101 ;次ラインデータ転送へ。
ORPUT103:
XOR AL,AL ;グラフィックチャージャモード解除。
OUT GRCG_MODE,AL ;
POP DS ;DSを復元。
RET ;呼出し元へ復帰。
;+--------------------------------------------------------------+
|
|
|
以上がGRCGを使用したキャラクタPUTルーチンです。指定されたレジスタとメモリ領域に値を設定してCALLすると、マスクパターンにしたがってグラフィックキャラクタをバックグラウンド画面にPUTします。その際、スタックポインタとセグメントレジスタを除く全レジスタを破壊します。これは不必要なプッシュ/ポップを避けるためで、決して手抜きではありません。通常のアプリケーションプログラム用のサブルーチンであればレジスタを保存するのが妥当ですが、この場合はまずゲーム専用といってよいでしょうから、“スピード”に最重点を置いています。
さて、コーリングシーケンスについてですが、不用意に何でもかんでもレジスタを通して引数を渡さずに、メモリインタフェースを利用してるところに注目してください。実用を考えると、レジスタインタフェースの方が手間が多くなることになります。引き渡すパラメタは、結局メモリに格納されているのですから。
ではデータ域先頭の「X座標バイト数」から説明していきます。これは、PUTするキャラクタの横方向のドット数÷8が入ります。「Y座標ライン数」は縦方向のドット数がそのまま入ります。ただしこの値は最大256(=設定値が0のとき)で、これによってPUTできるキャラクタの最大は640×256に制限されます。「1プレーンあたりのバイト数」は、以上の2つの値の積が入ります。このパラメタを設けた理由は、定数から定数を求めるといった無駄なかけ算を省くためです。いくら除積算の高速なV30とはいえ、この場合、30クロック程度の無駄が生じることになります(8086だと80クロックにもなります)。
その次からがキャラクタデータです。この領域の最初は「マスクパターン」です。これは四角いデータの中で実際に表示したい部分のみを抽出するためのものです。つまり、右図のAの部分のみを表示したいわけですから、Bの部分にマスクをかける必要があります。通常は単純にORをとる手法がとられるようですが、これではキャラクタの黒の部分が透明になってしまいますから、キャラクタは7色表示になってしまいます。また、黒を発色させるにはパレットの切替えがどうしても必要になってしまいます。その点、マスクをかける手法をとれば、手間は若干かかりますがパレットも意識しなくてすみますし、色も8色使えます。ましてGRCGを使用した場合は手間もかかりません。
次からの「各色プレーンデータ」はBASICのPUT@のデータ形式と同様、まったくノーマルな形式です(リストを見れば一目瞭然ですね)。
さて、指定された通りパラメタを設定してサブルーチンに飛んでくると、各パラメタをレジスタに設定し、グラフィックチャージャにRMWモードを設定しています。そして各データをタイルレジスタに設定します。最後のダミー出力というのは明度(インテンシティー)プレーン用のタイルレジスタで、16色モードを使用するときのみパターンデータを設定します。それほど難解な部分はありませんね?!
今度はハードウェアスクロールルーチンです。
|
;+------------------------------------------------------------------+
;<<グラフィック画面ハードウェア下スクロールサブルーチン.>>
;
;入力: AL=スクロール対象画面スイッチ
; AL=0;画面1
; AL=4;画面2
; AH=スクロールライン数
; BX=現在画面表示オフセット格納域オフセットアドレス
; SI=現在表示ライン数格納域オフセットアドレス
; DS=データ格納域セグメント
;
;出力: なし
;
;レジスタ破壊: AX,DX 以外保存
;
;補足説明: 画面表示オフセットは自動的に更新される。
;+------------------------------------------------------------------+
;
GDC_CMD = 0A2H ;グラフィックGDCコマンドポート
GDC_PARA= 0A0H ;グラフィックGDCパラメタポート
;
GSCROL_DOWN:
OR AL,070H ;‘SCROLL’GDCコマンド設定。
OUT GDC_CMD,AL ;
MOV AL,40 ;スクロールライン数から加算オフセット算出。
MUL AH ;
MOV DX,[BX] ;新画面オフセットアドレス算出。
SUB DX,AX ;
MOV [BX],DX ;
MOV AL,DL ;画面表示オフセットアドレスをGDCに設定。
OUT GDC_PARA,AL ;
MOV AL,DH ;
OUT GDC_PARA,AL ;
MOV AX,CX ;CXを退避。
MOV DX,[SI] ;表示ライン数をGDCパラメタに変換。
MOV CL,4 ;
SHL DX,CL ;
MOV CX,AX ;CXを復元。
MOV AL,DL ;表示ライン数をGDCに設定。
OUT GDC_PARA,AL ;
MOV AL,DH ;
OUT GDC_PARA,AL ;
RET ;呼出し元へ復帰。
;+------------------------------------------------------------------+
|
|
|
このスクロールルーチンは一応下方向スクロール専用です。上スクロールにしたい人は、リストの下線行の“SUB”を“ADD”に替えてください。せこい話ですが、スクロールルーチンというのもコール頻度の比較的高いものですから、上下の判断をするのがもったいなかっただけです。
パラメタの説明をしておきます。「スクロール対象画面スイッチ」とは、スクロールさせる画面の選択スイッチです。通常は画面分割などしませんから、サブルーチン内で0に固定してもよいでしょう。「スクロールライン数」は1回のコールでスクロールさせるライン数です。「現在画面表示オフセット格納域オフセットアドレス」と長たらしくわかりにくい名前ですが、これはGDCに設定するための表示画面のオフセットアドレスのメモリ格納域のアドレスです。セグメントはDSです。次の「現在表示ライン数格納域オフセットアドレス」は、スクロール画面の縦方向の大きさのメモリ格納域アドレスです。これも通常は変化しないでしょうから、サブルーチン内で固定してよいと思います。セグメントはDSです。
それではプログラムの説明に入ります。とりあえずGDCの‘SCROLL’コマンドのフォーマットを示します。
|
Cはコマンドで、GDCのコマンドポートに与えます。P0~P7はパラメタで、パラメタポートに与えます。「パラメタアドレス」には0~7(P0~P7)が入ります。例えばここに6(0110)を設定すると、次にパラメタポートに設定した値はP0ではなくP4に設定されたことになります。ですから画面1の情報を設定する場合は0を、画面2の場合は4を設定します。また、GDCがテキストモードであれば画面が4分割できる関係上、設定できる値は0~15となります。 |
「表示開始アドレス」は(L),(M),(H)の3つのビット領域に分かれています。このうち(H)の領域は、μPD7220が256KW(=512KB)の表示メモリを持てるのに対して、PC-9801シリーズでは48KWしか使用していないので、ここは常時00に固定されることになります。さて、この領域は早い話が表示画面のオフセットアドレスです。つまりここに設定されている値に40(10進)を加算すると、グラフィック画面が見かけ上1ライン下スクロールすることになります。ここで注意することはVRAMはCPUからアクセスする場合は、0A8000H~0BFFFFHに存在しますが、GDC側からだと004000H~00FFFFH(ライト時)になるということです。さらにGDC内部ではデータはすべてワード単位で処理されるため、1アドレス=16ビットです。ですから40ワード加算することで1ライン(80バイト)スクロールするのです。勘違いなさらないように。また、GDCの内部表示オフセットアドレスは、各プレーンとも0となっており、先に示したライト時のアドレスとは異なっています。よって、ここの値を書き換えると、4画面同時にスクロールするのです。
最後に「ライン数」ですが、これはスクロールエリアのライン数(幅)です。1~400までの任意の値が設定できます。以上のことを踏まえた上でリストを追ってみてください。
前編はとりあえずこんなところで。後編へと続きます。
|
|