もんしょの巣穴blog

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
  1. --/--/--(--) --:--:--|
  2. スポンサー広告

パフォーマンス計測

昨日、SetRenderState()とかをキャッシュするStateManagerを作成したので、今日はそのパフォーマンステストプログラムを作成してみました。
SetRenderState()の発行回数は当然減らせるだけ減らすべきなので、無駄な発行を行わないようにキャッシュしておくのStateManagerです。
と言うのも、会社の先輩にSetRenderState()の速度について聞かれて、そういえば試したことなかったなと思ったので。
で、ついでに他のテストもかねてやってみたわけで。
2000ポリゴン弱のモデルを読み込み、以下の3種類の頂点バッファ、インデックスバッファを作成します。

1.メッシュごとに頂点バッファとインデックスバッファを作成する
2.頂点バッファをモデル全体で統合し、インデックスバッファはメッシュごとに作成する
3.頂点バッファもインデックスバッファも1つに統合する

これでそれぞれテストを行ったのですが、速度はどれもほぼ変わりません。
やはり、3が若干速かったですが、FPSにして1も違いませんでした(Z書き込みなしで1000体表示して22FPSくらい)。
次に問題のStateManagerですが、やはり1000体表示しました。
当然、SetRenderState()、SetTextureStageState()、SetSamplerState()をそれぞれキャッシュを通した場合と通していない場合でチェック。
結果はほぼ変わらず。というか、先のテスト以上に差がない。
これはどういうことでしょう?
考えられるのは、そもそもDirectX、もしくはドライバの方でキャッシュを行っている可能性です。
調べてみたのですが、”Pure Device”ならキャッシュは効かない、と言うような話があるフォーラムにありました。
つまり、”Pure Device”ではないと言うこと?っていうか、”Pure Device”ってなに?
ちなみに、std::map を使ったのですが、Debugモードだとかなり重いです。Releaseモードなら問題なし。

と言うわけで、このStateManagerは失敗かも。
std::map を std::vector あたりで実装するならもう少し速くなるかも。
無駄な項目が出てきてしまいますが、O(1)でアクセスできるし。
まあ、そっちも試してみます。

追記
std::map を std::vector に変えてみましたが、ほぼ変わりません。
誤差程度の差なので、意味がないですね。
とりあえず std::map で行こうと思いますが、使うかどうかは微妙なところか。

スポンサーサイト
  1. 2009/03/29(日) 19:31:34|
  2. プログラミング
  3. | トラックバック:0
  4. | コメント:0

OpenGL ES 2.0 その04

前回はDirectXでいうところのDrawPrimitiveUP()と同じ描画方法をやりましたが、今回は頂点バッファインデックスバッファを使用した場合の描画方法をやります。
OpenGLにはディスプレイリストが存在するのですが(2.0でもあるかどうかは知らない)、GLES2.0には存在しません。
頂点バッファもインデックスバッファも作成方法はほぼ同じです。データの種類と、一部の関数での引数が違うだけです。
では、まず頂点バッファの作成方法から見ていきます。

glGenBuffers( 1, &g_hVbo );

まずはバッファを作成します。複数のバッファを作成することも可能で、DirectXのストリームのように複数のバッファを設定してもかまいません。
次にこのバッファに頂点情報を書き込みます。

glBindBuffer( GL_ARRAY_BUFFER, g_hVbo );
glBufferData( GL_ARRAY_BUFFER, size, pData, GL_STATIC_DRAW );
glBindBuffer( GL_ARRAY_BUFFER, 0 );

まず、作成したバッファをバインドします。これが1行目です。
2行目で情報を書き込みます。ターゲット、データサイズ、データの先頭アドレス、usage となります。
usage は通常、GL_STATIC_DRAW を選択します。これは基本的に変更しないデータを書き込む場合の設定です。
GL_DYNAMIC_DRAW を選択するのはデータを頻繁に変更する場合です。多分、速度は遅くなるけど、アクセスがしやすいような状態になっているんではないかと思います。
3行目でバッファのバインドを解除しています。OpenGLではバインドは引数に 0 を渡すとデフォルト設定に戻ります。DirectXもテクスチャやシェーダはそんな感じですね。
GL_ARRAY_BUFFER は頂点バッファのターゲットです。
このターゲットを GL_ELEMENT_ARRAY_BUFFER に変更するとインデックスバッファを指定したことになります。

実際の描画は以下のように行います。

glBindBuffer( GL_ARRAY_BUFFER, g_hVbo );
// ここで頂点とシェーダプログラム内の属性をバインド
glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, g_hIbo );

頂点バッファを設定した後に頂点属性のバインドを行います。
頂点属性のバインドは前回やった glVertexAttribPointer() のことです。
これらの設定は必ず頂点バッファの設定後に行う必要があります。
また、全く同じ頂点属性を持つ複数の頂点バッファを入れ替える場合も、頂点バッファのバインド後に属性のバインドを行う必要があります。
DirectXではVertexDeclarationが同じなら設定し直す必要はないのですが、OpenGLではやらないと駄目っぽいです。
どうも、頂点バッファの実体に対してバインドしているようなのです。
つまり、glBindBuffer() は属性をバインドするターゲットはこのオブジェクトですよ、と教えてやるだけのものなんじゃないかと思います。
その後、ターゲットを変更してインデックスバッファも設定します。もちろん、インデックスバッファを使用しないなら設定の必要はありません。
OpenGLはDirectXのようにバッファを使用する描画関数と使用しない描画関数が分かれていません。

glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, NULL );

インデックスを使用する場合、glDrawElements() 関数の第4引数で指定していましたが、インデックスバッファを使用する場合は NULL を指定します。
インデックスバッファを使用しているのに NULL を使用しないと描画がおかしくなるので注意してください。
また、DirectXと異なり、頂点バッファは使うけどインデックスバッファは使わずにインデックス配列を使って描画、とかも可能です。もちろん、その逆もOKです。
この辺の設定とかは頂点バッファやインデックスバッファをクラス化しておくといろいろ楽できるでしょう。

  1. 2009/03/15(日) 16:40:51|
  2. プログラミング
  3. | トラックバック:0
  4. | コメント:0

OpenGL ES 2.0 その03

前回、シェーダプログラムを設定するところまでやりました。
今回でやっと描画です。といっても、トライアングルを出すだけですが。
今回はDirectXで言うところのDrawPrimitiveUP()系の解説になるので注意してください。

DirectXの場合、シェーダプログラムを利用する時にVertexDeclarationを作成する必要がありました。
GLESの場合は作成の必要はありませんが、多分それが内部でやっていることと同じようなことをする必要があります。
すなわち、頂点データとシェーダプログラム内の変数を対応させなければならないわけです。

まず、頂点データを用意します。
前回解説した頂点シェーダの入力は座標のみなので、3次元ベクトル3つを配列で確保します。

GLfloat  pfVertices[] =
{
    -0.4f, -0.4f, 0.0f,
    +0.4f, -0.4f, 0.0f,
      0.0f,  0.4f, 0.0f
};

頂点データと頂点シェーダ入力の対応付けは、シェーダプログラムから入力変数の位置を取得->その属性を有効に->データのポインタを設定、と言う順番で行われます。

// 頂点座標属性の位置を取得
GLuint nAttLoc = glGetAttribLocation( hProgram, "myVertex" );
// 頂点座標属性を有効に
glEnableVertexAttribArray( nAttLoc );
// 頂点座標属性のポインタを設定する
glVertexAttribPointer( nAttLoc, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, pfVertices );

入力変数の位置は glGetAttribLocation() 関数で取得できます。プログラムハンドルと変数名を指定します。
なお、シェーダプログラム内で attribute で宣言されている変数が頂点入力です。
glEnableVertexAttribArray() で有効にしてからポインタを設定します。
glVertexAttribPointer() は頂点データのポインタを指定します。
引数は順番に、変数の位置、頂点1つあたりの要素数、フォーマット、正規化するかどうかのフラグ、ストライド、頂点データのポインタとなります。
要素数は頂点1つあたりのもので、3Dベクトルなら3となります。
正規化するかどうかのフラグは主に法線で用います。
ストライドは頂点データの先頭から次の頂点データの先頭までのバイト数です。今回はfloatの3Dベクトルなので、sizeof(float) * 3 となります。
頂点データの他の入力変数を設定する場合、そのデータの配列は別々に作成してもかまいません。
DirectXでは頂点ストリームを複数使う方法もありますが、通常は頂点の各情報を1つの構造体にまとめています。
GLESでも同じような方法は可能ですが、頂点データのポインタは該当データの先頭アドレスを指定しなければなりません。

次にプログラムのグローバル変数を設定します。プログラムの中で uniform で宣言されている変数です。
これも頂点入力と同じ要領ですが、有効・無効の設定は必要ありません。

// 変数の位置を取得する
GLuint nMtxLoc = glGetUniformLocation( hProgram, "myPMVMatrix" );
// 変数を設定する
glUniformMatrix4fv( nMtxLoc, 1, GL_FALSE, pfIdentity );

glUniformMatrix4fv() 関数は名前からもわかるとおり4*4行列を設定する関数です。
引数は、変数の位置、設定する行列の数、転置するかどうかのフラグ、行列です。
当然、他にもベクトルを設定したりする関数もあります。

こうして頂点入力と変数を設定したらいよいよ描画です。といっても、一行だけです。

glDrawArrays( GL_TRIANGLES, 0, 3 );

引数は、プリミティブタイプ、使用する頂点の開始位置、使用する頂点の数となります。
DirectXでは描画するプリミティブの数を引数として渡していました。
つまり、TriangleListで三角形を1つ描画したいなら1を渡す必要がありました。
GLESでは使用する頂点の数です。TriangleListなら3nを指定する必要があるわけです。

上記の例はインデックスを用いない方法ですが、当然使用することは可能です。

glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, pIndices );

インデックスを使用する場合、glDrawElements()を使います。
引数は、プリミティブタイプ、使用する頂点の数、インデックスフォーマット、インデックスデータとなります。
使用する頂点の数は glDrawArrays() と同じです。

これで、前回までのプログラムとあわせて三角形が表示されたはずです。
たったこれだけのことをやるのに3回も使って解説するとは、ちょっと効率悪いですかね。

  1. 2009/03/03(火) 00:22:24|
  2. プログラミング
  3. | トラックバック:0
  4. | コメント:0

プロフィール

Author:monsho
ゲームプログラマ?

最近の記事

最近のコメント

最近のトラックバック

月別アーカイブ

カテゴリー

ブロとも申請フォーム

この人とブロともになる

ブログ内検索

RSSフィード

リンク

このブログをリンクに追加する

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。