日別アーカイブ: 2009年9月10日

OpenGL の同期と描画速度の測定

そろそろパフォーマンスの測定もしようと思い同期周りを調べてみます。
ゲームでは 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フレーム前の結果を受け取るようにします。
これで数値がとれるようになったので、あとは実際に試すだけです。

glquery00.jpg
↑計測の例

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 シェーダー管理