2009/09/30
NetWalker PC-Z1 Cortex-A8 (ARM) 浮動小数演算の実行速度
先日のテストでは Atom に匹敵する実行速度を出していながら、浮動小数演算では
大きく差をつけられる結果となりました。
もう少し詳しく調べたところ、いろいろわかってきました。
結論は
・VFP が遅い
・NEON の SIMD 2/4 を使えばかなり速い
・NEON の単精度スカラー同士の演算は VFP なので遅い
今回は直接命令毎の実行速度を測ってみます。例えば整数乗算なら下記のような感じで。
省略していますが実際には mul 命令を 40 個並べています。
これを 100M 回 (1億) ループします。
各種演算命令を並べてテストした結果が下記の通りです。
4000M 命令の実行時間の実測値 (秒) です。
命令はパイプラインがストールしないよう、意味がないけど依存が発生しない組み合わせで
並べています。
整数演算は mul でおよそ 392M命令/sec です。add の場合は 775M 命令なので、
ほぼ 1命令/cycle に近い数値で実行出来ていることがわかります。
問題の VFP による浮動小数演算は乗算で 5倍、add 比で 9倍も遅くなっています。
倍精度の乗算でも 10% ほどしか増えていないので、オーバーヘッドは別のところに
あるのかもしれません。
丸めモードや Flush-to-Zero モードなど、FPSCR も書き換えてみましたが結果は同じでした。
なお乗算する値が 0 の場合のみ、fmuls は 3割ほど速い速度で完了します。
対照的に NEON がかなり高速です。4要素(128bit) の SIMD 演算であるにもかかわらず
整数演算と同速度で実行しており、2要素(64bit) の SIMD の場合はむしろ整数演算よりも
高速です。ほぼ 1cycle 毎に 2 float の演算をしています。
さらに積和演算が可能なので、SIMD で vmla を用いた場合
40命令 × 100Mループ × 4 (SIMD) × 2 (積和) / 10sec = およそ 3.2G FLOPS
となります。
ではなぜ普通にコンパイルしたプログラムの浮動小数演算が遅いのかと言えば、NEON の
スカラー演算命令は VFP と共有されているからです。
sレジスタ を用いた演算は、NEON ではなく VFP で実行されます。
実際に vmul.f32 s,s,s は fmuls s,s,s と全く同じバイトコードでした。
コンパイラは一般の浮動小数演算では VFP 命令を出力しています。
結局最初に書いたとおり VFP が非常に遅いという結論になります。
そのためスカラー演算であっても 2/4 ベクタとみなして NEON 命令を使った方が
高速になるわけです。
例えば fmuls s,s,s → vmul.f32 d,d,d なら 10 倍です。
実際のアプリケーションで使ったわけではありませんが、周辺を考えなければ Atom 比でも
結構良い結果になるのではないでしょうか。
これが iPod touch 3G/iPhone 3GS の大きさにも収まっていると考えればなおさらです。
vmla は破壊代入するため、他の命令のように同じオペランドのまま並べると依存が発生して
ストールします。5倍ほど遅くなるので、このテストでは代入側レジスタを 5個用いて
インターリーブしています。quad 時スループット 2 のレイテンシ 10 くらいでしょうか。
関連エントリ
・NetWalker PC-Z1 Atom と速度比較
・NetWalker PC-Z1 Bluetooth とキーカスタムその他
・NetWalker PC-Z1 意外にいける
・Netwalker PC-Z1 買いました
・タッチタイプの境界
・NetWalker PC-Z1
大きく差をつけられる結果となりました。
もう少し詳しく調べたところ、いろいろわかってきました。
結論は
・VFP が遅い
・NEON の SIMD 2/4 を使えばかなり速い
・NEON の単精度スカラー同士の演算は VFP なので遅い
今回は直接命令毎の実行速度を測ってみます。例えば整数乗算なら下記のような感じで。
省略していますが実際には mul 命令を 40 個並べています。
これを 100M 回 (1億) ループします。
static void Start_ASMINT_MUL() { TimerClass timer; timer.Begin(); __asm__ __volatile__( "\ mov r2, #123 \n\ " : : : "r2","cc" ); for( int i= 0 ; i< VECTOR_LOOP ; i++ ){ __asm__ __volatile__( "\ mul r3, r2, r2 \n\ mul r3, r2, r2 \n\ ~ " : : : "r3","cc" ); } timer.End( "ASMINT_MUL" ); }
各種演算命令を並べてテストした結果が下記の通りです。
4000M 命令の実行時間の実測値 (秒) です。
i.MX515 Cortex-A8 (ARM) 800MHz 命令 実行時間 演算ユニットと演算単位 --------------------------------------------------------------- mul r,r,r 10.21 sec ALU 32bit add r,r,r 5.16 sec ALU 32bit fmuls s,s,s 50.45 sec VFP 32bit fadds s,s,s 45.45 sec VFP 32bit fmuld d,d,d 55.49 sec VFP 64bit vmul.f32 s,s,s 50.45 sec VFP 32bit vmul.f32 q,q,q 10.07 sec NEON 128bit (32bit x4) vadd.f32 q,q,q 10.08 sec NEON 128bit (32bit x4) vmla.f32 q,q,q 10.09 sec NEON 128bit (32bit x4) 積和 vmul.f32 d,d,d 5.04 sec NEON 64bit (32bit x2)
命令はパイプラインがストールしないよう、意味がないけど依存が発生しない組み合わせで
並べています。
整数演算は mul でおよそ 392M命令/sec です。add の場合は 775M 命令なので、
ほぼ 1命令/cycle に近い数値で実行出来ていることがわかります。
問題の VFP による浮動小数演算は乗算で 5倍、add 比で 9倍も遅くなっています。
倍精度の乗算でも 10% ほどしか増えていないので、オーバーヘッドは別のところに
あるのかもしれません。
丸めモードや Flush-to-Zero モードなど、FPSCR も書き換えてみましたが結果は同じでした。
なお乗算する値が 0 の場合のみ、fmuls は 3割ほど速い速度で完了します。
対照的に NEON がかなり高速です。4要素(128bit) の SIMD 演算であるにもかかわらず
整数演算と同速度で実行しており、2要素(64bit) の SIMD の場合はむしろ整数演算よりも
高速です。ほぼ 1cycle 毎に 2 float の演算をしています。
さらに積和演算が可能なので、SIMD で vmla を用いた場合
40命令 × 100Mループ × 4 (SIMD) × 2 (積和) / 10sec = およそ 3.2G FLOPS
となります。
ではなぜ普通にコンパイルしたプログラムの浮動小数演算が遅いのかと言えば、NEON の
スカラー演算命令は VFP と共有されているからです。
sレジスタ を用いた演算は、NEON ではなく VFP で実行されます。
実際に vmul.f32 s,s,s は fmuls s,s,s と全く同じバイトコードでした。
コンパイラは一般の浮動小数演算では VFP 命令を出力しています。
結局最初に書いたとおり VFP が非常に遅いという結論になります。
そのためスカラー演算であっても 2/4 ベクタとみなして NEON 命令を使った方が
高速になるわけです。
例えば fmuls s,s,s → vmul.f32 d,d,d なら 10 倍です。
実際のアプリケーションで使ったわけではありませんが、周辺を考えなければ Atom 比でも
結構良い結果になるのではないでしょうか。
これが iPod touch 3G/iPhone 3GS の大きさにも収まっていると考えればなおさらです。
vmla は破壊代入するため、他の命令のように同じオペランドのまま並べると依存が発生して
ストールします。5倍ほど遅くなるので、このテストでは代入側レジスタを 5個用いて
インターリーブしています。quad 時スループット 2 のレイテンシ 10 くらいでしょうか。
関連エントリ
・NetWalker PC-Z1 Atom と速度比較
・NetWalker PC-Z1 Bluetooth とキーカスタムその他
・NetWalker PC-Z1 意外にいける
・Netwalker PC-Z1 買いました
・タッチタイプの境界
・NetWalker PC-Z1
2009/09/28
GeForce ドライバ 191.03 beta
GeForce のドライバ 191.03 (beta) が出ています。
やっと OpenGL 3.2 と Compute Shader 4.0 の共存が出来るようになりました。
どちらも動いています。
・GEFORCE/ION DRIVER RELEASE 191
この辺 RADEON はまだ対応していませんが、すでに DirectX11 が動く 5870 が発売されました。
ShaderModel とか Compte Shader の対応とか非常に興味あるけど
週末 RADEON HD 5870 はすでに売り切れでした。
関連エントリ
・OpenGL や GLSL の互換性
・Direct3D11/DirectX11 GeForce の ComputeShader とドライバ
やっと OpenGL 3.2 と Compute Shader 4.0 の共存が出来るようになりました。
どちらも動いています。
・GEFORCE/ION DRIVER RELEASE 191
この辺 RADEON はまだ対応していませんが、すでに DirectX11 が動く 5870 が発売されました。
ShaderModel とか Compte Shader の対応とか非常に興味あるけど
週末 RADEON HD 5870 はすでに売り切れでした。
関連エントリ
・OpenGL や GLSL の互換性
・Direct3D11/DirectX11 GeForce の ComputeShader とドライバ
2009/09/23
RADEON HD 5870 と DirectX 11
Direct3D 11 対応 GPU RADEON HD 5870/5850 が発表されました。
Windows7 発売に間に合うし、Direct3D 11 SDK も直前に RTM しています。
足並みが揃った良いタイミングです。
あとは安定して入手できるかどうか、ドライバが間に合うかどうかでしょう。
・AMD、業界初DirectX 11対応GPU「Radeon HD 5800」シリーズ ~上位モデルは1,600SP搭載の2.7TFLOPS
最近は GPU の世代が代わり 機能が増えても一時的なパフォーマンスの低下は見られなく
なりました。GeForce 6800 や 8800 の時もそうでしたがむしろ倍のスコアを出しています。
速度を求める場合も機能が目的でも、安心して手を出せるのは嬉しいところです。
Direct3D 10 → Direct3D 11 の更新は API 的なデザインの変更は少ないものの、
Direct3D 8 → Direct3D 9 と同じように増えた機能はかなりあります。
・汎用性の向上 Compute Shader
・グラフィックス機能の進化 Tessellation, BC6H/BC7
・管理機能の強化 Dynamic Shader Linkage
・スレッド対応 Deferred Context
等
一見地味な Dynamic Shader Linkage や Deferred Context も、開発者にとっては
大変嬉しい機能です。
Dynamic Shader Linkage は描画発行時にシェーダー内の飛び先を決定することが出来ます。
いわば関数ポインタのようなもので、シェーダーの種類を増やさずに機能の切り替えを
容易にしてくれます。
Deferred Context は他のスレッドが発行した描画命令を Display List としてバッファリングし、
コンテキストに矛盾が生じないようにします。
シェーダーの機能切り替えも効率よいスレッドの活用も、これまではアプリケーション側で工夫して
実現する必要がありました。
つまり DirectX 11 では従来 CPU 側で行ってきたいくつかの分野もアクセラレーションの
対象となっているといえます。GPGPU に限らず GPU の役割は徐々に広がっているようです。
今後 CPU/GPU もお互いに相手の機能を取り込むだけではなく、より連携しやすいような歩み寄りも
必要ではないでしょうか。例えば CPU にも積極的に GPU 連携のための命令が追加されるとか。
SSE5 の half 型変換命令はその一つの好例かもしれません。
DirectX/GPU 年表 も更新しました。
関連エントリ
・DirectX SDK August 2009 の解説と Direct3D 11 RTM
・Direct3D11/DirectX11 (18) GPU を使ったアウトラインフォントの描画の(6)
・Direct3D11/DirectX11 (13) TessFactor とシェーダーリンクの補足など
・Direct3D11/DirectX11 (12) テセレータのレンダーステート他
Windows7 発売に間に合うし、Direct3D 11 SDK も直前に RTM しています。
足並みが揃った良いタイミングです。
あとは安定して入手できるかどうか、ドライバが間に合うかどうかでしょう。
・AMD、業界初DirectX 11対応GPU「Radeon HD 5800」シリーズ ~上位モデルは1,600SP搭載の2.7TFLOPS
最近は GPU の世代が代わり 機能が増えても一時的なパフォーマンスの低下は見られなく
なりました。GeForce 6800 や 8800 の時もそうでしたがむしろ倍のスコアを出しています。
速度を求める場合も機能が目的でも、安心して手を出せるのは嬉しいところです。
Direct3D 10 → Direct3D 11 の更新は API 的なデザインの変更は少ないものの、
Direct3D 8 → Direct3D 9 と同じように増えた機能はかなりあります。
・汎用性の向上 Compute Shader
・グラフィックス機能の進化 Tessellation, BC6H/BC7
・管理機能の強化 Dynamic Shader Linkage
・スレッド対応 Deferred Context
等
一見地味な Dynamic Shader Linkage や Deferred Context も、開発者にとっては
大変嬉しい機能です。
Dynamic Shader Linkage は描画発行時にシェーダー内の飛び先を決定することが出来ます。
いわば関数ポインタのようなもので、シェーダーの種類を増やさずに機能の切り替えを
容易にしてくれます。
Deferred Context は他のスレッドが発行した描画命令を Display List としてバッファリングし、
コンテキストに矛盾が生じないようにします。
シェーダーの機能切り替えも効率よいスレッドの活用も、これまではアプリケーション側で工夫して
実現する必要がありました。
つまり DirectX 11 では従来 CPU 側で行ってきたいくつかの分野もアクセラレーションの
対象となっているといえます。GPGPU に限らず GPU の役割は徐々に広がっているようです。
今後 CPU/GPU もお互いに相手の機能を取り込むだけではなく、より連携しやすいような歩み寄りも
必要ではないでしょうか。例えば CPU にも積極的に GPU 連携のための命令が追加されるとか。
SSE5 の half 型変換命令はその一つの好例かもしれません。
DirectX/GPU 年表 も更新しました。
関連エントリ
・DirectX SDK August 2009 の解説と Direct3D 11 RTM
・Direct3D11/DirectX11 (18) GPU を使ったアウトラインフォントの描画の(6)
・Direct3D11/DirectX11 (13) TessFactor とシェーダーリンクの補足など
・Direct3D11/DirectX11 (12) テセレータのレンダーステート他
2009/09/16
Mobile GPU
HYPERでんちの GPU 年表更新しました。
ほとんどが Mobile デバイスの追加です。
2009年になって一気に Shader 対応端末が普及したことがわかります。
この流れを推し進めているは OpenGL ES 2.0 。
OpenGL ES 2.0 は Direct3D 10 以降のように Shader パイプラインしか定義しておらず
GL ES 2.0 対応であればシェーダーベースの GPU であることがわかります。
デバイスの機能的にもシェーダーに進むそれなりの理由があるのでしょう。
例えば一部 GPU はすでにユニファイドシェーダーを採用しています。
ShaderModel 4.0 にかなり近いか、最初から 4.0 に対応し得る設計です。
シェーダーユニットの再利用は実行効率的にも優れており、ユニットの個数で
スケーラビリティを保ちやすいなどのメリットが考えられます。
また CPU の補助として GPGPU 的な使い方も視野に入れているようです。
他にも先行しているデスクトップ向け 3D ゲームの移植しやすさや同等のテクニックが
使えること、デスクトップ向け GPU の設計を流用できる、バス帯域の制約から演算の
比重を高めるなど、考えられる理由があります。
表を見ているとかつてのデスクトップ向け 3D アクセラレータが多数登場し、
各社多種のビデオカードがしのぎを削っていた頃を思い出します。
ほぼ 2強で占められたハイエンドと違い、様々な GPU が使われているようです。
電力も面積も限られているモバイル向け GPU にとってバス帯域は大きな問題です。
タイルベースで深度判定のみ先行し、最小限のフラグメントのみレンダリングする
PowerVR と同じように、AMD もチップ内の少量かつ高速な RAM を用いたタイルベース
レンダリングを行っているようです。この辺の工夫も非常に興味深いところです。
WindowsVista/7 のウィンドウが GPU 描画になったのと同じように、Mobile デバイス
でも 3D アクセラレータを用いた表現が重要となっています。
一般のアプリケーションも当たり前のように GPU を使っているなんて
3D アクセラレータが乗っていなかったり、乗っていてもドライバが無くて使えなかった
昔からは考えられないことです。
とはいえ今時の CPU は速いので、MI-Zaurus + CPU だけでレンダリングしていた頃を
考えれば CPU でも十分描画できるのかもしれません。
・GPU 年表
・Direct3D Mobile DeviceCaps 一覧
・Zaurus GA 3D Engine
ほとんどが Mobile デバイスの追加です。
2009年になって一気に Shader 対応端末が普及したことがわかります。
この流れを推し進めているは OpenGL ES 2.0 。
OpenGL ES 2.0 は Direct3D 10 以降のように Shader パイプラインしか定義しておらず
GL ES 2.0 対応であればシェーダーベースの GPU であることがわかります。
デバイスの機能的にもシェーダーに進むそれなりの理由があるのでしょう。
例えば一部 GPU はすでにユニファイドシェーダーを採用しています。
ShaderModel 4.0 にかなり近いか、最初から 4.0 に対応し得る設計です。
シェーダーユニットの再利用は実行効率的にも優れており、ユニットの個数で
スケーラビリティを保ちやすいなどのメリットが考えられます。
また CPU の補助として GPGPU 的な使い方も視野に入れているようです。
他にも先行しているデスクトップ向け 3D ゲームの移植しやすさや同等のテクニックが
使えること、デスクトップ向け GPU の設計を流用できる、バス帯域の制約から演算の
比重を高めるなど、考えられる理由があります。
表を見ているとかつてのデスクトップ向け 3D アクセラレータが多数登場し、
各社多種のビデオカードがしのぎを削っていた頃を思い出します。
ほぼ 2強で占められたハイエンドと違い、様々な GPU が使われているようです。
電力も面積も限られているモバイル向け GPU にとってバス帯域は大きな問題です。
タイルベースで深度判定のみ先行し、最小限のフラグメントのみレンダリングする
PowerVR と同じように、AMD もチップ内の少量かつ高速な RAM を用いたタイルベース
レンダリングを行っているようです。この辺の工夫も非常に興味深いところです。
WindowsVista/7 のウィンドウが GPU 描画になったのと同じように、Mobile デバイス
でも 3D アクセラレータを用いた表現が重要となっています。
一般のアプリケーションも当たり前のように GPU を使っているなんて
3D アクセラレータが乗っていなかったり、乗っていてもドライバが無くて使えなかった
昔からは考えられないことです。
とはいえ今時の CPU は速いので、MI-Zaurus + CPU だけでレンダリングしていた頃を
考えれば CPU でも十分描画できるのかもしれません。
・GPU 年表
・Direct3D Mobile DeviceCaps 一覧
・Zaurus GA 3D Engine
簡単なテストです。
出来るだけ描画面積を小さくしてほぼ頂点演算のみ。
単一マテリアルかつ共有頂点、48000ポリゴンの最適化していない素のモデルデータを
描画しています。Indexed + Triangle List で Strip 化や頂点キャッシュ用ソートを
していません。
VAIO Type P で描画してみました。
当初 OpenGL を使おうとしましたがドライバが未対応でした(↓)。
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)
出来るだけ描画面積を小さくしてほぼ頂点演算のみ。
単一マテリアルかつ共有頂点、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)
2009/09/14
OpenGL や GLSL の互換性
Direct3D の HLSL コンパイラは Microsoft が用意しており、共通のバイトコードを
生成しています。
実行時はさらにドライバが GPU 毎のネイティブコードに最適化&変換を行っています。
一見二度手間ですが、最初の最適化は共通バイトコードへの変換時に済んでいます。
時間のかかる巨大なシェーダーでも事前に変換しておくことが可能だし、コンパイラ部の
最適化の恩恵はすべての GPU で受けられます。
ドライバも共通コードからの変換だけ行えばよいので、互換性も比較的維持しやすいのでは
ないかと考えられます。
その代わり一度バイナリにしてしまうと、コンパイルした時点のコンパイラの能力である程度
固定されます。あとからコンパイラが強化される可能性もあるし、GPU が ShaderModel 5.0
対応になっても、3.0 向けにコンパイルしたシェーダーで能力を出し切れるとは限りません。
OpenGL の GLSL の場合 API が受け取るのは生のソースコードです。
コンパイルそのものが GPU ドライバの役目となっていて、中間の共通コードがありません。
動的にコンパイルするメリットは常にハードに最適なプロファイルを選べることです。
逆にデメリットとしては、コンパイル時間がかかるのではないかとの懸念もありますが
もっと別の問題もあるようです。
今まで GeForce で組み立ててきたコードを RADEON で走らせてみました。
環境は下記の通り。
● 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 は無くても通ります。
とりあえず最初にデフォルト宣言をしておけばどちらでも通ります。
細かく個別に宣言をしている場合は注意。
ドキュメントを見る限り 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() が必要です。
でもシェーダーバージョンは同じ。
● Extension の判定
GeForce 190.57 を Version 3.1 で作り直した場合のはまりです。
Extension の判定で glGetString( GL_EXTENSIONS ) を参照していましたが、
Context を作り直すとエラーになります。
ドキュメントをよく見ると glGetString( GL_EXTENSIONS ) は古い仕様で、
OpenGL 3.x の場合は
を用いるのが正しいやり方のようです。
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 のはじめ方
生成しています。
実行時はさらにドライバが 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 のはじめ方
2009/09/13
DirectX SDK August 2009 texconvex のデータ化け
前回書いた BC6H/BC7 のデータ化けですが BC1/DXT1 でも同様の問題が発生する
ことがわかりました。texconvex.exe に何らかの問題があるようです。
DirectX SDK August 2009 付属 texconv.exe と texconvex.exe を、それぞれ
x86 と x64 で比較してみました。
変換スクリプト(バッチ)
入力は 512x512 サイズの bmp。mipmap 無し。
texconv と texconvex で出力拡張子の大文字小文字がばらばらなのも気になるところ。
出力ファイルサイズはすべて同一です。つまり 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)
↓問題のファイル (dx11_bc1_86_t000.dds)
・データ本体の先頭 16byte が欠けている
・データ本体の先頭に 32byte のゴミデータが挿入されている
・データ本体が 16byte 下がっている
これはまだましな方です。
dx10_bc1_64_t000.dds の場合 224byte の不明なデータが挿入されていました。
この症状は、以前下記エントリで試した texconv10.exe の頃と変わっていないような気がします。
・Direct3D10 と DDS テクスチャフォーマット
ヘッダ部分の相違は下記の通り。こちらは特に問題は無いです。
texconvex で出力フォーマットの指定を R8G8B8A8_UNORM にするとデータが全部ゼロで埋められてしまいます。
関連エントリ
・DirectX SDK August 2009 の解説と Direct3D 11 RTM
・Direct3D10 と DDS テクスチャフォーマット
ことがわかりました。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
入力は 512x512 サイズの 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 テクスチャフォーマット
2009/09/11
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 と表示されるので間違いではないようです。
● CapsViewer が復活した!!
FeatureLevel 登場時から結局 caps と同じことではないかと心配しましたが、
今回のリリースではそれを正式に認めたようなもの。
本当に CapsViewer に組み込まれてしまいました。
DXCapsViewer.exe を起動すると DXGI 1.1 Devices の欄が追加されています。
それぞれの FeatureLevel 毎に対応機能やフォーマットなど、各ドライバの対応状況を
確認することが出来ます。

