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

iPhone 3GS/US15W (GMA500) PowerVR SGX 535 の頂点性能

簡単なテストです。
出来るだけ描画面積を小さくしてほぼ頂点演算のみ。
単一マテリアルかつ共有頂点、48000ポリゴンの最適化していない素のモデルデータを
描画しています。Indexed + Triangle List で Strip 化や頂点キャッシュ用ソートを
していません。

VAIO Type P で描画してみました。
当初 OpenGL を使おうとしましたがドライバが未対応でした(↓)。

GL_VERSION: 1.1.0
GL_RENDERER: GDI Generic
GL_VENDOR: Microsoft Corporation
GL_SHADING_LANGUAGE_VERSION: (null)

D3D11 の CapsViewer で調べると Direct3D 11 の FEATURE_LEVEL_9 も未対応。
Direct3D 10 の FeatureLevel 9_1 には対応しています。
今回は昔作った Direct3D 9 のツール&シェーダーを使っています。
シェーダーも複数の機能を盛り込んで汎用化したもので、あまり最適化されていません。

・Atom Z540 + GMA500
・Windows7 RC で Aero off

この条件で上記モデルデータを 4個描画しておよそ 38fps。
結果だけ見ると頂点演算は 7.3M triangles/sec くらい。

Wikipedia PowerVR によると GMA500 は PowerVR SGX535 で 28M poly/s 。
記載されているピーク値の 1/4 くらいですが最初はこんなもんでしょう。

モデルを拡大して描画面積を広げるとあっという間に処理落ちします。
Unified シェーダーということもあって、おそらくピーク値は極端な値を示す傾向があると
考えられます。つまり実際のアプリケーションで使う場合、シェーダーユニットをピクセルにも
割り振る必要があるので、その分だけ数値は落ちます。
こんなにいい加減なテストでも PowerVR MBX Lite の性能値として記載されている頂点
演算速度よりはおそらく高速です。頂点だけに絞ればかなりの差が付きそうです。

また iPhone 3GS + GL ES 2.0 で同じモデルデータを描画したところほとんど同じ
結果になりました。4個描画時に 40fps、7.68M tri/s くらいです。
こちらの方が多少簡略化したフラグメントシェーダーを用いており、厳密に同一条件では
ありませんが、ピクセルの影響は少ないのでほぼ同じとみて良さそうです。

どちらも同じ SGX 535 相当であることが結果からも明らかになりました。
つまり Type P は、iPhone 3GS や iPod touch 3G とほとんど同じ能力の GPU で
8倍の面積を描画していることになります。

ちなみに GeForce/RADEON などデスクトップ PC 向け GPU だと、上記モデルは
100~150個 * 60fps 出ます。GPU グレード間の差が付かないので、ハイエンドだと
このテストは低負荷すぎるようです。

関連エントリ
VAIO type P + Windows7 RC で Direct3D 11
Intel GMA500 のスペックについて考える。続き (2)

OpenGL や GLSL の互換性

Direct3D の HLSL コンパイラは Microsoft が用意しており、共通のバイトコードを
生成しています。
実行時はさらにドライバが GPU 毎のネイティブコードに最適化&変換を行っています。

一見二度手間ですが、最初の最適化は共通バイトコードへの変換時に済んでいます。
時間のかかる巨大なシェーダーでも事前に変換しておくことが可能だし、コンパイラ部の
最適化の恩恵はすべての GPU で受けられます。
ドライバも共通コードからの変換だけ行えばよいので、互換性も比較的維持しやすいのでは
ないかと考えられます。

その代わり一度バイナリにしてしまうと、コンパイルした時点のコンパイラの能力である程度
固定されます。あとからコンパイラが強化される可能性もあるし、GPU が ShaderModel 5.0
対応になっても、3.0 向けにコンパイルしたシェーダーで能力を出し切れるとは限りません。

OpenGL の GLSL の場合 API が受け取るのは生のソースコードです。
コンパイルそのものが GPU ドライバの役目となっていて、中間の共通コードがありません。
動的にコンパイルするメリットは常にハードに最適なプロファイルを選べることです。

