openMSXで、V9990に画像を出力してみる

V9990のエミュレーションが組み込まれたMSXエミュレーター

オープンソースMSXエミュレーターopenMSXには、各種拡張カートリッジのエミュレーターが予め用意されている。その中の、V9990搭載グラフィックカード、GFX9000のエクステンションを有効にすることで、V9990対応ソフトを動かすことができる。

 msx-sdcc@Wikiに簡単な解説とサンプルコードがあったので、ありがたくパク…参考にさせてもらうことにする。


www28.atwiki.jpwww28.atwiki.jp


512x424ドット、32768色モードを使いたい

 ただ、これそのままやっても面白くないので、画面モードを512x424のインターレースモードで高解像度の画像表示に挑戦してみた。なにしろ256x212ではいまどきかなり凸凹して見えてしまうので…

 で、実際画面モードを希望のものにするにはどうしたらいいかとなると、上記サイトの情報だけでは全く足りないので。
MSX Banzai! - The MSX Worshipping Shrine - V9990 Programmers Manual

をチェック。うーん、MSX2 テクニカルハンドブックを思い出す詳しい説明だ。ただ問題は英語だということなのだけど…



 どうやら、V9990の場合は、インターレースモードで2プレーンに分けて描画する必要はなく、普通の縦424ピクセルの画像としてVRAMに上から書き込んでいけばうまいことインターレース表示してくれるらしいので、そのようにする。

 以前の記事で書いた、z88dkを使用し、以下のようなコードを書く。
juangotoh.hatenablog.com

#include <stdio.h>

unsigned char inp(unsigned char addr);
void outp(unsigned char addr, unsigned char data);
void V9990Reg(unsigned char reg, unsigned char data);
void V9990Memadr(unsigned long addr,unsigned char rw);

unsigned char inp(unsigned char addr)
{
    #asm
    
    ld hl,2
    add hl,sp   ;skip return address
    ld c,(hl)   ; c=addr (LSB)
    in a,(c)
    ld h,0
    ld l,a      ; hl = return parameter

    #endasm
}

void outp(unsigned char addr, unsigned char data)
{
    #asm

    ld hl,2
    add hl,sp
    ld a,(hl)   ;a=data
    inc hl
    inc hl
    ld c,(hl)   ;c=addr
    out (c),a

    #endasm

}

void V9990Reg(unsigned char reg, unsigned char data){
	outp(0x64,reg);
	outp(0x63,data);
}

void V9990Memadr(unsigned long addr,unsigned char rw){
	unsigned char hi,mi,lo;

	lo=addr & 0xff;
	mi=(addr >> 8) & 0x3f;
	hi=(addr >> 14) & 0x7;


	if (rw) {
		/* R#3 VRAM READ ADDRESS REGISTER */
		outp(0x64,0x3);
	}else{
		/* R#0 VRAM WRITE ADDRESS REGISTER */
		outp(0x64,0x0);
	}

	/* SET ADDRESS */
	outp(0x63,lo);
	outp(0x63,mi);
	outp(0x63,hi);

}

void main(void){
	long i;
    unsigned char col[3];
    FILE *fp;
    int c;
    int cIndex=0;


	
    // R#6 SCREEN MODE 512x424 16bit/pixel
    V9990Reg(0x6,0x97);

    // R#7 ENABLE INTERLACE
	V9990Reg(0x7,0x85);

	// R#8 ENABLE SCREEN
	V9990Reg(0x8,0x82);

	// SET VRAM ADDRESS(r=0,w=1) 
	V9990Memadr(0x0,1);
    if ((fp = fopen("A004.RAW", "rb")) == NULL ) {
		//puts("file not found\n");
		return 1;
	}
    fseek(fp,0,SEEK_SET);
    while((c=fgetc(fp)) !=EOF){
        unsigned char d1,d2,tmp;
        if (feof(fp)) break;
        col[cIndex]=c;
        cIndex++;
        if (cIndex >2){
            // col[0]=R col[1]=G col[2]=B
            cIndex =0;
            d2 = (col[1] >>1) & 0x7c;
            tmp=col[0] >>6;
            d2 |= tmp;
            d1 = ((col[0] <<2) & 0xE0) | (col[2] >>3);
            
            outp(0x60,d1);
            outp(0x60,d2);
            
        }

    }
    fclose(fp);

}

これをMSXDOS用にコンパイルし、できたコマンドと同じディレクトリに"A004.RAW"という画像ファイルを置く。
 これはあらかじめPhotoshopで512x424ピクセルにした、RGB各8bit、つまり24bitカラーのRAW画像だ。RAW画像というのは、ひたすらピクセルデータが左上から右下まで隙間なく並んだ、全く圧縮されておらず、一切メタデータを含まない画像で、普通こんなものを使う用途はあまりない。なにしろ画像ファイルに縦横のドット数とかRGBの並び順とかの、表示に必要な情報が一切含まれていない。ただ、予め必要な情報を自分が持っているなら、プログラムから読み出すのに一番簡単なフォーマットである。
V9990の16bitカラーモードは、1ピクセルに2バイト使用し、RGB各5bitとなっており、その並びは
0GGGGGRR RRRBBBBB
となっている。リトルエンディアンで格納されるので、RRRBBBBBの方を先に書き込む必要がある。もとのRAW画像は、
RRRRRRRR GGGGGGGG BBBBBBBB
の順で並んでいるので、3バイト読み込んでは、下位3ビットをクリアし、左右にずらした上で合成し、2バイトにまとめなないといけないのでちょっとだけめんどい。

結果

 実行してみると、まあ呆れるほど遅い。そりゃ1バイトずつ愚直に読み込んでは表示してるんだから遅いわなあ。結果が以下の画像。
f:id:juangotoh:20170716180720j:plain

あー、縦方向の解像度全く出てない。もちろん1/60秒ごとにチラチラ切り替わる画面をスクリーンショットでうまく取り込めないという問題はあるのだけど、これ実際見てるとわかるのだけど、openMSXのインターレースモードって、エミュレーターの画面上で全く同じ位置に奇数ラインと偶数ラインが描かれてて、それを切り換え表示してる感じなんだよね。本来テレビのインターレースモードは、走査線の奇数偶数で上下にずれて隙間を埋めてるから、それで解像度上がるのだけど、同じ位置で混ざって表示されるんじゃインターレース使う意味がない。実機とエミュレーターの違いが出てしまった感じかあ。

 まあとにかく、32,768色、横512ドットモードの画面のサンプルということで。

余談

 V9990、640x400とか、640x480とかの画面モードがあるのだけど、これらのモードだと最大16色しか出ないんだよね。時代的にMS-DOSで動くPC-9801シリーズのアプリが移植できればOK的なモードだったのかなあ。