コンシューマ機で見たメインループのあれこれ

ゲームを作る際には必ずメインループを作ると思います。
ゲーム中の1フレームを処理するルーチンをVブランク毎に繰り返す
ループルーチンの事です。
今回は昔現場で見た(覗いた)メインループのいくつかを話してみたいと思います。

広告
広告1




VSync信号待ち型

VSync信号とは昔のブラウン管TVの垂直帰線信号のことで
電子ビームのホームポジションへ戻る信号の事です。
これは1/60秒毎に行われ、現在の60fpsと呼ばれる
もととなった信号です。
ここから電子ビームの描画開始までの時間がVブランク中と呼ばれ、
コンシューマ機によってはかなり重要な時間になりますから
昔のコンシューマ機ではこのVSync割り込みを乗っ取ることから
始まります。
私も使う、ごく一般的な方法だったと思います。

1.VSync信号割り込みをトラップし、割り込みが来たらフラグを
  立てる構造にします。
2.1フレームの処理を行います。
3.フラグを0にして、次にフラグが1になるまでフラグを監視しながら
  その場ループします。もちろん割り込み発生は許可しておきます。
4.VSync割り込みが発生すればフラグは1になり、その場ループは
  フラグが1になることで終了、2へ戻ります。

これを繰り返すことでVSync毎のループを確保し、割り込みをカウントすることで
秒数を算出したりします。

VSync割り込み記載型

これはVSync割り込みが定期的に呼び出されるものなら、
ここから1フレームの処理も呼び出せばいいじゃないかという
発想と思われます。
VSync毎に処理が自動で実行されますから、メインループは

L:
     jp  L

という、何もしないループになります。
逆アセンブルをかけると途中で無限ループに入るので逆アセで
のぞいていたプログラマが混乱することがあるとかないとか?

リアルタイムモニタ型

これは昔某T社にいた時に先輩方が使っていた手法で
「リアルタイムモニタ」という言葉も先輩に教わった言葉です。
私は実際に使用しなかった手法なのですが、ファミコンにこれを
適用していたのは珍しいんじゃないかと思いましたのでご紹介します。

これは現在でいうスレッドに近い構造のもので、分けられたスレッドは
プログラムカウンタ,レジスタ、スタックも独立して保時されます。
さすがにファミコンはスタックメモリが少ないので大まかなスレッド数になり、
スクロール、オブジェクトの移動、ヒットチェック、サウンドドライバー、
スプライト回し等に分散されていたようです。
(8つくらいまでは可能だったかと思います。)

実行開始はVSync割り込みから若いスレッド番号順で実行されます。
ワークの並び=スレッド番号なので入れ替えはできません。
実行順をリスト構造にすれば順位を入れ替えることができると思いますが
昔のゲームではさほど順位での問題にはならなかったようです。

各スレッドは1フレーム分の処理が終わればdispathし、次のスレッドへと
実行権を渡していきます。

ここで特殊なのが全体で処理が間に合わず次のVSync割り込みが発生すると
現在のスレッドはそのプログラムカウンタで中断され、
また一番若いスレッドから実行され始めます。
そうしてまた中断されたスレッドへ実行権が戻るとスレッドはその中断された
プログラムカウンタから再開します。
これにより重要なスレッドはVBlank毎に確実に処理され、時間をかけても
特に問題にならないスレッドは後回しにされる構造になっていました。

もちろん全体の処理が重いと順位の低い処理が中断されるわけですから
1フレーム全体の流れでは完了していないワークに対しどうするかを
対応する必要があります。
(それをすっきりまとめることが出来ず私は使用しませんでした)

例えば重いヒットチェックでそれが起こればゲームとしては一定で
動いてもヒットが回りきっていないので本来当たるはずの処理が
当たらないことにもなります。重い処理が続けばその状態が続くことにも
なります。

逆に言うと多少の遅れが出ても一定のタイミングを維持し続けるという
メリットもあります。重い思考ルーチンを低いスレッドで実行し続け、
一定タイミングで思考中アイコンをアニメーションをさせ続けることも可能です。
ありがたいのは思考ルーチンを書くとき、一定時間処理したらdispatchなどと
書かなくても自動で中断される点です。

あとがき

私が見てきたコンシューマ機でのメインループの形状をご紹介しました。
現在のメインループの作り方と違う部分も結構あれば、
昔と変わらない部分もあるかと思います。

昔のパソコンなどではVSync割り込みが使えないものも多いため
タイマー割り込みを利用したり、VSync信号がIOポートに出ていれば
それの変化をカウントしたりして一定のループを作り出す工夫をしていました。

先人の知恵と工夫に感謝です。

広告
広告1




シェアする

  • このエントリーをはてなブックマークに追加

フォローする

広告
広告1




コメント

  1. もよよん より:

    はじめまして
    非常に興味深く記事を拝見しております。

    過去、私も現場で見たメインループで、この時はゲームボーイで
    VSync信号待ち型ではあるのですが
    メインループ側の待ちループをHALTで作成されていて、
    Vbrankで割り込みがかかるのと、割り込み処理ルーチンにはRETIだけが書いてある
    というものを見たことがあります。

    RETでも戻るのはCALLの次の行になりますから、RETIでもHALTの次の行に戻るというわけです。

    これだと、フラグ用のメモリや、フラグをリセットする時間がかからないのですね

    最初に見たときには、HALT! 止めてどうするんだ!!と思ったものです。

    • ふあう・らぼ より:

      はじめまして。

      >HALT
      なるほど、そういう待ちも確かにありましたね。
      HALT中は省電力なのでゲームボーイでよく使われていたんでしたっけ。