051960 の zoomed sprite

FPGA で動いているパンクショットはズームを使ってないのですが、 051960 は多くのゲームに使われていてズームを使っていると、結構残念な表示になっています。
StarRuby でシミュレーション表示してみようかなと思う → MAMEソースコードを読んでみる → ズームを使ってるシーンから sprite map のデータを取り込んで調べてみる、という3つをやって、時間を無駄に使った感がありますが、記載しておきます。

基本的な仕様

sprite attribute

offset.bit
                                  • -
4.7:2 y.zoom bit5:0 4.1 y.flip 4.0 y.position bit8 5.7:0 y.position bit7:0 6.7:2 x.zoom bit5:0 6.1 x.flip 6.0 x.position bit8 7.7:0 x.position bit7:0

zoom は min:0 で 100%, max:63 で 50%(?), x.y の違いは特になし

MAME の K051960_sprites_draw()

ZOOM についてはソースコードが汚いので解読に時間がかかりました。
100%から50%の単位を%に変換した上で、zoom 値を固定小数点にして、座標算出みたいなことをやっているらしいです。

sy = oy + ((zoomy * y + (1 << 11)) >> 12);
zy = zh = (oy + ((zoomy * (y + 1) + (1 << 11)) >> 12)) - sy;

この式ですが、 sy はスプライト座標空間の整数部を算出しています。 zoomy は固定小数点を持って、小数部は12bitということらしいです。そもそもこの (1<<11) が何を指すのかよくわかりません。
zy は次のy座標の値を算出して、現在のy座標から引いてるので、何ドット描画するか計算しているみたい。 固定小数値から小数部を切り捨てる計算を2度しているのは固定小数で carry が発生すると、その1chipが毎回同じ幅にならないものと思われます。

そのあと pdrawgfxzoom_transtable() or drawgfxzoom_transtable() を呼んで 16x16 dot 単位で描画するものと思われます。つまりこの縮小ズームに関してはきちんと再現されていないだろうということにしました。

実際に使っているゲームの値をとってみた

ゲームで縮小状態から登場して等倍に大きくなる演出があるのでこれの値をとってみました。これは 64x64 pixel のスプライトを2つを横に並べており、拡大率が変わるとプログラムが2つのスプライトの幅を調整するためです。*1

zoom/width
 0 64|40 44
 4 62|44 42
 8 60|48 40
12 58|52 38
16 56|56 36
36 46|60 34

zoom の値は 4 ごとに更新されていますが、 zoom 20 から 32 は1枚のスプライトが画面外のため有効な数値を設定していなかったので省略します。これから推測するに zoom が 2 毎に幅が1減ると思います。これが 128 pixel サイズのスプライトだったら 1 毎に幅が1減るのではないか、と予想をたてます。

こうなると zoom の最大値 63 の場合、 128 pixel は 65 pixel, 64 pixel は 33 pixel, 16 pixel は 9pixel と 元のサイズ /2 +1 になって、 50% には絶対ならないのですが、こういう推測が出ました。

現状の間違っている計算方法

  • 原点は左上
  • 固定小数点は 9bit を整数部、6bit を小数部とした 15bit で表現する
  • 原点では offset を 0.0 として、1pixel 進むたびに {9'h001, zoom} を increment する
  • offset の小数部を切り捨てた(ソフト的には 6回右シフト)値を描画する

この計算方法は実は verilog 書いてるときにたぶんこれだろうと予想した方法でして、いまの実装で zoom がおかしいのは単なるバグだということもわかりました。かっこわるい。これだと 50% にならないから変な気がしますし、MAME と同じにならないなとも思ってたんですが、疑問点はちょっと減りました。

この計算方法は flip を考慮してないので、flip したら同じ画がでずに、切り捨てられた絵がでてくる場合があります。いいのだろうか。

推測した計算方法

  • 原点は左上
  • レジスタは carry + zoom の 7bit で表現する
  • 原点ではレジスタを 0 とする
  • 1clock ごとに offset += 1, reg += zoom とする
  • carry が発生しない場合はoffset の pixel を描画, carry が発生したら pixel を描画しない

これで座標更新かけたら幅の部分は計算は合いました。この方法だとシンプルで、ZOOM の値にかかわらず描画時間が同じになりそうです。

*1:このプログラムが適当に合わせてあって、ハードの仕様通りにやってなかったら、この調査はあまり意味がないので、推測にしかなりません。将来的にはテストプログラムを組んで計測してみたいものです。