video priority

仕様がバラバラでエミュレータソースコードから読み解くのが一番面倒な仕様、それがレイヤ(planeともいうが、ここらへんは方言がひどくて別の意味を言っていることもある)をどの順番に並べるかという点である。

別の用途もあったので m72 を一緒にやってみた。

おおざっぱな仕様

tilemap の特定の 2bit が priority となっており、sprite には別レイヤに関する priority bit はない。この 2bit は mameレンダリングエンジン的には group と称するようだ。

m72_get_tile_info() から抜粋:

	if (color & 0x80) pri = 2;
	else if (color & 0x40) pri = 1;
	else pri = 0;
	(中略)
	tileinfo->group = pri;

とりあえず 0 から 2 の3通りを group としている。

レンダリングする順番

ソフトエミュの場合、レンダリングは背面から前面に順番に描画していく方式をとる。

VIDEO_UPDATE( m72 )
{
	if (video_off)
	{
		bitmap_fill(bitmap,cliprect,get_black_pen(screen->machine));
		return 0;
	}

	tilemap_set_scrollx(fg_tilemap,0,scrollx1);
	tilemap_set_scrolly(fg_tilemap,0,scrolly1);

	tilemap_set_scrollx(bg_tilemap,0,scrollx2);
	tilemap_set_scrolly(bg_tilemap,0,scrolly2);

	tilemap_draw(bitmap,cliprect,bg_tilemap,TILEMAP_DRAW_LAYER1,0);
	tilemap_draw(bitmap,cliprect,fg_tilemap,TILEMAP_DRAW_LAYER1,0);
	m72_draw_sprites(screen->machine, bitmap,cliprect);
	tilemap_draw(bitmap,cliprect,bg_tilemap,TILEMAP_DRAW_LAYER0,0);
	tilemap_draw(bitmap,cliprect,fg_tilemap,TILEMAP_DRAW_LAYER0,0);
	return 0;
}

この関数は描画する単位時間(line とか frame とか)ごとに呼ばれる。video_off という画面表示レジスタがあり、それが有効な場合は画面真っ黒でおしまい。表示する場合はスクロール値を取り込み、 bg の layer1, fg の layer1, sprite , bg の layer0, fg の layer1 の順番に重ねていく。

ここで fg, bg と 2枚の tilemap と 2 枚の layer がでてきたが、これが曲者で fg, bg はエミュレータ開発者が勝手に付けた変数の名前で fg だから foreground, bg だから background という固定概念は捨てるべきである。

次に TILEMAP_DRAW_LAYER1 もソフトウェア処理で必要な付加属性でハードウェアでは考え方が異なる。オブジェクト的には [bf]g.layer[01] ということになる。

透過色の設定

基本的に透過色はキャラクタデータの 0 ということになってるはずなんだが、慣例というだけで独自の拡張を加えた場合がある。

video_start(m72) から抜粋:

	tilemap_set_transmask(fg_tilemap,0,0xffff,0x0001);
	tilemap_set_transmask(fg_tilemap,1,0x00ff,0xff01);
	tilemap_set_transmask(fg_tilemap,2,0x0001,0xffff);

	tilemap_set_transmask(bg_tilemap,0,0xffff,0x0000);
	tilemap_set_transmask(bg_tilemap,1,0x00ff,0xff00);
	tilemap_set_transmask(bg_tilemap,2,0x0007,0xfff8);

tilemap_set_transmask() は透過色を個別に設定できる関数で、非常にわかりにくかった。あくまでのソースを読んだ解釈なので間違っている可能性があるということで参考資料にして欲しい。

この関数の prorotype はこうなる。

void tilemap_set_transmask(tilemap_t *tmap, int group, UINT32 fgmask, UINT32 bgmask);

group は m72_get_tile_info() で設定される priority bit とひもづける値であるらしい。2番目の fgmask というのは名称が紛らわしいので困るのだが TILEMAP_DRAW_LAYER0 に結びつけられる属性、 3番目の bgmask は TILEMAP_DRAW_LAYER1 の属性。

この時点で名前が交錯していて混乱気味。いろいろ考えてこれが一番矛盾がないのでそう解釈した。

この fgmask, bgmask はキャラクタデータの何番を透過するか否かというパラメータである(0番がbit0,15番がbit15,値の0が不透明,1が透過)。4bpp であれば 0 から 15 を持つので 16bit, 3bpp であれば 0 から 7 を持つので 8bit の値が有効になる。このキャラクタデータは実装されている tilemap.c では pen と呼ばれておりよくわからない。前述のような理由で矛盾がないのでそう解釈した。

ソースコードに戻る。

	tilemap_set_transmask(fg_tilemap,0,0xffff,0x0001);

fg_tilemap は m72.c で実装されている tilemap の変数名(単に2枚ある1枚の名称で foreground ではない), 0 は tilemap attribute の group, 0xffff は tilemap_draw(bitmap,cliprect,fg_tilemap,TILEMAP_DRAW_LAYER0,0); での描画時の設定である。

この関数が呼ばれているときに描画する fg_tilemap の group で 0 の場合は描画しない(全て透過)、ということになる。

ハードへの実装

ハードの場合は基本的に並列に動いており、ソフトのように各レイヤを順番に描画するのは時間が足りなかったり大量のメモリを要求する。よって 1 pixel 描画枚に各レイヤの値を分析して描画するレイヤを選択する。このため描画選択は前面から背面に条件分岐していく。キャラクタデータの焦点が透過から不透明に変わっているのも注意。

	//tile #0.front > tile #1.front > sprite > tile #0.back > tile #1.back
	/*
	tile #0.font (priority:charter ROM index)
	00:opaque none, transport all
	01:opaque 8-15
	1x:opaque 4-15
	*/
	if(
		(tl0.lb_priority == 2'b01 && tl0.lb_q[3:0] >= 4'h8) ||
		(tl0.lb_priority[1] == 1'b1 && tl0.lb_q[3:0] >= 4'h4)
	){
		tc.a = tl0.lb_q;
		color_tile_sprite = TILE;
	}
	/*
	tile #1.front
	00:opaque none, transport all
	01:opaque 8-15
	1x:opaque 4-15
	*/
	else if(
		(tl1.lb_priority == 2'b01 && tl1.lb_q[3:0] >= 4'h8) ||
		(tl1.lb_priority[1] == 1'b1 && tl1.lb_q[3:0] != 4'h0)
	){
		tc.a = tl1.lb_q;
		color_tile_sprite = TILE;
	}
	//sprite opaque 1:15
	else if(slb.lb_q[3:0] != 4'h0){
		color_tile_sprite = SPRITE;
	}
	/*
	tile #0.back
	00:opaque all
	01:opaque 0-7
	1x:opaque 0-3
	*/
	以下略

tl0, tl1 は tile#[01] linebuffer, slb は sprite linebuffer, tc は tile color RAM の略。 この流れで感じで組んでいったら tl1, tl0 の順番が間違ってたみたい。それ以外はなんかそれっぽく重ねる順番があっていたのでよしとする。

しかし、当時レベルでこんな条件分岐するような回路を組むのはコストが無駄にかかる気がするので本当にあってるのかが怪しい気もする。tilemap_set_transmask() の mask 引数は 2 までで、3つある場合はどうなのとかも気にあるんだが、この関数を使う場面は少ないみたいで要求は満たしているみたい。