video renderer
仕様をソフトとして実装する
最終目的はハードウェアの実装ですが、仕様の理解には1度ソフトで実装することをおすすめします。ハードウェアシミュレーションはタイミングまで厳格に調査することができますが、パソコンが高性能になっているとはいえ時間がかかります。
ソフトでは1画面分の画像を生成することは一瞬でできますから仕様の理解とそのすりあわせはソフトでやります。
試験データの出力
MAME をデバッガ付きで起動して、テストしたい画面のVRAMデータとゲーム画面を出力します。一連のコマンドはテキストファイルに記載し、source コマンドで一括実行できます。
save tile_color.bin,cc000,a00 save tile0_map.bin,d0000,4000 save tile1_map.bin,d8000,4000 save sprite_color.bin,c8000,a00 save sprite_map.bin,c0000,400 save mainram.bin,40000,4000
mainram は scroll の値を取り込むためです。(scroll register は CPU からは write only なので出力できなかった)
キャラクタROMの生成
専用ページをご覧ください。
tile
tile は 2 枚あり、描画仕様は同じです。非表示領域全てを表示するプログラムを用意し、エミュレータと同じ絵がでるまで仕様の確認とソフトの実装をします。
これらはそのソフトの結果です。市松模様は透過領域を示し、黄色い枠は可視領域を示します。可視領域はスクロールレジスタによって移動する領域です。
1つのレイヤで2つの枠があります。これは描画期間中にスクロールレジスタを書き換えることによって実現できます。原理的にはファミコンで使われているテクニックに非常に似ています。
sprite
sprite も同様に実装していきます。スプライトサイズ(この場合は連続描画枚数と書いた方が正確かも)の関係か tile より広めの非表示領域を持ちます。可視領域は移動しません。
DE1で実際に動かしたときにスプライトに無駄な表示があることがありました。これはスプライトサイズの数だけ sprite attibute を読み飛ばすという、ハードの都合であろう仕様に当初気付きませんでした(静止画像だと気付かないこともある)。ハードを実装した段階でも attribute の仕様を再確認する場面はあります。
各レイヤを結合し、最終的なゲーム画面を出力する
これは手間がかかるのでソフトとして実装しませんでした。
本当はした方がいいのですが、1度目の開発で確認できていたのでとばしました。
仕様をハードとして実装する
タイミングと内部処理用の座標を生成する
video timing に記載したタイミングで HSync, VSync と同時に描画パラメータの座標データが必要となります。
座標データは tile, sprite の座標の仕様でハードの都合で単純になるであろう値を作っていきます。よって可視領域の左上の座標が [0,0] となることは少ないです。
そのほかにも下記のパラメータを作っていきながら、M72 の本来の映像仕様や拡張した映像仕様に辻褄の合うように実装していきます。
- real_x, real_y: 最終的な画面の座標
- virtual_x, virtual_y: M72 の描画画面の座標
- virtual_x は upscan をするとき 1 scanline 中に 0から511 を 2度ループする
- virtual_y は upscan をするとき real_y が 2増える間、 1増える
- virtual_count: M72 の 1 scanline 中のカウンタで、スプライトの描画打ちきりなどの時間管理に使う
バッファを用意する
ハードの設計にもよりますが、M72 ではフレームバッファを持たずに各描画期間中にラインバッファに直接描画する方式を採用している物と思われます。
このため各レイヤで2本ずつのラインバッファを設け、1scanlineごとに書き込みバッファか読み込みバッファを切替えます。
ラインバッファへ描画する
tile の場合:
- tile ではすべてのラインバッファを更新するので、ラインバッファの初期化は不要です。
- nametable は 32bit で 1chip を構成します。この nametable RAM はDE1では SDRAM に配置したので、burst access を2度行います。
- nametable を取得後、8 pixel 分を1度でラインバッファへ書き込みます。
spriteの場合:
- sprite では描画しないラインバッファがあるので、描画前に全領域を 0 で初期化します。
- sprite atribute RAM は DE1 では内部 RAM に配置してあり、1clockでパラメータを全て取得できます。
- パラメータに応じて描画していきます。基本 1 pixel 単位での描画ですが、1clock / 1pixel で書き込むと遅いので、ラインバッファを4本並列に並べて 1 clock/ 4 pixel で描画します。
- 描画するスプライトが多すぎる場合は時間切れとして描画を途中で打ち切ります。
描画時間を管理する
本物のM72では各レイヤの描画は3つのハードで並列動作しています(基準クロック32MHz) が、DE1 では3つのROMのバスを個別に用意することができませんので高速(基準クロック72MHz)に1枚ずつ順番に描画していきます。
tile は描画数が可視領域だけですので、描画時間も固定となります。sprite は描画時間が不定です。
実装初期では、72MHz で3つ分の動作をさせたところ、 SDRAM のアクセスがボトルネックとなり、スプライトの描画枚数はとても少ない物でした。また描画期間には CPU が SDRAM へアクセスできないので全体的な処理落ちも頻繁に発生し、ゲームとしての再現度は非常に悪いものでした。
このため tile, sprite 双方で SDRAM のアクセス回数を減らす最適化処理を入れてあります。最適化処理は下記です。
- tile は charcter cache を設け、前回と同じ address を参照する場合は SDRAM を動かさない
- 片方のレイヤは空白が多いので処理時間が 3/4 程度になった。(ただし描画時間が可変になった)
- sprite はデータの並べ替えを行い、64bit burst access ができるようにした
- sprite のラインバッファの本数を増やし 2pixel 単位から 4pixel 単位に変更 (読み込み、書き込みで4本ずつなのでspriteだけで合計8本のRAMを使う)
- この2つの最適化で処理時間は 1/2 程度になった。
この様な時間管理はハードウェアシミュレーションで正確にやることができることがソフトとの違いです。
各レイヤを結合する
- 読み込み用ラインバッファからデータを取得します。
- 各レイヤの pixel データを参照し、描画するレイヤを決めます。
- 描画する pixel データを color RAM の address として入力し、 1 clock 待ちます。
- color RAM のデータをラッチし、最終描画データとしてモニターへ出力します。
シミュレータで確認する
最終描画データは R,G,B と pixel clock となり、このデータをシミュレーションモデルに設けた RAM へ書きため、出力します。出力したデータは readmemh/writememh のフォーマットなので、これをもとに PC 向け表示データに変換して確認します。
私のPCでは1frameの確認に2分程度かかります。(以前のPCではもっと時間がかかった)
DE1 で確認する
最初は CPU (V30) にはゲームのプログラムを動作させずに、ホスト CPU (TG68)で SDcard の ROM image/RAM image を SDRAM へ転送し、FPGA 内蔵 RAM とレジスタは初期パラメータとして color RAM へ転送した状態で動作確認しました。
なお DE1 は color の描画精度がRGB各4bitに対し、M72 の color RAM data は RGB 5bit なので最下位bitは無効にしました。このため色の再現が悪いです。
- 最終更新:2014-12-29 06:31:16