dev PPU
_
|
ようやくスプライトが表示できた |
BGとスプライトを組み合わせて完成! |
さらに探索ステージを記述。 各スプライトに関して4クロック×64スプライトで256+その他=258クロックを要しますが、 背景描画クロックに収まりました。 描画範囲外のスプライトを無視するようにすれば16スプライトも可能ですか!?
スプライトのパターンデータのためにスプライトバッファレジスタを、 それぞれ8スプライト分用意する必要があります。 背景描画が終わって、水平同期までの間に8スプライト分のデータをフェッチするステージを追加。 各スプライトで6クロック×8スプライトで48+その他=50クロックで収まりました。 このパターンデータフェッチがあるから16スプライトは難しいようです。 (実は16スプライトも可能なんですがそれはまたいずれ)
非力なノートパソコンでのシミュレーションは時間がかかりましたが、 34フレーム目にようやくマリオが現れました。 宿題終わり。
実家から戻り、FPGAへの実装でも描画を確認。 信号も安定し、ようやくデモでマリオが走ってます。 合成結果、速度最適化
Total equivalent gate count for design: 46,810これからRAMのゲート数16,390×2を引いて、=14,030ゲート。どうなんだろ。 面積最適化で1,000ゲート減らせるんだけどな。 インクリメンタルコンパイル調べないと…
カラーは、各ドットに対して6ビットのピクセルデータを出力しました。 VGA出力には、下位4ビットでRGB各3ビットから成る色を選択し、 上位2ビットを明度として加算した結果を出力しました。 よく見ると、全体的に薄いような気がします。
コインカウンタ | 某○リオ |
マリオで遊んでて気付く。
マリオがコインカウンタ(背景)に重なると、
背景よりも優先度が低いはずのピクセル(スプライトゼロ)が描画されている。
これは、スプライトと背景、及びスプライト同士の優先順位で矛盾しているものを描画しているから。
実機でもこの現象が起きることを確認したので、あこれでいいのか、と。
沙羅曼蛇などで使用される、 IRQ割り込みによるラスタスクロールに対応するため、 スクロールレジスタの書き込み箇所を変更。 背景といっしょにスクロールしていた点数表示が固定されるようになりました。
イロガ、スコシチガイマスカ?とのご指摘を頂いたわけですが、 発色はテレビの設定などによって様々であるものの、 パレットをワイヤードロジックでやるのは色変更も自由にできないので、 64色のRGBカラーをROMとして持つことにしました。
左が12色+明度、右が64色。
タダケータイのカメラはだめだな。
64色の方がメリハリが利いてますな。
でもこれは好みの問題だよなあ。
(12色+明度でやるのが実機に近いらしい)
FF2において、敵との遭遇時や敵を倒した時のエフェクト時に、 画面がちらつく問題が発生した。
通常VBlank終了前にスクロール値を設定するが、 PPUへのwriteログを見てみると、
1. スプライトヒット待ち
2. NMI有効
3. スクロール値設定
4. VBlank開始
5. スプライトデータ転送
6. VRAMデータ転送
7. スプライトヒットクリア待ち
8. VBlank終了
9. スクロール値設定
1. スプライトヒット待ち(最初に戻る)
8.のVBlank終了前ではなく、実は7.のスプライトヒットクリアをトリガとしてスクロール値をwriteしていたようだ。
PPUのHDLを見てみると、スプライトヒットクリアと、スクロール値の反映を同じタイミングで行っていた。
このため、正しくスクロール値が反映されないで、ちらつきが発生していた模様。
スプライトヒットクリアの次のラインで、スクロール値を反映するようにしたところちらつきがなくなった。
1ラインに表示できるスプライトは最大8個まで、について。
ドラクエなどで最大4人パーティとなっている理由が、 1ラインに表示できるスプライトが8個までというPPUの仕様からきたものであるということは有名な話。 ただしソフトによっては、フレーム毎にスプライトの優先順位を変え、 点滅させることで擬似的に9個以上のスプライトを表示するというテクニックを使っている。
もちろん、PPUをFPGAで実装するにあたって、 規定クロックでのパターンフェッチを無視すれば同時に9個以上のスプライトを表示させることは可能 (50MHzにて探索576個、パターンフェッチ191個、という机上計算)。
ただし、ドラクエWなどでは、8個までしか表示できないということを利用して、 スクロール時に意図的にスプライトを非表示にする演出が使われている。 また、ちらつきがあるのも趣があるということもあり、 スプライト実装は8個までにとどめておく。
スプライトが8個以上表示される場合、 PPUレジスタ$2002のビット5のフラグが立つという仕様があるが、 このあたり、8個以上なのか9個以上なのか資料によってバラバラ。 8個は表示できるからフラグを立てて知らせる必要はないんじゃないかと思う。 検索結果が9個以上の場合にフラグを立てることで、 点滅させるなりして処理することが適切じゃないか?と思った。
今のところ解析結果としては9個以上でフラグが立つということらしいが、 スプライトメモリの内容によっては8個でもフラグが立つ場合があるらしい。 おそらく表示スプライトの探索ロジックを削減したことによる仕様外の動作みたいだが、 このあたりは実装する必要性を感じない。 単純に9個以上でフラグを立たせる実装としている。
そもそもこのフラグを見ているソフトはあるのか? ドラクエWで試してみたが、 このフラグを立てたり立てなかったりしても必ず5キャラで点滅することからフラグは見ていないようだ。
巷のエミュでは8個以上でフラグが立つ実装となっているものもあるが、 これは探索速度を向上させるための処置だと思われる。 結局、役に立ちそうもないフラグという結論となった。
ファイアーエムブレムで左上にごみが出る。 別に左8ドットを表示しないフラグも設定されていないのでこれは正常のようだ。
これは、通常上8ラインは表示されていないファミコンの仕様により隠れている部分であって、 TVなどでプレイする分には問題にはならない。 エミュでも上8ラインを表示しないものであればゴミは隠れる。 が、全ラインを表示する設定にすると出てくる。
ファミコンの仕様上、左8ドットと上8ラインに関しては、 スクロールなどの際にタイル番号などが境界の上になり、 色が変わったり異なるパターンが表示されたり、 スプライトが切れたりしやすい。 そういった悪見を隠すためのPPUレジスタ$2001のマスクフラグがある。 でも設定されてないことも多いんだよね。
なんだかゴミが出る。 ミネルバトンサーガでソフトリセット直後のタイトル画面や、 ルート16ターボでの画面切り替え時に出る。 よく見るとスプライトがあった位置に出るようで、 スプライトRAMへのアクセスが怪しいとあたりを付けた。
エミュでは出ないということは、スプライトRAMへのreadとwriteが同時に起こっている可能性が高い。 エミュでそれを再現することは難しいからね。 それで、SFL記述をよくよく見直してみると、 PPUの描画設定レジスタ$2001:4のスプライト有効無効設定レジスタはあるものの、 スプライト探索時にこれを参照していなかった。 このため、スプライトRAMへのスプライト探索時のreadと、DMAからのwriteが重なってしまい、 writeがうまくいかずにゴミとしてスプライト情報が残ってしまうようだった。
スプライトパターンフェッチ時は対策済みだったが、 スプライト探索時にもスプライト無効時はスプライトRAMへアクセスしないように修正した。 これにより、ゴミが出ることは無くなった。
正常な画面切り替え |
PPUとはちょっと話がずれるが、「アルマジロ」はカスタムMMC3チップを使用している。 オリジナルのMMC3と違うのは、CHR-ROMバンク指定時にVRAMのミラーリングも指定されるというところ。 実装してみたところ、右の動画のように、画面切り替え時に本来は黒い画面からステージ画面に切り替わるはずが、 他のパターンが表示されているような不具合が発生した。
最初、ミラーリングの実装が間違ってるのかと思い、いろいろ試してみたものの状況は変わらず。 エミュでも再現できないので、ふと、IRQについて思い出した。 で、試しにIRQを発生させないようにすると、黒い画面からステージ画面への切り替えができた (ただしIRQが入っていないので下の点数画面が表示されない)。
MMC3のIRQについては、描画ラインでカウンタをデクリメントし、ゼロになるとIRQが発生する。 この描画ラインの判定方法が悪く、IRQの発生タイミングが悪かったのが原因だった。
ドキュメントを探してみると、CHR_A12でいろいろと判定しているように書かれてあるが、 今回は「if(chr_read & (CHR_A13==0b0)) f_draw := 1;」にて描画ラインと判定することとした。 つまりCHR_ROMへのReadアクセス時にフラグを立て、 HBlank時にフラグが立っていたら描画ラインとしてIRQカウンタをデクリメントする。 これを実装して、ようやく正常にステージ画面への切り替えができるようになった。
ただしこれだと1スキャンライン分、IRQが遅れるんだよね…。
自作エミュでマリオ3の動作確認をしてて気づいたんですけど、 パックンフラワーが土管に隠れてない!
アトリビュート属性
ということで、スプライトとBGの描画優先順位がおかしいのかと思い、 パックンフラワー付近のアトリビュート属性をピクセルごとにテキスト出力したものがこちら。 「01」というのがパックンフラワーの頭で、 「22」というのが土管の所にあるスプライト。なんじゃこれは?
このアトリビュート属性を見ると、ビット5が立っていると描画の優先順位がBGよりも低くなる。 つまり、BGパターンが無い所(青い空)ではスプライトが描画されるが、 BGパターンがある所(土管)ではこのスプライトは描画されない。 しかも、そのピクセルで描画されるスプライトの優先順位はIDによって決まっていて、 この部分では、パックンフラワーのスプライトよりも土管の所にある「22」のスプライトの方が優先度が高いIDとなっていた。
どういうことかというと、土管の所にBGに隠れるスプライトを置いて、
それよりも優先度が低いパックンフラワーのスプライトが描画されないようにしている模様。
マリオ3:BGパターンがある箇所の土管
マリオ1なんかだと、単純に土管の出口にあたるところにはBGパターンを置かないようにして、 土管に入るスプライトの描画をBGの裏側にすることで土管に隠している。 しかしマリオ3だとステージ構成がより豊かになっているので、 土管の出口にBGパターンがあるような場合だと、マリオ1のようにはいかない。 そこで隠しスプライトを土管の所に置くことで、 BGパターンが描画されていてもスプライトを土管に隠すことができる。 なんという親切設計!
以上の事が分かったため、自作エミュのスプライト描画部分を修正してちゃんとパックンフラワーが土管に隠れるようになった。 HDL側ではエミュとは異なる実装をしていたため、初めからちゃんと隠れるようになっていた。 この部分、スプライト描画処理がちゃんと実装されてるかどうかの判断に使えますね。
解析資料では、PPUの描画機能についてラインバッファ(256ドット分)があるとの記述が見当たらない。 しかし人によってはラインバッファに描画するとの説明をしている場合があり、 考えた結果をちょっとメモとして残す。
FPGAで作ってみたところ、PPUの内部ではスプライトのパターンシフトレジスタ出力を、 スプライトの優先順位回路に基づいて出力し、BGとの優先度に応じて描画させた。 解析資料にある通り、スプライトパターンは描画前のスキャンラインでフェッチするため、 描画ライン時にBGの描画と同時にスプライトも描画できる。 つまりラインバッファは不要である。
もしファミコンのPPUでラインバッファを使うとしたら、 341サイクル中、BGを256サイクルで描画し、8ドット×8スプライトを64サイクル+αで描画できるので、 スプライトの優先順位回路は不要になる。 ただしこの場合、スプライトパターンフェッチと前のスキャンで取得したパターン描画が重なるため、 スプライト関係のバッファレジスタが余分に必要になってしまう。
つまり、描画回路の設計として、ラインバッファか優先順位回路のどちらかを選択する事が考えられるが、 結果としてファミコンではコスト的にラインバッファは採用しなかった、と思われる。
一方、PCエンジンではラインバッファを使用しているらしい。 最初の256サイクルでBGを描画(横256ドットモード時)し、 続く512サイクルで32ドット×16個(最大時)のスプライトを描画しているとのこと。 スプライトがインデックス順に描画されるのであれば、 おそらく優先順位回路は使用していないと考えられる。 つまり、価格コストバランスを考慮すると、 スプライトが多くなるほど優先順位回路よりもラインバッファを使用した方がコスト的に抑えられる、 という仮説が立つ。
ちなみにNES on FPGAでは、PPU出力をVGA出力にするためのフリップバッファ(256ドット×2ライン)を使用している。
これは、PPUドットクロックが約5.37MHzで、VGAドットクロックが25.2MHzという差(約4倍)を吸収するため。
PPUがバッファAに1ライン(256ドット)描画している間に、
VGAがバッファBから2ライン(512ドット×2ライン)出力する。
描画及び出力が終わったらバッファABをBAに切り替える(フリップ)。
PPU描画用としてラインバッファを使用しているわけではないことに注意。