逆にデメリットとしては、コンパイル時間がかかるのではないかとの懸念もありますが
もっと別の問題もあるようです。
今まで GeForce で組み立ててきたコードを RADEON で走らせてみました。
環境は下記の通り。

GeForce 9800GT 190.62 (GL 3.1 GLSL 1.4) WHQL
GeForce GTX280 190.57 (GL 3.2 GLSL 1.5)
RADEON HD 4760 Catalyst 9.9 (GL 3.1 GLSL 1.4)

● Version 指定

RADEON の GLSL では「#version」はソースコードの先頭にないとエラーになります。

・#version の前にプリプロセッサ命令があるだけでもだめ。
・glShaderSource() に複数のソースコードを渡している場合、最初のコードの先頭でないとだめ。2番目以降のソースコードには記述できない。

この挙動は OpenGLES 2.0 の GLSL と同じです。また GLSLangSpec.Full.1.40.05.pdf
を見ても、#version の前に許されるのはコメントとホワイトスぺースのみと記載されています。
こちらの動作の方が正解でした。

ただプリプロセッサ命令も許されていないので、複数のターゲット間で互換性ととる場合に
version 指定を分岐できないのは不便です。C 言語側の Shader Loader 部分に手を加えて、
独自のプリプロセッサを追加するなどの対策が必要かもしれません。

● precision 指定

OpenGL ES の GLSL から取り入れた仕様として precision 指定があります。
宣言時に変数が必要とする精度のヒントを与えます。
highp, middlep, lowp の 3段階ありますが

・GeForce は in/out 宣言の前に必要
・RADEON は in/out 宣言の後ろ書かないとエラー

RADEON の場合必ず何らかの精度指定が必須で、個別指定かまたはデフォルトの
precision 行が必要です。GeForce は無くても通ります。
とりあえず最初にデフォルト宣言をしておけばどちらでも通ります。
細かく個別に宣言をしている場合は注意。

precision highp float;

ドキュメントを見る限り RADEON の方が正しいようです。
全体的に GeForce の方が判定が緩く RADEON の方が厳密になっています。

GPU のドライバ毎にコンパイラの仕様が異なっている可能性を考えると、
Direct3D + HLSL のように共通化されている方が楽だと思います。
慣れるまではこれらの互換性の維持に苦労しそうです。

●OpenGL 3.1

現段階で OpenGL 3.2/GLSL 1.5 に対応しているのは GeForce 190.57 だけです。
RADEON で試すにあたって OpenGL 3.1/GLSL 1.4 で動作するように修正しました。

GeForce の場合、最初に wglCreateContext() で作った Context がすでに
OpenGL 3.x に対応していました。
wglCreateContextAttribsARB() で作り直さなくても動作します。
RADEON の場合 OpenGL 2.1 だったので、wglCreateContextAttribsARB() が必要です。
でもシェーダーバージョンは同じ。

// GeForce 190.57
// wglCreateContext()
GL VERSION: 3.2.0
GL RENDERER: GeForce GTX 280/PCI/SSE2
GL VENDOR: NVIDIA Corporation
GL SHADING_LANGUAGE_VERSION: 1.50 NVIDIA via Cg compiler
 ↓
// wglCreateContextAttribsARB()
GL VERSION: 3.1.0
GL RENDERER: GeForce GTX 280/PCI/SSE2
GL VENDOR: NVIDIA Corporation
GL SHADING_LANGUAGE_VERSION: 1.40 NVIDIA via Cg compiler

// GeForce 190.62
// wglCreateContext()
GL_VERSION: 3.1.0
GL_RENDERER: GeForce 9800 GT/PCI/SSE2
GL_VENDOR: NVIDIA Corporation
GL_SHADING_LANGUAGE_VERSION: 1.40 NVIDIA via Cg compiler
 ↓
// wglCreateContextAttribsARB()
GL_VERSION: 3.1.0
GL_RENDERER: GeForce 9800 GT/PCI/SSE2
GL_VENDOR: NVIDIA Corporation
GL_SHADING_LANGUAGE_VERSION: 1.40 NVIDIA via Cg compiler
// RADEON 9.9
// wglCreateContext()
GL_VERSION: 2.1.8918
GL_RENDERER: ATI Radeon HD 4600 Series
GL_VENDOR: ATI Technologies Inc.
GL_SHADING_LANGUAGE_VERSION: 1.40
 ↓
