いろいろと忙しくて、こったネタに入れないので、手軽なプログラムをしてみました。
DirectX のプログラムでいつもいやなのはFPS(Frams Per Seconds)が固定されないことです。
海外のゲームでは、FPSが常に変動していることを仮定してプログラムを組みますが、
コンシューマ(家庭用)主体のわが国では、FPSを固定してプログラムするのに慣れている方が大半です。
また、モーションブラーを効果的に使うには、FPS値が固定しているほうが望ましいと考えられます。
という言い訳をかまして、FPS を一定にするプログラムを作ってみました。
で、いつものようにプログラムです。
ピクセルシェーダ2_0用ですが、まるで意味はありません。 この後説明するプログラムをコピー&ペーストして、自分のプログラムに貼り付けてみてください。
ソースには、いつものように適当にファイルが入っています。
大事なソースは次のようになります。
main.h | アプリケーションのヘッダ |
main.cpp | アプリケーションのソース |
hlsl.fx | アプリケーションで使うHLSLプログラム |
ディスプレイは上から下に向かって書いているのですが(液晶でもそうか?)、
その走査が一番下まで言ってから上に帰ってくる間を「垂直帰線期間」といいます。
DirectX では、この間にフロントバッファとバックバッファを差し替えることによって、ちらつきのない画面を再現しています。
この間隔はディスプレイによって異なりますが、私が使っている液晶ディスプレイは60Hz(1秒間に60回の画面書き換えを行う)です。性能がよいディスプレイだと、より周波数が高くなっていることでしょう。
さて、60秒に1回書き換えるならば、それ以上の速さで描画したときにディスプレイで表示されない画面が発生してしまいます。
これは無駄なので、描画以外の別の処理や別のアプリケーションにCPUなどのパワーを受け渡したいとことです。
もちろん、FPS が一定になると、アニメーションの処理などが簡単になるといった別の側面もあります。
これを実現するには、画面を描画した後に、現在垂直帰線期間かどうかを調べて、まだ垂直帰線期間中になければ、別の処理に負荷を明け渡せば実現できます。
DirectX では、現在の走査情報を調べる関数として、
IDirect3DDevice9::GetRasterStatus( ID, D3DRASTER_STATUS* 出力先)
が用意されています。最初の引数のIDは、マルチヘッドディスプレイのための識別IDです。
垂直帰線期間を調べるには、出力先として与える D3DRASTER_STATUS のメンバ「InVBlank」をみます。この値が TRUE ならば、垂直帰線期間です。
また、FLASE の時は、D3DRASTER_STATUS.ScanLine に現在書き込んでいる走査線の値が格納されるので、負荷を予想するのに便利でしょう。
なお、GetRasterStatus はディスプレイ等によってサポートされない場合があるらしいので、今回のプログラムを使えば完璧かどうかはわかりません。
GetRasterStatusを呼び出す場所ですが、画面を更新する「Present」関数の直前で呼び出して調べます。
関数呼び出しがPresent関数よりもはるか前だと、その後の処理が垂直帰線期間を飛び越える可能性があるので、注意が必要です。
なお、アプリケーションウィザードを使ったプログラムの場合は、Render関数の最後でGetRasterStatusによる調査をおこなえばよいです。
なお、今回のプログラムでは、GetRasterStatusの呼び出しで無限ループをかけて無駄にCPUを使っています。
都合に応じて、Sleep で休ませたり、他の処理を呼び出すなりしてください。
main.cpp 0366: //------------------------------------------------------------- 0367: // Name: Render() 0368: // Desc: 画面を描画する. 0369: //------------------------------------------------------------- 0370: HRESULT CMyD3DApplication::Render() 0371: { 0400: //--------------------------------------------------------- 0401: // 描画 0402: //--------------------------------------------------------- 0403: if( SUCCEEDED( m_pd3dDevice->BeginScene() ) ) 0404: { 描画~ 0561: // 描画の終了 0562: m_pd3dDevice->EndScene(); 0563: } 0564: 0568: // ----------------------------------------------------------------------- 0569: // 画面のリフレッシュレートにあわせる 0570: // ----------------------------------------------------------------------- 0571: D3DRASTER_STATUS rs; 0572: do{ 0573: if(D3D_OK!=m_pd3dDevice->GetRasterStatus(0,&rs)){ 0574: rs.InVBlank = TRUE;// 所得失敗!本当はどうするのですかねぇ。 0575: } 0576: }while(!rs.InVBlank);// ラスタが垂直帰線消去間隔にある 0577: 0578: return S_OK; 0579: }
このプログラムを実行しても、処理落ちしたときには、がたつくので、 過度に期待しないでください。
いままでFPSを一定にして処理していなかったので、あまり詳しくないのですが、 こんな方法でいいのでしょうかねぇ?
cozさんから、
FPSを一定にする方法ですが、DirectX9では、 D3DPRESENT_INTERVAL_DEFAULT などに設定すればいいと思う んですが、そういうことではないんですか?
というお言葉をいただきました。
早速、下のプログラムのように変更して試してみました。
d3dapp.cpp 0723: // Create the device ****: m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; 0724: hr = m_pD3D->CreateDevice( m_d3dSettings.AdapterOrdinal(), pDeviceInfo->DevType, 0725: m_hWndFocus, behaviorFlags, &m_d3dpp, 0726: &m_pd3dDevice );
確かに、同じような効果が得られますね。
ところで、このままでは、パフォーマンスが低かったのですが、他に何か設定がいるのでしょうか?
nemoさんから、
ところで、HPのコンテンツのDirectX 9.0:FPS 固定 [FPS fixing]についてなのです が、最後のほうでD3DPRESENT_INTERVAL_DEFAULTについて記述があります。 たしかにD3DPRESENT_INTERVAL_DEFAULTを設定するとFPSは固定されるのですが、うち の液晶では少しちらついてしまいます。 これはタイマーの精度が低いのが原因なようで、timeBeginPeriod(1);で精度を上げ てやれば問題なく働きます。
というお言葉をいただきました。
D3DPRESENT_INTERVAL_DEFAULT だけでは完璧ではないということなので、過信しないようにしましょう。