そろそろパフォーマンスの測定もしようと思い同期周りを調べてみます。
ゲームでは CPU と GPU の同期の設計が重要です。描画パフォーマンス優先なら CPU と
GPU ができるだけ並列動作するようにスケジューリングしておきます。
動的な頂点生成で CPU からデータを転送する場合、またはクエリによって GPU の結果を
CPU で受け取る場合など、お互いに待っている時間をなくすようにします。
ここで間違うとパフォーマンスに大きく影響を与えることがあります。
●インターバルの設定
Direct3D 同様に描画時間の管理を自前で行っていたのですが、ウエイト無しにしても
必ず 60fps 前後で止まってしまうことに気がつきました。
ヘッダを見ていたら wglSwapIntervalEXT() を見つけたので使えるか確認。
GeForce GTX280 + 190.57 、”WGL_EXT_swap_control” が含まれています。
wglSwapIntervalEXT( 0 );
これで Display と同期せずに最大速度で描画するようになりました。
モニタのリフレッシュレート依存だと思いますが、 1 を与えると 60fps 固定、
2でその半分となりました。
●フェンス
D3D では ID3D11Query を用いて GPU の状態を受け取ることが出来ます。
レンダリングしたピクセル数、演算頂点数といったレンダリング結果の問い合わせだけでなく、
同期のための Event (Fence) や TimeStamp も含まれています。
OpenGL にも glBeginQuery() ~ glEndQuery() があります。
こちらはもともと実際にレンダリングした pixel 数を返す Occlusion Query の機能だった
ようです。Direct3D にも昔からあるもので、この手のオクルージョン判定は高速化のためと
言うよりレンズフレアの描画などに用いられていました。
同期用の機能は別の API になっていて、glSyncFence() を使います。
fence は描画コマンドと同じように PushBuffer (CommandBuffer) に記録されます。
レンダリングが fence に到達すると、対応する同期オブジェクトを Signal 状態にします。
CPU では同期オブジェクトの状態を見ることで、描画の進行具合と同期を取ることが出来ます。
// 挿入 GLsync SyncObject; SyncObject= glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 ); // 同期待ち glClientWaitSync( SyncObject, 0, 0 ); // no wait glWaitSync( SyncObject, 0, GL_TIMEOUT_IGNORED ); // 削除 glDeleteSync( SyncObject );
glClientWaitSync() は Timeout 時間を指定可能で、状態を返すことが出来ます。
glWaitSync() は Signal 状態になるまで、つまり完了するまでブロックします。
glSynciv() を用いて、現在の状態を参照することも出来るようです。
SwapBuffers() の動作をまだ厳密につかんでいないので、これで正しいかどうか
必要なのかどうかもわかりませんが、同期の例としてはこんな感じで。
static GLsync SyncObject[2]; static int SyncCurrent= 0; static int FenceCount= 0; void Render() { ~ SyncObject[SyncCurrent]= glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 ); SwapBuffers( hDC ); Current^= 1; if( FenceCount ){ glWaitSync( SyncObject[SyncCurrent], 0, GL_TIMEOUT_IGNORED ); glDeleteSync( SyncObject[SyncCurrent] ); } FenceCount= 1; }
●速度の測定
当初 TimeStamp が見つからなかったのですが Extension の方にありました。
GL_EXT_timer_query です。GeForce は対応していました。
glBeginQuery() ~ glEndQuery() が拡張されていて、GPU 処理の経過時間を
計測することが出来ます。
// 作成 const int maxid= 8; GLuint idbuffer[maxid]; glGenQueries( maxid, idbuffer ); // 削除 glDeleteQueries( maxid, idbuffer ); // 計測 glBeginQuery( GL_TIME_ELAPSED_EXT, idbuffer[0] ); ~ // 計測期間 glEndQuery( GL_TIME_ELAPSED_EXT ); // 結果が準備できているか調べる&待つ GLuint qret= FALSE; do{ glGetQueryObjectuiv( id, GL_QUERY_RESULT_AVAILABLE, &qret ); }while( qret != TRUE ); // 計測結果の受け取り GLuint64EXT data= 0; glGetQueryObjectui64vEXT( id, GL_QUERY_RESULT, &data );
Begin ~ End 区間に発行した GPU 命令の実行時間が返ります。値は nano 秒です。
Direct3D とは少々使い方が異なります。D3D11_QUERY_TIMESTAMP の場合は絶対時間、
つまり GPU クロックの参照に相当し、かつ単位は GPU/ドライバ によって異なります。
また計測期間が連続しているとは限らないため、その結果も受け取る必要があります。
D3D11_QUERY_TIMESTAMP_DISJOINT はこれらの追加情報を返します。
実際に用いる場合は query を 2重化して、1~2フレーム前の結果を受け取るようにします。
これで数値がとれるようになったので、あとは実際に試すだけです。
↑計測の例
OpenGL ES にはこの手の API が無いので、出来れば一通り追加して欲しいところです。
関連エントリ
・OpenGL のはじめ方 (2) wgl
・OpenGL 3.2 GeometryShader をもう少し使ってみる
・OpenGL GLSL のバージョン
・OpenGL 3.2 の GeometryShader
・OpenGL のはじめ方
・OpenGL ES 2.0 Emulator
・OpenGL ES 2.0
・OpenGLES2.0 DDS テクスチャを読み込む
・OpenGLES2.0 Direct3D とのフォーマット変換
・OpenGLES 2.0 頂点フォーマットの管理
・OpenGLES2.0 の頂点
・OpenGLES2.0 D3D座標系
・OpenGLES2.0 シェーダー管理
参考になりました。ありがとうございました。