// wglCreateContextAttribsARB()
GL_VERSION: 3.1.8918
GL_RENDERER: ATI Radeon HD 4600 Series
GL_VENDOR: ATI Technologies Inc.
GL_SHADING_LANGUAGE_VERSION: 1.40

● Extension の判定

GeForce 190.57 を Version 3.1 で作り直した場合のはまりです。
Extension の判定で glGetString( GL_EXTENSIONS ) を参照していましたが、
Context を作り直すとエラーになります。
ドキュメントをよく見ると glGetString( GL_EXTENSIONS ) は古い仕様で、
OpenGL 3.x の場合は

glGetIntegerv( GL_NUM_EXTENSIONS, .. )
glGetStringi( GL_EXTENSIONS, .. )

を用いるのが正しいやり方のようです。
WGL_CONTEXT_FLAGS_ARB を 0 にしても
WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB がセットされてしまうことが
原因のようです。
190.62 では WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB を指定しない限り
大丈夫でした。

互換性周りはまだ慣れてないせいか、または出来るだけ共通で動かそうと欲張っている
せいか苦労しています。
Direct3D の場合問題になる互換性はハードウエア能力の差でした。
その後 Direct3D 10 では完全に足並みが揃って、GeForce も RADEON もほとんど
区別せずに同じシェーダーが動くようになっています。
OpenGL ではハードウエア能力の違いよりも、まだまだドライバの差が表面化している感じです。
API 仕様が決まっても、安定するまでしばらく時間がかかるのかもしれません。

関連エントリ
OpenGL の同期と描画速度の測定
OpenGL のはじめ方 (2) wgl
OpenGL 3.2 GeometryShader をもう少し使ってみる
OpenGL GLSL のバージョン
OpenGL 3.2 の GeometryShader
OpenGL のはじめ方

DirectX SDK August 2009 texconvex のデータ化け

前回書いた BC6H/BC7 のデータ化けですが BC1/DXT1 でも同様の問題が発生する
ことがわかりました。texconvex.exe に何らかの問題があるようです。

DirectX SDK August 2009 付属 texconv.exe と texconvex.exe を、それぞれ
x86 と x64 で比較してみました。

変換スクリプト(バッチ)

set DXSDK_UTILBIN=${DXSDK_DIR}Utilities/bin
set BIN86=$DXSDK_UTILBIN/x86
set BIN64=$DXSDK_UTILBIN/x64

set CONV86=$BIN86/texconv.exe
set CONVEX86=$BIN86/texconvex.exe
set CONV86=$BIN64/texconv.exe
set CONVEX86=$BIN64/texconvex.exe

$CONV86       -ft dds -f DXT1      -m 1 -px dx9_dxt1_86_ t000.bmp
$CONV64       -ft dds -f DXT1      -m 1 -px dx9_dxt1_64_ t000.bmp
$CONVEX86 -10 -ft dds -f BC1_UNORM -m 1 -px dx10_bc1_86_ t000.bmp
$CONVEX64 -10 -ft dds -f BC1_UNORM -m 1 -px dx10_bc1_64_ t000.bmp
$CONVEX86 -11 -ft dds -f BC1_UNORM -m 1 -px dx11_bc1_86_ t000.bmp
$CONVEX64 -11 -ft dds -f BC1_UNORM -m 1 -px dx11_bc1_64_ t000.bmp

hexdump dx9_dxt1_86_t000.dds > dx9_dxt1_86_t000.txt 
hexdump dx9_dxt1_64_t000.dds > dx9_dxt1_64_t000.txt 
hexdump dx10_bc1_86_t000.dds > dx10_bc1_86_t000.txt 
hexdump dx10_bc1_64_t000.dds > dx10_bc1_64_t000.txt 
hexdump dx11_bc1_86_t000.dds > dx11_bc1_86_t000.txt 
hexdump dx11_bc1_64_t000.dds > dx11_bc1_64_t000.txt 