●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 に名称が変わっています。
付属のコマンドラインツール texconvex.exe を使うと実際に BC6H/BC7 で圧縮できる
ようになりました。DxTex.exe などそれ以外のツールは Direct3D 9 のテクスチャしか
扱えないので要注意です。
実際に試しましたが、圧縮は非常に低速です。
最初 1600x1200 のデータを渡してしまってかなり後悔しました。
結局中断して 640x480 でやり直したくらい。それでも結構待たされています。
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 テクスチャフォーマット
ついに 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 毎に対応機能やフォーマットなど、各ドライバの対応状況を
確認することが出来ます。

●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 のテクスチャしか
扱えないので要注意です。
実際に試しましたが、圧縮は非常に低速です。
最初 1600x1200 のデータを渡してしまってかなり後悔しました。
結局中断して 640x480 でやり直したくらい。それでも結構待たされています。
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 テクスチャフォーマット
2009/09/10
OpenGL の同期と描画速度の測定
そろそろパフォーマンスの測定もしようと思い同期周りを調べてみます。
ゲームでは CPU と GPU の同期の設計が重要です。描画パフォーマンス優先なら CPU と
GPU ができるだけ並列動作するようにスケジューリングしておきます。
動的な頂点生成で CPU からデータを転送する場合、またはクエリによって GPU の結果を
CPU で受け取る場合など、お互いに待っている時間をなくすようにします。
ここで間違うとパフォーマンスに大きく影響を与えることがあります。
●インターバルの設定
Direct3D 同様に描画時間の管理を自前で行っていたのですが、ウエイト無しにしても
必ず 60fps 前後で止まってしまうことに気がつきました。
ヘッダを見ていたら wglSwapIntervalEXT() を見つけたので使えるか確認。
GeForce GTX280 + 190.57 、"WGL_EXT_swap_control" が含まれています。
これで 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 では同期オブジェクトの状態を見ることで、描画の進行具合と同期を取ることが出来ます。
glClientWaitSync() は Timeout 時間を指定可能で、状態を返すことが出来ます。
glWaitSync() は Signal 状態になるまで、つまり完了するまでブロックします。
glSynciv() を用いて、現在の状態を参照することも出来るようです。
SwapBuffers() の動作をまだ厳密につかんでいないので、これで正しいかどうか
必要なのかどうかもわかりませんが、同期の例としてはこんな感じで。
●速度の測定
当初 TimeStamp が見つからなかったのですが Extension の方にありました。
GL_EXT_timer_query です。GeForce は対応していました。
glBeginQuery() ~ glEndQuery() が拡張されていて、GPU 処理の経過時間を
計測することが出来ます。
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 シェーダー管理
ゲームでは 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 シェーダー管理
2009/09/06
OpenGL のはじめ方 (2) wgl
Windows で OpenGL を使う方法がだんだんわかってきました。
DirectX ならとりあえず新しい SDK を入れれば終わりですが、OpenGL の場合は少々違います。
特別な SDK や lib が無くても、ドライバさえ対応していれば使うことができます。
敢えて OpenGL の各種ライブラリは使用していません。便利で容易に任せられる反面、中で何を
やっているのか、最新バージョンを使うにはどうしたらいいのかがわかりにくかったからです。
(1) OS と OpenGL のやりとりは WGL が行う。
(2) Windows SDK には OpenGL 1.1 のヘッダ&lib のみ。最小限。
(3) 新しい OpenGL に対応している GPU ライバをインストールする。
(4) ヘッダファイルは OpenGL Registry からダウンロードできる。
(5) wglGetProcAddress() を使って自分で最新の API を取得する。
API の取得は以前行ったとおり。ここ数日、実際に OpenGL 3.2 で追加された機能を
試すことが出来ました。今度は初期化部分、wgl まわりをもう少し詳しく調べてみます。
・MSDN Using OpenGL on Windows NT/2000 and Windows 95/98
●ピクセルフォーマットと初期化
Direct3D と同じように、とりあえず Win32 API でウィンドウを作っておきます。
OpenGL の Context (HGLRC) を作るには HDC が必要です。
そのまま wglCreateContext() を呼び出してもエラーになります。
OpenGL が対応しているピクセルフォーマットになっていないことが原因でした。
あらかじめフレームバッファのフォーマットを指定しておきます。
現在対応しているフォーマットは下記の方法で取得可能です。
GeForce 190.57 で実際に列挙してみました。126個あります。
・GeForce 190.57 PIXELFORMATDESCRIPTOR 一覧
最後の方に W と GL 両方付いているものが Window 描画対応かつ OpenGL で使える
フォーマットです。
MSDN Choosing and Setting a Best-Match Pixel Format の例では
Color=24, Depth=32 を指定していますが、Direct3D を考えても、上の列挙の結果を見ても、
あまり一般的とはいえません。
今回は下記のピクセルフォーマット指定を行いました。
ChoosePixelFormat() を使うとフォーマットのマッチングを自動で行ってくれるようです。
エラー判定を省いていますが、初期化手順は上記の通りです。
hDC, hGLRC は保存しておきます。終了時のコードは下記の通りです。
●描画ループ
wglMakeCurrent() で hGLRC を指定すれば、以後描画のために gl ~ API を
用いることが出来ます。Double Buffer を指定したので毎フレームの描画の最後に
SwapBuffers() を入れます。Direct3D の Present() に相当します。
●新しい Context の作成
基本的な WGL と OpenGL の関係は上記の通りです。
さらに新しい API を使う場合、いくつか追加の手順が必要となります。
まず拡張 API にはいくつか種類があります。
・新しいバージョンの OpenGL core API
・OpenGL 拡張 API
・WGL 拡張 API
これら拡張 API を使う方法はどれも同じです。ヘッダファイルと対応ドライバを手に入れたなら、
あとは wglGetProcAddress() を用いるだけ。
wglGetProcAddress() 関数は、wglCreateContext() のあと wglMakeCurrent() で
hGLRC を設定した後でないと呼び出すことが出来ません。
(1) OpenGL Registry から wglext.h をダウンロードしておきます。
(2) 通常の手段で Context を作成し wglMakeCurrent() しておきます。
(3) WGL の拡張 API 、wglCreateContextAttribsARB() を取り出します。
(4) wglCreateContextAttribsARB() を使って HGLRC を作り直します。
InitOpenGL() については こちら を参照してください。
なお GeForce で試している分には、特にこの手順を踏まなくても OpenGL 3.2 の命令が
使えるし描画もできています。
上の例では wglCreateContextAttribsARB() だけ個別に取り出していますが、
実際は InitOpenGL() (GLFunction) のテーブルに、gl ~ と同じように wgl 関数
を組み込みました。よって上の (A) のタイミングで InitOpenGL() を用いて
wglCreateContextAttribsARB を含めた一度にすべての拡張関数を取り出しています。
●プロファイルと互換性
OpenGL 3.1~ は DirectX のように、徐々に古い固定機能を無くし新しい機能への
置き換えが進んでいます。それでもアプリケーションが困らないように、従来と互換性を保った
API を使う手段も残されています。
・OpenGL 3.1 + GL_ARB_compatibility
・OpenGL 3.2 + WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
OpenGL 3.2 では Profile の選択が出来るようになりました。
wglCreateContextAttribsARB() 呼び出し時の WGL_CONTEXT_PROFILE_MASK_ARB
で指定できる Profile は次の 2つ。
・WGL_CONTEXT_CORE_PROFILE_BIT_ARB
・WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
Core profile はシェーダーを用いた新しい API セット、Compatibility profile は
互換性を持った API です。
テストで GeForce の 190.57 を使っている分には、どちらもほとんど差がありませんでした。
関連エントリ
・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 シェーダー管理
DirectX ならとりあえず新しい SDK を入れれば終わりですが、OpenGL の場合は少々違います。
特別な SDK や lib が無くても、ドライバさえ対応していれば使うことができます。
敢えて OpenGL の各種ライブラリは使用していません。便利で容易に任せられる反面、中で何を
やっているのか、最新バージョンを使うにはどうしたらいいのかがわかりにくかったからです。
(1) OS と OpenGL のやりとりは WGL が行う。
(2) Windows SDK には OpenGL 1.1 のヘッダ&lib のみ。最小限。
(3) 新しい OpenGL に対応している GPU ライバをインストールする。
(4) ヘッダファイルは OpenGL Registry からダウンロードできる。
(5) wglGetProcAddress() を使って自分で最新の API を取得する。
API の取得は以前行ったとおり。ここ数日、実際に OpenGL 3.2 で追加された機能を
試すことが出来ました。今度は初期化部分、wgl まわりをもう少し詳しく調べてみます。
・MSDN Using OpenGL on Windows NT/2000 and Windows 95/98
●ピクセルフォーマットと初期化
Direct3D と同じように、とりあえず Win32 API でウィンドウを作っておきます。
OpenGL の Context (HGLRC) を作るには HDC が必要です。
そのまま wglCreateContext() を呼び出してもエラーになります。
OpenGL が対応しているピクセルフォーマットになっていないことが原因でした。
あらかじめフレームバッファのフォーマットを指定しておきます。
現在対応しているフォーマットは下記の方法で取得可能です。
HDC hDC= GetDC( hWnd ); // 現在対応しているフォーマットの数を参照する int format_count= DescribePixelFormat( hDC, 0, 0, NULL ); // 列挙する for( int fi= 1 ; fi<= format_count ; fi++ ){ PIXELFORMATDESCRIPTOR pformat; DescribePixelFormat( hDC, fi, sizeof(PIXELFORMATDESCRIPTOR), &pformat ); }
GeForce 190.57 で実際に列挙してみました。126個あります。
・GeForce 190.57 PIXELFORMATDESCRIPTOR 一覧
最後の方に W と GL 両方付いているものが Window 描画対応かつ OpenGL で使える
フォーマットです。
MSDN Choosing and Setting a Best-Match Pixel Format の例では
Color=24, Depth=32 を指定していますが、Direct3D を考えても、上の列挙の結果を見ても、
あまり一般的とはいえません。
今回は下記のピクセルフォーマット指定を行いました。
ChoosePixelFormat() を使うとフォーマットのマッチングを自動で行ってくれるようです。
static PIXELFORMATDESCRIPTOR pformat= { sizeof(PIXELFORMATDESCRIPTOR), 1, 0 |PFD_DRAW_TO_WINDOW |PFD_SUPPORT_OPENGL |PFD_DOUBLEBUFFER , PFD_TYPE_RGBA, 32, // color 0, 0, // R 0, 0, // G 0, 0, // B 0, 0, // A 0, 0, 0, 0, 0, // AC R G B A 24, // depth 8, // stencil 0, // aux 0, // layertype 0, // reserved 0, // layermask 0, // visiblemask 0 // damagemask }; HDC hDC= GetDC( hWnd ); // ピクセルフォーマットの選択 int pfmt= ChoosePixelFormat( hDC, &pformat ); SetPixelFormat( hDC, pfmt, &pformat ); // OpenGL コンテキストの作成 HGLRC hGLRC= wglCreateContext( hDC ); wglMakeCurrent( hDC, hGLRC );
エラー判定を省いていますが、初期化手順は上記の通りです。
hDC, hGLRC は保存しておきます。終了時のコードは下記の通りです。
if( hGLRC ){ wglMakeCurrent( NULL, NULL ); wglDeleteContext( hGLRC ); hGLRC= NULL; } if( hDC ){ ReleaseDC( hWnd, hDC ); hDC= NULL; }
●描画ループ
wglMakeCurrent() で hGLRC を指定すれば、以後描画のために gl ~ API を
用いることが出来ます。Double Buffer を指定したので毎フレームの描画の最後に
SwapBuffers() を入れます。Direct3D の Present() に相当します。
// フレームバッファクリア glClearColor( 0.0f, 0.0f, 0.3, 0.0f ); glClearDepth( 1.0f ); glClearStencil( 0 ); glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT ); // 描画など ~ // flip SwapBuffers( hDC );
●新しい Context の作成
基本的な WGL と OpenGL の関係は上記の通りです。
さらに新しい API を使う場合、いくつか追加の手順が必要となります。
まず拡張 API にはいくつか種類があります。
・新しいバージョンの OpenGL core API
・OpenGL 拡張 API
・WGL 拡張 API
これら拡張 API を使う方法はどれも同じです。ヘッダファイルと対応ドライバを手に入れたなら、
あとは wglGetProcAddress() を用いるだけ。
wglGetProcAddress() 関数は、wglCreateContext() のあと wglMakeCurrent() で
hGLRC を設定した後でないと呼び出すことが出来ません。
(1) OpenGL Registry から wglext.h をダウンロードしておきます。
(2) 通常の手段で Context を作成し wglMakeCurrent() しておきます。
(3) WGL の拡張 API 、wglCreateContextAttribsARB() を取り出します。
(4) wglCreateContextAttribsARB() を使って HGLRC を作り直します。
HDC hDC= GetDC( hWnd ); // ピクセルフォーマットの選択 int pfmt= ChoosePixelFormat( hDC, &pformat ); SetPixelFormat( hDC, pfmt, &pformat ); // OpenGL コンテキストの作成 HGLRC hGLRC= wglCreateContext( hDC ); wglMakeCurrent( hDC, hGLRC ); // API 取り出し --- (A) PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB= (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress( "wglCreateContextAttribsARB" ); // 使用する OpenGL のバージョンとプロファイルの指定 static const int att[]= { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 2, WGL_CONTEXT_FLAGS_ARB, 0, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0, }; // 新しい HGLRC の作成 HGLRC hglrc= wglCreateContextAttribsARB( hDC, NULL, att ); wglMakeCurrent( hDC, hglrc ); // 古い HGLRC の削除と置き換え wglDeleteContext( hGLRC ); hGLRC= hglrc; // 新しい Core API の取り出し InitOpenGL();
InitOpenGL() については こちら を参照してください。
なお GeForce で試している分には、特にこの手順を踏まなくても OpenGL 3.2 の命令が
使えるし描画もできています。
上の例では wglCreateContextAttribsARB() だけ個別に取り出していますが、
実際は InitOpenGL() (GLFunction) のテーブルに、gl ~ と同じように wgl 関数
を組み込みました。よって上の (A) のタイミングで InitOpenGL() を用いて
wglCreateContextAttribsARB を含めた一度にすべての拡張関数を取り出しています。
●プロファイルと互換性
OpenGL 3.1~ は DirectX のように、徐々に古い固定機能を無くし新しい機能への
置き換えが進んでいます。それでもアプリケーションが困らないように、従来と互換性を保った
API を使う手段も残されています。
・OpenGL 3.1 + GL_ARB_compatibility
・OpenGL 3.2 + WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
OpenGL 3.2 では Profile の選択が出来るようになりました。
wglCreateContextAttribsARB() 呼び出し時の WGL_CONTEXT_PROFILE_MASK_ARB
で指定できる Profile は次の 2つ。
・WGL_CONTEXT_CORE_PROFILE_BIT_ARB
・WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
Core profile はシェーダーを用いた新しい API セット、Compatibility profile は
互換性を持った API です。
テストで GeForce の 190.57 を使っている分には、どちらもほとんど差がありませんでした。
関連エントリ
・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 シェーダー管理
2009/09/05
OpenGL 3.2 GeometryShader をもう少し使ってみる
前々回は VertexShaded から GeometryShader へのパラメータ渡しで
built-in のシステム変数 gl_Position を使いました。
gl_Position を使うメリットは下記の通りです。
・宣言なしに使うことが出来る
・VertexShader の段階で書き込んでいるので GeomteryShader が無くても描画できる
前々回のような単なるスルー出力では GeomteryShader が無くても描画可能です。
VertexShader で gl_Position に値を書き込んでいたので、GeomteryShader を attach
せずにそのまま描画することが出来ました。
ただし、常に VertexShader の段階で座標が確定するとは限りません。
頂点からの座標値も、普通の変数を介して GeomteryShader に渡すことが出来ます。

↑基本の状態
gl_in を使用していません。
面毎に法線と texcoord をまとめてフラットシェーディング風にしてみます。(↓)

きちんと法線を算出している訳じゃないので、本来平面のはずの Quad が Triangle に
分かれて見えてしまいます。今回は気にしないでいきます。
さらに座標もずらしてみます。(↓)

上のスクリーンショットではわからないかもしれませんが、投影後の 2D 座標がずれる
だけなので、形状として厳密に正しい物ではないです。
3D で変形を行うには、Geometry Shader で頂点演算を行う必要があります。
Vertex Shader の方がスルー出力になります。
座標値が vec3 になっている点に注意。任意の型やデータをやりとりできるのが
gl_Position を使わない場合の利点です。
なお正しく面法線を求めていれば、三角形ではなく四角形に押し出されていたはず。
3D 座標(local)でずらした結果(↓)

以前は VertexShader で受け取っていた uniform 変数 World, PView が
GeometryShader に移動しています。
OpenGL + GLSL の場合、Link したあとに uniform の設定を行うので C++ 側のコード
は何も修正しなくて済みます。結構便利です。
Direct3D だと各シェーダー毎に ConstantBuffer の設定が必要なので、
VSSetConstantBuffers() → GSSetConstantBuffers() と C++ の呼び出し側も
変更しなければなりませんでした。
ここまできたら VertexShader は不要な気もします。
一応試してみましたが VertexShader の省略は出来ませんでした。
Direct3D の Effect (FX) だと VertexShader と GeometryShader に同じ内容を
割り当てることで省略できます。
GeometryShader でポリゴンを増やしてみます。中に元の形状が内部に置いてある状態です。
共有頂点分の演算が重複するので、こういうケースでは VertexShader を併用
した方が効率がよいかもしれません。
out の layout で max_vertices= 6 に注意。

関連エントリ
・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 シェーダー管理
built-in のシステム変数 gl_Position を使いました。
VertexShader: gl_Position= vec4( POSITION.xyz, 1.0 ) * PView; GeomteryShader: position= gl_in[0].gl_Position;
gl_Position を使うメリットは下記の通りです。
・宣言なしに使うことが出来る
・VertexShader の段階で書き込んでいるので GeomteryShader が無くても描画できる
前々回のような単なるスルー出力では GeomteryShader が無くても描画可能です。
VertexShader で gl_Position に値を書き込んでいたので、GeomteryShader を attach
せずにそのまま描画することが出来ました。
ただし、常に VertexShader の段階で座標が確定するとは限りません。
頂点からの座標値も、普通の変数を介して GeomteryShader に渡すことが出来ます。

↑基本の状態
// GLSL 1.5 VertexShader gl_Position を使わない #version 150 uniform vec4 World[3]; uniform mat4 PView; in vec3 POSITION; in vec3 NORMAL; in vec2 TEXCOORD; out vec4 vPosition; out vec3 vNormal; out vec2 vTexcoord; void main() { vPosition= vec4( POSITION.xyz, 1 ) * PView; vNormal.x= dot( NORMAL.xyz, World[0].xyz ); vNormal.y= dot( NORMAL.xyz, World[1].xyz ); vNormal.z= dot( NORMAL.xyz, World[2].xyz ); vTexcoord= TEXCOORD; }
// GLSL 1.5 GeometryShader #version 150 layout(triangles) in; layout(triangle_strip, max_vertices= 3) out; in Inputs { vec4 vPosition; vec3 vNormal; vec2 vTexcoord; } gin[3]; out vec3 vNormal; out vec2 vTexcoord; void main() { for( int i= 0 ; i< 3 ; i++ ){ gl_Position= gin[i].vPosition; vNormal= gin[i].vNormal; vTexcoord= gin[i].vTexcoord; EmitVertex(); } EndPrimitive(); }
gl_in を使用していません。
面毎に法線と texcoord をまとめてフラットシェーディング風にしてみます。(↓)

// GeometryShader フラット化 void main() { vNormal= normalize( gin[0].vNormal + gin[1].vNormal + gin[2].vNormal ); vTexcoord= (gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord)/3; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= gin[i].vPosition; EmitVertex(); } EndPrimitive(); }
きちんと法線を算出している訳じゃないので、本来平面のはずの Quad が Triangle に
分かれて見えてしまいます。今回は気にしないでいきます。
さらに座標もずらしてみます。(↓)

// GeometryShader 座標もずらす void main() { vec3 vnormal= normalize(gin[0].vNormal + gin[1].vNormal + gin[2].vNormal); vNormal= vnormal; vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3; vec3 offset= vnormal.xyz*2.0; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= gin[i].vPosition + vec4( offset.xy, 0.0, 0.0 ); EmitVertex(); } EndPrimitive(); }
上のスクリーンショットではわからないかもしれませんが、投影後の 2D 座標がずれる
だけなので、形状として厳密に正しい物ではないです。
3D で変形を行うには、Geometry Shader で頂点演算を行う必要があります。
Vertex Shader の方がスルー出力になります。
座標値が vec3 になっている点に注意。任意の型やデータをやりとりできるのが
gl_Position を使わない場合の利点です。
なお正しく面法線を求めていれば、三角形ではなく四角形に押し出されていたはず。
3D 座標(local)でずらした結果(↓)

// GLSL 1.5 VertexShader #version 150 in vec3 POSITION; in vec3 NORMAL; in vec2 TEXCOORD; out vec3 vPosition; // vec4 → vec3 out vec3 vNormal; out vec2 vTexcoord; void main() { vPosition= POSITION; vNormal= NORMAL; vTexcoord= TEXCOORD; }
// GLSL 1.5 GeometryShader #version 150 uniform vec4 World[3]; uniform mat4 PView; layout(triangles) in; layout(triangle_strip, max_vertices= 3) out; in Inputs { vec3 vPosition; // vec4 → vec3 vec3 vNormal; vec2 vTexcoord; } gin[3]; out vec3 vNormal; out vec2 vTexcoord; vec4 transform( vec3 pos ) { return vec4( pos, 1 ) * PView; } void main() { vec3 vnormal= normalize(gin[0].vNormal + gin[1].vNormal + gin[2].vNormal); vNormal.x= dot( vnormal.xyz, World[0].xyz ); vNormal.y= dot( vnormal.xyz, World[1].xyz ); vNormal.z= dot( vnormal.xyz, World[2].xyz ); vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= transform( gin[i].vPosition + vnormal * 1.5 ); EmitVertex(); } EndPrimitive(); }
以前は VertexShader で受け取っていた uniform 変数 World, PView が
GeometryShader に移動しています。
OpenGL + GLSL の場合、Link したあとに uniform の設定を行うので C++ 側のコード
は何も修正しなくて済みます。結構便利です。
Direct3D だと各シェーダー毎に ConstantBuffer の設定が必要なので、
VSSetConstantBuffers() → GSSetConstantBuffers() と C++ の呼び出し側も
変更しなければなりませんでした。
ここまできたら VertexShader は不要な気もします。
一応試してみましたが VertexShader の省略は出来ませんでした。
Direct3D の Effect (FX) だと VertexShader と GeometryShader に同じ内容を
割り当てることで省略できます。
GeometryShader でポリゴンを増やしてみます。中に元の形状が内部に置いてある状態です。
共有頂点分の演算が重複するので、こういうケースでは VertexShader を併用
した方が効率がよいかもしれません。
out の layout で max_vertices= 6 に注意。

// GLSL 1.5 GeometryShader #version 150 uniform vec4 World[3]; uniform mat4 PView; layout(triangles) in; layout(triangle_strip, max_vertices= 6) out; in Inputs { vec3 vPosition; vec3 vNormal; vec2 vTexcoord; } gin[3]; out vec3 vNormal; out vec2 vTexcoord; vec4 transform( vec3 pos ) { return vec4( pos, 1 ) * PView; } void setnormal( vec3 normal ) { vNormal.x= dot( normal.xyz, World[0].xyz ); vNormal.y= dot( normal.xyz, World[1].xyz ); vNormal.z= dot( normal.xyz, World[2].xyz ); } void main() { for( int i= 0 ; i< 3 ; i++ ){ setnormal( gin[i].vNormal ); vTexcoord= gin[i].vTexcoord; gl_Position= transform( gin[i].vPosition ); EmitVertex(); } EndPrimitive(); vec3 vnormal= normalize( gin[0].vNormal + gin[1].vNormal + gin[2].vNormal ); setnormal( vnormal ); vTexcoord= ( gin[0].vTexcoord + gin[1].vTexcoord + gin[2].vTexcoord )/3; for( int i= 0 ; i< 3 ; i++ ){ gl_Position= transform( gin[i].vPosition + vnormal * 2.0 ); EmitVertex(); } EndPrimitive(); }
関連エントリ
・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 シェーダー管理
2009/09/04
OpenGL GLSL のバージョン
GeometryShader 利用時に GLSL のバージョンの違いが問題になったのでまとめてみました。
・Wikipedia GLSL
OpenGL ES 2.0 の GLSL は、GLSL ES または ESSL と呼ばれています。
上の対応表では OpenGL 2.0 / GLSL 1.1 の列にありますが、GLSL ES 1.0 自体は
GLSL 1.2 の機能も含まれています。実際は OpenGL 2.0~2.1 の中間になります。
OpenGL 2.0 で GLSL が core に組み込まれました。
OpenGL 3.0 / GLSL 1.3 で大きな仕様変更があり、シェーダーも固定機能の排除が
進められています。attribute/varying が in/out に変更になったのもここです。
Direct3D との対応付けは難しいですが、おそらく OpenGL 2.0 が Direct3D 9、
OpenGL 3.0 が ShaderModel 4.0 の Direct3D 10 に近い位置づけです。
実際はここまで明確な区別が無く、もっと入り組んでいるし互換性も保たれています。
機能拡張も段階的に進められています。
例えば OpenGL 3.1 で Uniform Block / Uniform Buffer が追加されていて、
これはちょうど Direct3D 10 の ConstantBuffer に相当するものです。
OpenGL 3.2 では Geometry Shader が入りました。
GLES と GLSL ES (ESSL) は「 #ifdef GL_ES 」で区別できます。
昨日はむりやり define して動かしましたが、下記のような定義でシェーダーを共有
することにしました。
OpenGL ES のコンパイラには少々問題があって、ソースの先頭に #version が無いと
エラーになるようです。コンパイルオプションの切り替えのために複数のソースを
渡しているせいか、#version をどこに書いてもエラーとなってしまいました。
上の定義では OpenGL ES 側に #version を書いていないのはそのためです。
関連エントリ
・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 シェーダー管理
OpenGL 1.3 ---- OpenGL ES 1.0 ---- OpenGL 1.5 *1 OpenGL ES 1.1 ---- OpenGL 2.0 GLSL 1.1 OpenGL ES 2.0 GLSL ES 1.0 (ESSL) OpenGL 2.1 GLSL 1.2 OpenGL 3.0 GLSL 1.3 OpenGL 3.1 GLSL 1.4 OpenGL 3.2 GLSL 1.5 *1: Shader 対応。Extension で GLSL1.0 も利用可能。Core API になったのは OpenGL 2.0 から。
・Wikipedia GLSL
OpenGL ES 2.0 の GLSL は、GLSL ES または ESSL と呼ばれています。
上の対応表では OpenGL 2.0 / GLSL 1.1 の列にありますが、GLSL ES 1.0 自体は
GLSL 1.2 の機能も含まれています。実際は OpenGL 2.0~2.1 の中間になります。
OpenGL 2.0 で GLSL が core に組み込まれました。
OpenGL 3.0 / GLSL 1.3 で大きな仕様変更があり、シェーダーも固定機能の排除が
進められています。attribute/varying が in/out に変更になったのもここです。
Direct3D との対応付けは難しいですが、おそらく OpenGL 2.0 が Direct3D 9、
OpenGL 3.0 が ShaderModel 4.0 の Direct3D 10 に近い位置づけです。
実際はここまで明確な区別が無く、もっと入り組んでいるし互換性も保たれています。
機能拡張も段階的に進められています。
例えば OpenGL 3.1 で Uniform Block / Uniform Buffer が追加されていて、
これはちょうど Direct3D 10 の ConstantBuffer に相当するものです。
OpenGL 3.2 では Geometry Shader が入りました。
GLES と GLSL ES (ESSL) は「 #ifdef GL_ES 」で区別できます。
昨日はむりやり define して動かしましたが、下記のような定義でシェーダーを共有
することにしました。
// Vertex Shader #ifdef GL_ES # define IN attribute # define OUT varying # define LOWP lowp # define MEDIUMP mediump precision MEDIUMP float; precision MEDIUMP int; #else # define IN in # define OUT out # version 150 # define LOWP # define MEDIUMP #endif
// Fragment Shader #ifdef GL_ES # define IN varying # define LOWP lowp # define MEDIUMP mediump precision MEDIUMP float; precision MEDIUMP int; precision LOWP sampler2D; #else # define IN in # version 150 # define LOWP # define MEDIUMP #endif
OpenGL ES のコンパイラには少々問題があって、ソースの先頭に #version が無いと
エラーになるようです。コンパイルオプションの切り替えのために複数のソースを
渡しているせいか、#version をどこに書いてもエラーとなってしまいました。
上の定義では OpenGL ES 側に #version を書いていないのはそのためです。
関連エントリ
・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 シェーダー管理
2009/09/03
OpenGL 3.2 の GeometryShader
OpenGL 3.2 では core 機能に Geometry Shader が含まれています。
試してみました。
・OpenGL Registry
GeometryShader はポリゴンの面単位で実行可能なシェーダープログラムです。
面単位なので、プリミティブタイプが Triangle List (GL_TRIANGLES) なら一度に
3頂点を入力として受け取ります。主な用途は下記の通りです。
・頂点演算
・面単位のマテリアル演算
・形状の操作
・出力先の変更
・StreamOutput (Transform Feedback)
わかりやすいところでは面法線の生成など面単位のマテリアル設定が考えられます。
ユニークなのは入力と関係なく出力プリミティブを組み立てられることです。
出力頂点数は任意なので、エッジだけ Line として書き出したり、面を分割したり、
面を消すことも出来ます。
例えば入力を PointList (GL_POINTS) で 1頂点だけ受け取り、Triangle で
2 プリミティブ出力すればポイントスプライトの代わりになるわけです。
出力先の変更は Direct3D だと RenderTarget Array, OpenGL では
Layered Rendering と呼ばれているようです。
Geometry Shader で書き込むフレームバッファの選択が可能で、Cube Map への
レンダリングも一回で出来る仕様になっています。
入力可能なプリミティブタイプも Direct3D 10 同様に増えています。
下記は増えた分のみ。使えるのは GeometryShader が有効な場合だけに限られます。
あまり馴染みがない名称ですが、極端な言い方をすれば 4,6 頂点のプリミティブが
使えるようになったということです。
GL_TRIANGLES だと GeometryShader は 3 頂点単位で受け取りますが、
GL_TRIANGLES_ADJACENCY では一度に 6頂点受け取れることになります。
GL_LINES_ADJACENCY の 4頂点は、QUAD プリミティブの代わりとしても使えます。
ちなみに Direct3D 11 の ShaderModel 5.0 はさらに増えていて、テセレータ用に
32頂点まで任意の数の頂点を入力できるようになっています。
今回使用したビデオカードは GeForce GTX280。ステートを調べると下記の通りです。
頂点の生成が最大 1024 float なのも Direct3D 10 の ShaderModel 4.0 と同じです。
● GeometryShader のコンパイル
Shader の読み込みとコンパイル手順はこれまでの VertexShader, FragmentShader
と同じです。もともとシェーダーしか使えない OpenGL ES 2.0 を使っていたため、
ちょうど GeometryShader の部分が追加された形になります。
エラー判定は省いてあります。
次はコンパイルしたシェーダーをリンクして Program の作成です。
これまで VertexShader, FragmentShader だけ attach していたところに
GeometryShader が加わります。
●ドライバの補足
GLSL の GeometryShader の書き方がわからなかったため、ドキュメント
OpenGL Shadeing Language 1.50.09 と Direct3D の知識を頼りに進めました。
最初はコンパイルすらうまく通らず、EmitVertex を用いると
「#extension GL_EXT_geometry_shader4 : enable」
を宣言しなさいとエラーが出てしまいます。
「#version 150」で GLSL 1.5 を指定すると未対応のバージョンだといわれます。
140 なら通ります。ドライバを調べてみると、最新版 190.62 は OpenGL 3.1 しか
対応していませんでした。
ドライバが OpenGL 3.2 / GLSL 1.5 に対応していなかったことが原因です。
・NVIDIA OpenGL 3.0/3.1/3.2 Support for Windows, Linux, FreeBSD, and Solaris
上のページにある、OpenGL 3.2 対応ドライバ 190.57 を入れたら、#version 150 指定
による GLSL 1.5 も、GeometryShader 専用命令も通るようになりました。
「#version 150」とだけ書いておけばよく、extension 指定は不要です。
● GLSL v1.50
「#version 150」を指定したら VertexShader でもエラーが出るようになりました。
attribute, varying 宣言は古いので、in, out を使えと言われます。
もともと attribute は頂点からの入力、varying はラスタライザの補間パラメータを
意味しています。出力先が GeometryShader かもしれないし、シェーダーパイプラインが
多層化&汎用化されたので名称が変わったものと思われます。
OpenGL ES 2.0 との互換性も残したいのでとりあえず define でごまかしました。
attribute を in に、varying を out に置き換えます。
Matrix 周りが少々不自然なのは、OpenGL ES 2.0 に mat4x3 が無かったことと頂点
アニメーションを削ったためです。もともと Skinning の処理が入っていました。
同じように Fragment Shader も書き換えます。
FragmentShader の場合 VertexShader と逆で varying が in になります。
GeometryShader は最初から in/out で記述します。
プリミティブのタイプは layout() で宣言しています。
入力は block 宣言を使っており、Triangle なので 3頂点分のデータを受け取ります。
VertexShader が書き込んだシステム変数 gl_Position の値は、こちらも builtin
の gl_in[] という配列で受け取れます。
出力はこれまで通り out (varying) 宣言した変数に書き込みます。
必要なパラメータを書き込んだ後に EmitVertex() 命令で頂点が確定します。
プリミティブの区切りは EndPrimitive() 命令です。
このジオメトリシェーダーは何もせずに、頂点の出力をそのままスルーしています。
上の (1) の行を削除すると面に同じ法線が適用されるため、フラットシェーディング
風の見た目になるはずです。
同じシェーダーを DirectX の HLSL で書くとこんな感じです。
書式が違うだけでほとんど同じです。
Direct3D でも、アセンブラ出力すると頂点の生成は emit 命令なのです。
関連エントリ
・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 シェーダー管理
試してみました。
・OpenGL Registry
GeometryShader はポリゴンの面単位で実行可能なシェーダープログラムです。
面単位なので、プリミティブタイプが Triangle List (GL_TRIANGLES) なら一度に
3頂点を入力として受け取ります。主な用途は下記の通りです。
・頂点演算
・面単位のマテリアル演算
・形状の操作
・出力先の変更
・StreamOutput (Transform Feedback)
わかりやすいところでは面法線の生成など面単位のマテリアル設定が考えられます。
ユニークなのは入力と関係なく出力プリミティブを組み立てられることです。
出力頂点数は任意なので、エッジだけ Line として書き出したり、面を分割したり、
面を消すことも出来ます。
例えば入力を PointList (GL_POINTS) で 1頂点だけ受け取り、Triangle で
2 プリミティブ出力すればポイントスプライトの代わりになるわけです。
出力先の変更は Direct3D だと RenderTarget Array, OpenGL では
Layered Rendering と呼ばれているようです。
Geometry Shader で書き込むフレームバッファの選択が可能で、Cube Map への
レンダリングも一回で出来る仕様になっています。
入力可能なプリミティブタイプも Direct3D 10 同様に増えています。
下記は増えた分のみ。使えるのは GeometryShader が有効な場合だけに限られます。
GL_LINES_ADJACENCY GL_LINE_STRIP_ADJACENCY GL_TRIANGLES_ADJACENCY GL_TRIANGLE_STRIP_ADJACENCY
あまり馴染みがない名称ですが、極端な言い方をすれば 4,6 頂点のプリミティブが
使えるようになったということです。
GL_TRIANGLES だと GeometryShader は 3 頂点単位で受け取りますが、
GL_TRIANGLES_ADJACENCY では一度に 6頂点受け取れることになります。
GL_LINES_ADJACENCY の 4頂点は、QUAD プリミティブの代わりとしても使えます。
ちなみに Direct3D 11 の ShaderModel 5.0 はさらに増えていて、テセレータ用に
32頂点まで任意の数の頂点を入力できるようになっています。
今回使用したビデオカードは GeForce GTX280。ステートを調べると下記の通りです。
頂点の生成が最大 1024 float なのも Direct3D 10 の ShaderModel 4.0 と同じです。
GL_MAX_GEOMETRY_OUTPUT_VERTICES = 1024 GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS = 32 GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = 1024
● GeometryShader のコンパイル
Shader の読み込みとコンパイル手順はこれまでの VertexShader, FragmentShader
と同じです。もともとシェーダーしか使えない OpenGL ES 2.0 を使っていたため、
ちょうど GeometryShader の部分が追加された形になります。
GLuint vshader= glCreateShader( GL_VERTEX_SHADER ); glShaderSource( vshader, 1, vshader_source_text, NULL ); glCompileShader( vshader ); GLuint gshader= glCreateShader( GL_GEOMETRY_SHADER ); glShaderSource( gshader, 1, gshader_source_text, NULL ); glCompileShader( gshader ); GLuint pshader= glCreateShader( GL_FRAGMENT_SHADER ); glShaderSource( pshader, 1, pshader_source_text, NULL ); glCompileShader( pshader );
エラー判定は省いてあります。
次はコンパイルしたシェーダーをリンクして Program の作成です。
これまで VertexShader, FragmentShader だけ attach していたところに
GeometryShader が加わります。
GLuint shaderProgram= glCreateProgram(); glAttachShader( shaderProgram, vshader ); glAttachShader( shaderProgram, gshader ); glAttachShader( shaderProgram, pshader ); glLinkProgram( shaderProgram );
●ドライバの補足
GLSL の GeometryShader の書き方がわからなかったため、ドキュメント
OpenGL Shadeing Language 1.50.09 と Direct3D の知識を頼りに進めました。
最初はコンパイルすらうまく通らず、EmitVertex を用いると
「#extension GL_EXT_geometry_shader4 : enable」
を宣言しなさいとエラーが出てしまいます。
「#version 150」で GLSL 1.5 を指定すると未対応のバージョンだといわれます。
140 なら通ります。ドライバを調べてみると、最新版 190.62 は OpenGL 3.1 しか
対応していませんでした。
ドライバが OpenGL 3.2 / GLSL 1.5 に対応していなかったことが原因です。
・NVIDIA OpenGL 3.0/3.1/3.2 Support for Windows, Linux, FreeBSD, and Solaris
上のページにある、OpenGL 3.2 対応ドライバ 190.57 を入れたら、#version 150 指定
による GLSL 1.5 も、GeometryShader 専用命令も通るようになりました。
「#version 150」とだけ書いておけばよく、extension 指定は不要です。
● GLSL v1.50
「#version 150」を指定したら VertexShader でもエラーが出るようになりました。
attribute, varying 宣言は古いので、in, out を使えと言われます。
もともと attribute は頂点からの入力、varying はラスタライザの補間パラメータを
意味しています。出力先が GeometryShader かもしれないし、シェーダーパイプラインが
多層化&汎用化されたので名称が変わったものと思われます。
OpenGL ES 2.0 との互換性も残したいのでとりあえず define でごまかしました。
attribute を in に、varying を out に置き換えます。
// sysdef2.vsh (Vertex Shader) #define attribute in #define varying out #version 150 uniform vec4 World[3]; uniform mat4 PView; attribute vec3 POSITION; attribute vec3 NORMAL; attribute vec2 TEXCOORD; varying vec3 onormal; varying vec2 otexcoord; void main() { gl_Position= vec4( POSITION.xyz, 1 ) * PView; vec3 nvec; nvec.x= dot( NORMAL.xyz, World[0].xyz ); nvec.y= dot( NORMAL.xyz, World[1].xyz ); nvec.z= dot( NORMAL.xyz, World[2].xyz ); onormal= nvec; otexcoord= TEXCOORD; }
Matrix 周りが少々不自然なのは、OpenGL ES 2.0 に mat4x3 が無かったことと頂点
アニメーションを削ったためです。もともと Skinning の処理が入っていました。
同じように Fragment Shader も書き換えます。
// sysdef2.fsh (Fragment Shader) #define varying in #version 150 varying vec3 onormal; varying vec2 otexcoord; uniform sampler2D ColorMap; void main() { vec3 lightdir= vec3( 0.0, 0.0, -1.0 ); float lv= clamp( dot( normalize( onormal ), lightdir ), 0.0, 1.0 ) + 0.5; vec4 color= vec4( 1.0, 1.0, 1.0, 1.0 ) * lv; vec4 tcol= texture2D( ColorMap, otexcoord ); gl_FragColor= color * tcol; }
FragmentShader の場合 VertexShader と逆で varying が in になります。
GeometryShader は最初から in/out で記述します。
// sysdef2.gsh (Geometry Shader) #version 150 layout(triangles) in; layout(triangle_strip, max_vertices= 3) out; in Inputs { vec3 onormal; vec2 otexcoord; } gin[3]; out vec3 onormal; out vec2 otexcoord; void main() { gl_Position= gl_in[0].gl_Position; onormal= gin[0].onormal; otexcoord= gin[0].otexcoord; EmitVertex(); gl_Position= gl_in[1].gl_Position; onormal= gin[1].onormal; // (1) otexcoord= gin[1].otexcoord; EmitVertex(); gl_Position= gl_in[2].gl_Position; onormal= gin[2].onormal; // (1) otexcoord= gin[2].otexcoord; EmitVertex(); EndPrimitive(); }
プリミティブのタイプは layout() で宣言しています。
入力は block 宣言を使っており、Triangle なので 3頂点分のデータを受け取ります。
VertexShader が書き込んだシステム変数 gl_Position の値は、こちらも builtin
の gl_in[] という配列で受け取れます。
出力はこれまで通り out (varying) 宣言した変数に書き込みます。
必要なパラメータを書き込んだ後に EmitVertex() 命令で頂点が確定します。
プリミティブの区切りは EndPrimitive() 命令です。
このジオメトリシェーダーは何もせずに、頂点の出力をそのままスルーしています。
上の (1) の行を削除すると面に同じ法線が適用されるため、フラットシェーディング
風の見た目になるはずです。
同じシェーダーを DirectX の HLSL で書くとこんな感じです。
// HLSL Geometry Shader struct GS_INPUT { float4 Position : POSITION; float3 Normal : NORMAL; float2 Texcoord : TEXCOORD; }; struct GS_OUTPUT { float4 Position : SV_POSITION; float3 Normal : NORMAL; float2 Texcoord : TEXCOORD; }; [maxvertexcount(3)] void GS_Main( triangle GS_INPUT In[3], inout TriangleStream<GS_OUTPUT> gsstream ) { GS_OUTPUT Out; Out.Position= In[0].Position; Out.Texcoord= In[0].Texcoord; Out.Normal = In[0].Normal; gsstream.Append( Out ); Out.Position= In[1].Position; Out.Texcoord= In[1].Texcoord; Out.Normal = In[1].Normal; gsstream.Append( Out ); Out.Position= In[2].Position; Out.Texcoord= In[2].Texcoord; Out.Normal = In[2].Normal; gsstream.Append( Out ); gsstream.RestartStrip(); }
書式が違うだけでほとんど同じです。
Direct3D でも、アセンブラ出力すると頂点の生成は emit 命令なのです。
関連エントリ
・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 シェーダー管理
2009/09/01
OpenGL のはじめ方
Windows 上で DirectX ばかり使ってきたので、いざ OpenGL に手をつけてみようと思っても
何を使えばいいのか良くわかりませんでした。DirectX10/11 はすでにシェーダーのみとなって
いるため、OpenGL 入門によく載っているようなレガシーな命令もちょっと違います。
WindowsSDK に付属している gl.h ヘッダは OpenGL v1.1 ですが、すでに OpenGL は
3.2 まで進化しているようです。
Windows の場合、OS との間を取り持つ wgl~ という API 群があります。
wgl と gl は、Direct3D でいえば DXGI と D3D の関係に似ています。
・MSDN Win32 Extensions to OpenGL
以前 Maya plug-in を作ったときも GL_ARB_vertex_program など OpenGL の
Extension 関数を呼び出すために wglGetProcAddress() を使いました。
Core API の場合も全く同じ方法でした。
(1) 最新のヘッダファイルを入手する
(2) wgl 命令で描画の初期化
(3) ヘッダの定義を元に wglGetProcAddress() でアドレスを取り出す
・OpenGL Registry
上のページに gl3.h や glext.h, wglext.h 等が登録されています。
関数インターフェースの定義はこの中で行われており、必要ならプロトタイプ宣言もしてくれます。
wglGetProcAddress() を用いて関数名から実際のエントリアドレスを取得すれば
API を呼び出せるようになります。
たとえば gl3.h の中では、OpenGL 2.0 の命令として glCreateShader() が定義されています。
上がプロトタイプ宣言、下が関数ポインタの型宣言です。APIENTRYP はただの '*'。
今回はエントリアドレスを直接取り出すため、下の関数ポインタ型の宣言しか使いません。
プロトタイプ宣言はデフォルトで無効になっています。
この glCreateShader() を呼び出すには下記のようにすればよいわけです。
もちろん使えるのはディスプレイドライバが対応している場合だけです。
ドライバが対応していなければ wglGetProcAddress() はエラーを返します。
このように関数名からエントリアドレスを取得するやりかたは DLL と同じです。
DirectX の場合は COM なので、関数単独ではなく class 単位で API を取り出しますが
基本は同じです。
上の例のように、OpenGL 1.2~3.2 の膨大な関数を手作業で追加するのは酷です。
自動化します。
ヘッダのプロトタイプ宣言から、必要な関数名だけ抜き出すスクリプトを作っておきます。
関数名を全部大文字にして PFN ~ PROC で囲めばインターフェースの型宣言になります。
上のスクリプトは gl3.h ヘッダから関数名だけ抜き出して下記のような一覧を生成します。
そのまま gl3.h を渡すと OpenGL 1.1 など余計な関数まで再定義してしまうので、
必要な 1.2~3.2 部分だけ切り出してから与えることにします。
Core API だけでなく拡張命令も同じように追加できるでしょう。
出力ファイル名を GLExtensionList.inc としておきます。
これを C ソースで include し、GLDEFPROC() を用途に応じて再定義してして使い分けます。
ヘッダ側
ソース側
これで GLFunction.h を include すれば、OpenGL 3.2 までの命令をそのまま
使えるようになります。追加 lib は WindowsSDK の OpenGL32.lib です。
使用可能なのはドライバが対応している場合だけ。
また起動時は最初に一度 InitOpenGL() を呼び出しておく必要があります。
OpenGL ES 2.0 用に作成した描画コードが、一カ所直しただけでそのままコンパイルできました。
GLSL のシェーダーコードも共有しており、きちんど動作します。
ドライバと環境は GeForce 9800GT + 190.62 + Windows7 x64 。
モデルデータもテクスチャも GL ES 2.0 と全く同じ物です。
修正したのは下記の点
OpenGL 3.2 に厳密に従うには、ピクセルフォーマットなどテクスチャの読み込み部も
手を入れる必要がありそうです。
関連エントリ
・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 シェーダー管理
何を使えばいいのか良くわかりませんでした。DirectX10/11 はすでにシェーダーのみとなって
いるため、OpenGL 入門によく載っているようなレガシーな命令もちょっと違います。
WindowsSDK に付属している gl.h ヘッダは OpenGL v1.1 ですが、すでに OpenGL は
3.2 まで進化しているようです。
Windows の場合、OS との間を取り持つ wgl~ という API 群があります。
wgl と gl は、Direct3D でいえば DXGI と D3D の関係に似ています。
・MSDN Win32 Extensions to OpenGL
以前 Maya plug-in を作ったときも GL_ARB_vertex_program など OpenGL の
Extension 関数を呼び出すために wglGetProcAddress() を使いました。
Core API の場合も全く同じ方法でした。
(1) 最新のヘッダファイルを入手する
(2) wgl 命令で描画の初期化
(3) ヘッダの定義を元に wglGetProcAddress() でアドレスを取り出す
・OpenGL Registry
上のページに gl3.h や glext.h, wglext.h 等が登録されています。
関数インターフェースの定義はこの中で行われており、必要ならプロトタイプ宣言もしてくれます。
wglGetProcAddress() を用いて関数名から実際のエントリアドレスを取得すれば
API を呼び出せるようになります。
たとえば gl3.h の中では、OpenGL 2.0 の命令として glCreateShader() が定義されています。
#define APIENTRY #define APIENTRYP APIENTRY * #define GLAPI extern GLAPI GLuint APIENTRY glCreateShader (GLenum); ~ typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
上がプロトタイプ宣言、下が関数ポインタの型宣言です。APIENTRYP はただの '*'。
今回はエントリアドレスを直接取り出すため、下の関数ポインタ型の宣言しか使いません。
プロトタイプ宣言はデフォルトで無効になっています。
この glCreateShader() を呼び出すには下記のようにすればよいわけです。
// 取り出し PFNGLCREATESHADERPROC glCreateShader= (PFNGLCREATESHADERPROC)wglGetProcAddress( "glCreateShader" ); // 呼び出し glCreateShader( shader );
もちろん使えるのはディスプレイドライバが対応している場合だけです。
ドライバが対応していなければ wglGetProcAddress() はエラーを返します。
このように関数名からエントリアドレスを取得するやりかたは DLL と同じです。
DirectX の場合は COM なので、関数単独ではなく class 単位で API を取り出しますが
基本は同じです。
上の例のように、OpenGL 1.2~3.2 の膨大な関数を手作業で追加するのは酷です。
自動化します。
ヘッダのプロトタイプ宣言から、必要な関数名だけ抜き出すスクリプトを作っておきます。
import re import sys mp_api= re.compile( "^GLAPI\s+[a-zA-Z0-9_*]+\s+APIENTRY\s+(.*)\s+\(" ) def ProcList( filename ): f= open( filename, 'r' ) for line in f: pat_api= mp_api.search( line ) if pat_api != None: func= pat_api.group(1) print "GLDEFPROC( PFN"+ func.upper() + "PROC, " + func + " )" f.close() ProcList( sys.argv[1] )
関数名を全部大文字にして PFN ~ PROC で囲めばインターフェースの型宣言になります。
上のスクリプトは gl3.h ヘッダから関数名だけ抜き出して下記のような一覧を生成します。
GLDEFPROC( PFNGLATTACHSHADERPROC, glAttachShader ) GLDEFPROC( PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation ) GLDEFPROC( PFNGLCOMPILESHADERPROC, glCompileShader ) GLDEFPROC( PFNGLCREATEPROGRAMPROC, glCreateProgram ) GLDEFPROC( PFNGLCREATESHADERPROC, glCreateShader ) GLDEFPROC( PFNGLDELETEPROGRAMPROC, glDeleteProgram ) GLDEFPROC( PFNGLDELETESHADERPROC, glDeleteShader ) GLDEFPROC( PFNGLDETACHSHADERPROC, glDetachShader ) ~
そのまま gl3.h を渡すと OpenGL 1.1 など余計な関数まで再定義してしまうので、
必要な 1.2~3.2 部分だけ切り出してから与えることにします。
Core API だけでなく拡張命令も同じように追加できるでしょう。
出力ファイル名を GLExtensionList.inc としておきます。
これを C ソースで include し、GLDEFPROC() を用途に応じて再定義してして使い分けます。
ヘッダ側
// GLFunction.h #include <WinGDI.h> #include <gl/gl.h> #include <GL3/gl3.h> #define GLDEFPROC(type,name) extern type name; # include "GLExtensionList.inc" #undef GLDEFPROC extern void InitOpenGL();
ソース側
// GLFunction.cpp #define GLDEFPROC(type,name) type name; # include "GLExtensionList.inc" #undef GLDEFPROC struct T_ProcList { const char* Proc; void** Addr; }; static T_ProcList ProcList[]= { #define GLDEFPROC(type,name) { #name, (void**)&name }, # include "GLExtensionList.inc" #undef GLDEFPROC { NULL, NULL, }, }; void InitOpenGL() { const T_ProcList* tp= ProcList; for(; tp->Proc ; tp++ ){ *tp->Addr= wglGetProcAddress( tp->Proc ); if( !*tp->Addr ){ int errcode= GetLastError(); ERROR_UTF8( "'%s' not found %d/%x\n", tp->Proc, errcode, errcode ); //*tp->Addr= _UnknownAPI_Func; } } }
これで GLFunction.h を include すれば、OpenGL 3.2 までの命令をそのまま
使えるようになります。追加 lib は WindowsSDK の OpenGL32.lib です。
使用可能なのはドライバが対応している場合だけ。
また起動時は最初に一度 InitOpenGL() を呼び出しておく必要があります。
OpenGL ES 2.0 用に作成した描画コードが、一カ所直しただけでそのままコンパイルできました。
GLSL のシェーダーコードも共有しており、きちんど動作します。
ドライバと環境は GeForce 9800GT + 190.62 + Windows7 x64 。
モデルデータもテクスチャも GL ES 2.0 と全く同じ物です。
修正したのは下記の点
glClearDepthf( depth ); // GL ES のみ ↓ glClearDepth( depth );
OpenGL 3.2 に厳密に従うには、ピクセルフォーマットなどテクスチャの読み込み部も
手を入れる必要がありそうです。
関連エントリ
・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 シェーダー管理