入力は 512×512 サイズの bmp。mipmap 無し。
texconv と texconvex で出力拡張子の大文字小文字がばらばらなのも気になるところ。

 131200  dx9_dxt1_64_t000.dds
 131200  dx9_dxt1_86_t000.dds
 131200  dx10_bc1_64_t000.DDS
 131200  dx10_bc1_86_t000.DDS
 131200  dx11_bc1_64_t000.DDS
 131200  dx11_bc1_86_t000.DDS

出力ファイルサイズはすべて同一です。つまり 512*512*2 = 131072 、
131072 + 128 = 131200 なので、128byte の DDS ヘッダのみ追加されている状態です。
DXT10 ヘッダ (Direct3D 10 拡張ヘッダ) は存在していないことになります。

バイナリを比較すると、texconv を用いた下記 2ファイルは完全に同一です。
使用したコンバータが x86 か x64 だけの違いなので、一致していないと困ります。

・dx9_dxt1_64_t000.dds
・dx9_dxt1_86_t000.dds

texconvex を用いた dx10~, dx11~ の各ファイルはお互いにどれとも一致しませんでした。
それどころかコンバータを実行するたびにバイト単位で相違が生じています。

↓正常なファイル (dx9_dxt1_86_t000.dds)

00000000 :  44 44 53 20 7c 00 00 00 07 10 00 00 00 02 00 00  DDS |...........
00000010 :  00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000020 :  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030 :  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040 :  00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00  ............ ...
00000050 :  04 00 00 00 44 58 54 31 00 00 00 00 00 00 00 00  ....DXT1........
00000060 :  00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00  ................
00000070 :  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000080 :  a7 5b 23 11 d7 3f 0a 8a 49 6c 84 19 5e de a0 ca ここからデータ
00000090 :  74 9d e6 31 fd 3b 2b ed 91 9d e5 29 fa 58 16 d7
000000a0 :  f6 ce c8 42 75 ff a8 af cc 84 e2 21 b7 aa f0 5e
000000b0 :  eb 8c 61 2a c2 ca 7b ea 57 df c2 21 2f ad f5 d5
000000c0 :  92 be e2 29 82 02 a9 55 93 be a0 19 5f fe 0a a2
000000d0 :  57 d7 e2 3a 77 ea a0 a2 32 ae e6 4a 25 95 b8 c2
000000e0 :  79 e7 e8 4a 2a 28 b7 a5 36 d7 26 5b d6 b8 00 e0
000000f0 :  d2 be e3 42 fc e2 e0 b7 4f be 82 32 ff d7 3a f8
00000100 :  cd b5 80 21 a5 ea 5c f7 6d 7c 81 21 aa 22 d5 df

↓問題のファイル (dx11_bc1_86_t000.dds)

00000000 :  44 44 53 20 7c 00 00 00 06 00 00 00 00 02 00 00  DDS |...........
00000010 :  00 02 00 00 00 04 00 00 00 00 00 00 01 00 00 00  ................
00000020 :  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030 :  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040 :  00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00  ............ ...
00000050 :  04 00 00 00 44 58 54 31 04 00 00 00 00 00 00 00  ....DXT1........
00000060 :  00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00  ................
00000070 :  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000080 :  00 00 00 00 00 00 00 00 24 2a 00 0e 24 86 08 00
00000090 :  80 00 4d 02 00 00 00 00 20 da 0d 00 00 00 00 00
000000a0 :  74 9d e6 31 fd 3b 2b ed 91 9d e5 29 fa 58 16 d7
000000b0 :  f6 ce c8 42 75 ff a8 af cc 84 e2 21 b7 aa f0 5e
000000c0 :  eb 8c 61 2a c2 ca 7b ea 57 df c2 21 2f ad f5 d5
000000d0 :  92 be e2 29 82 02 a9 55 93 be a0 19 5f fe 0a a2
000000e0 :  57 d7 e2 3a 77 ea a0 a2 32 ae e6 4a 25 95 b8 c2
000000f0 :  79 e7 e8 4a 2a 28 b7 a5 36 d7 26 5b d6 b8 00 e0
00000100 :  d2 be e3 42 fc e2 e0 b7 4f be 82 32 ff d7 3a f8

・データ本体の先頭 16byte が欠けている
・データ本体の先頭に 32byte のゴミデータが挿入されている
・データ本体が 16byte 下がっている

これはまだましな方です。
dx10_bc1_64_t000.dds の場合 224byte の不明なデータが挿入されていました。
この症状は、以前下記エントリで試した texconv10.exe の頃と変わっていないような気がします。

Direct3D10 と DDS テクスチャフォーマット

ヘッダ部分の相違は下記の通り。こちらは特に問題は無いです。

・texconv (dx9)
dwFlags= 0x00001007 = DDSD_PIXELFORMAT|DDSD_WIDTH|DDSD_HEIGHT|DDSD_CAPS
dwPitchOrLinearSize= 0       (DDSD_LINEARSIZE が無いので無視できる)
dwDepth= 0          (DDSD_DEPTH が無いので無視できる)
dwMipMapCount= 0    (DDSD_MIPMAP が無いので無視できる)

・texconvex (dx11)
dwFlags= 0x00000006 = DDSD_WIDTH|DDSD_HEIGHT
dwPitchOrLinearSize= 0x400   (DDSD_LINEARSIZE が無いので無視できる)
dwDepth= 1          (DDSD_DEPTH が無いので無視できる)
dwMipMapCount= 1    (DDSD_MIPMAP が無いので無視できる)

・共通
dwSize   = 0x007c = 124 = ヘッダサイズ
dwWidth  = 0x0200 = 512
dwHeight = 0x0200 = 512
dwPfSize = 0x0020 = 32  = PIXELFORMAT サイズ
dwPfFlags= 0x0004 = DDPF_FOURCC
dwFourCC = "DXT1"
dwCaps   = 0x1000 = DDSCAPS_TEXTURE

texconvex で出力フォーマットの指定を R8G8B8A8_UNORM にするとデータが全部ゼロで埋められてしまいます。

関連エントリ
DirectX SDK August 2009 の解説と Direct3D 11 RTM
Direct3D10 と DDS テクスチャフォーマット

DirectX SDK August 2009 の解説と Direct3D 11 RTM

DirectX SDK August 2009 が出ています。
ついに Direct3D 11 の RTM 対応となりました。

DirectX SDK (August 2009)

D3D11 自体は Windows 7 や Windows SDK for Windows7 で RTM 版が出ています。
デバッグ向けライブラリや D3DX、ツールを含んだ SDK としてはこれが最初です。
これで安心して Windows7 RTM 環境へ移行できそうです。

●大きなトピック

予想通りベータ版で欠けていた機能が追加されました。
例えば

・Effect (fx)
・BC6/7 対応コンバータ

特に Effect はベータに入っていてもおかしくない大きなコンポーネントです。
もしかして SDK のリリースが遅れた原因もこのあたりでしょうか。

マニュアルの完成度は高く、大幅に更新されているようです。
ドキュメントも March SDK で足りなかった各項目が埋まっており、説明も増えて結構
力が入っています。かなり好印象です。

また今回新たに明らかになった点もいくつかあります。特に次の 2つ

● FeatureLevel 9_3 は ShaderModel 2.0 だった

最初マニュアルの表記ミスかと思いました。
CapsViewer でも確かに 2.0 と表示されるので間違いではないようです。

11_0   sm5.0
10_1   sm4.1
10_0   sm4.0
 9_3   sm2.0   (4_0_level_9_3) << ここ
 9_2   sm2.0   (4_0_level_9_1)
 9_1   sm2.0   (4_0_level_9_1)

● CapsViewer が復活した!!

FeatureLevel 登場時から結局 caps と同じことではないかと心配しましたが、
今回のリリースではそれを正式に認めたようなもの。
本当に CapsViewer に組み込まれてしまいました。

DXCapsViewer.exe を起動すると DXGI 1.1 Devices の欄が追加されています。
それぞれの FeatureLevel 毎に対応機能やフォーマットなど、各ドライバの対応状況を
確認することが出来ます。

d3d11_capsviewer.png

●D3DCSX が追加された

D3DX の ComputeShader 版です。prefix は D3DX のまま。
D3DX*.dll, D3DCompiler*.dll のように、番号付きの dll が今後追加されていく
ことになるようです。

●バージョンと互換性

SDK のメジャー番号は 9 のままです。
厳密には DirectX 9 SDK の中に Direct3D 10 や Direct3D 11 が含まれている
扱いになります。とはいえ、便宜上 DirectX 11 と表記することも多いです。

DirectX だけでなく Direct2D や DirectWrite も含まれています。
Vista でも動作します。

●ID3D11DeviceContext の更新

・追加
ID3D11DeviceContext::CopyStructureCount()

・削除
ID3D11DeviceContext::GetTextFilterSize()
ID3D11DeviceContext::SetTextFilterSize()

以前 DebugLayer が動かなかった原因はこの辺にありそうです。

● D3D11_PRIMITIVE の追加

enum D3D11_PRIMITIVE が追加されています。
似たようなシンボルとしてすでに D3D11_PRIMITIVE_TOPOLOGY が存在しています。

定義を見ると STRIP 形式が外されているようです。D3D11_PRIMITIVE は唯一
D3D11_SHADER_DESC の中で用いられています。考えられる用途は下記の通りです。

・D3D11_PRIMITIVE_TOPOLOGY ( strip あり )
  IA や SO など strip 形式を含めたプリミティブの指定。

・D3D11_PRIMITIVE ( strip 無し )
  GeometryShader / HullShader など、頂点ストリームやキャッシュを通過し、
  Triangle (Primitive) 単位で入力が必要なシェーダーの指定で用いる。

● BC6H/BC7 texture

新しい圧縮テクスチャフォーマットです。繰り返しになりますが、従来 DXT と
呼ばれていたフォーマットは Direct3D 10 より BC に名称が変わっています。

BC1 4bpp ← DXT1
BC2 8bpp ← DXT2/3
BC3 8bpp ← DXT4/5
BC4 4bpp ← 1チャンネル圧縮
BC5 8bpp ← 2チャンネル圧縮, 法線圧縮 (3Dc/ATI2)
BC6 8bpp ← (BC6H_UF16, BC6H_SF16)
BC7 8bpp ← (BC7_UNORM, BC7_UNORM_SRGB)

付属のコマンドラインツール texconvex.exe を使うと実際に BC6H/BC7 で圧縮できる
ようになりました。DxTex.exe などそれ以外のツールは Direct3D 9 のテクスチャしか
扱えないので要注意です。

実際に試しましたが、圧縮は非常に低速です。
最初 1600×1200 のデータを渡してしまってかなり後悔しました。
結局中断して 640×480 でやり直したくらい。それでも結構待たされています。
Core i7 920 なのに。

リファレンスデバイスを指定したアプリを作れば一応読み込むことが出来ました。
ただデータの一部が化けており正しく出ていない可能性があります。
DXT10 形式の dds データ化けは以前もあったので、使い方のミスかツール側の
問題かもしれません。

DXT10 ヘッダを追加した dds テクスチャは D3D10 で登場したものの、対応したツール
はまだあまり出ていません。128byte + 20byte と中途半端なヘッダサイズになって
しまうため、一括ロードだと SSE 対応も難しいという欠点があります。

●残るは GPU (とドライバ)

とりあえず 9_3 の Shader Model 2.0 と、DXCapsViewer には驚きました。

Direct3D 8 の改革が Direct3D 9 で完成したように、
Direct3D 10 で目指したものの完成形が Direct3D 11 だといえるでしょう。

手持ちのコードは March 2009 から Direct3D 11 に移行しましたが、互換性も取れる
ようになったし、かなり使いやすい印象です。
あとは対応 GPU が発売されるのを待つばかり。

関連エントリ
Direct3D11 Windows7 RTM と DebugLayer
Direct3D11/DirectX11 ComputeShader 4.0 を使う
DirectX SDK March 2009
Gamefest2008 と Direct3D 11
Direct3D10 と DDS テクスチャフォーマット

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