Direct3D 10 GPU の Query 値の違い

Query 系を少しだけ試してみました。やはり RADEON HD2900XT でも
同じように使えます。TIMESTAMP 取れました。よし。

ただドライバによって結果が異なるかもしれませんが、
GeForce8800GTS (163.11) では ID3D10Query の PIPELINE_STATISTICS の
CInvocations, CPrimitives が 0 のままです。
RADEON HD2900XT だとそれっぽい値が入っています。

IA 系の基礎的なパラメータは一緒ですが、CI~CP~以外にも値の
違うものがあります。例えば VSinvocations など。
おそらく描画に StreamOutput を使っているせいだと思いますが、
RADEON は IAVertices と同じ値で、GeForce はなぜか半分以下の値です。

SO_STATISTICS のPrimitivesStorageNeeded は、GeForce の場合
NumPrimitivesWritten と同じですが、RADEON の場合やたらと大きな値です。
基本的に動的な増減も無く、3 in 3 out しか使ってないのでこれは
計算方法の違いでしょうか。

まったく同じデータを表示してみた結果の例

・キャラクタの表示なので描画面積はかなり小さい
・ボーンアニメーションしているので、Pixel 数が常に変動しており誤差がある
・結果を表示するフォント描画の分も計算に含まれている

●GeForce8800GTS 163.11
 IAVertices 4092
 IAPrimitives 1504
 VSInvocations 1893
 GSInvocations 115
 GSPrimitives 230
 CInvocations 0
 CPrimitives 0
 PSInvocations 9034
 NumPrimitivesWritten 95
 PrimitivesStorageNeeded 95

●RADEON HD2900XT 07.7
 IAVertices 4100
 IAPrimitives 1512
 VSInvocations 4100
 GSInvocations 123
 GSPrimitives 246
 CInvocations 1635
 CPrimitives 1635
 PSInvocations 9656
 NumPrimitivesWritten 95
 PrimitivesStorageNeeded 1389

Primitive 数が 8 個だけ RADEON の方が多いのは、上記の計測値もポリゴン
でフォント描画しているためです。つまり、CInvocations, CPrimitives,
PrimitivesStorageNeeded の値でちょうど 8桁分、数字が多いわけです。
データもプログラムも同一です。

もう少しライブラリを整備したらきちんと調べていきます。

ちなみに Counter はどちらも全滅でした。
D3D10_COUNTER_INFO は両者とも
LastDeviceDependentCounter= 0
NumSimultaneousCounters= 0
NumDetectableParallelUnits= 1

Direct3D 10 Query

CPU と GPU は基本的に非同期に動作しますが、CPU 側でも GPU から値を
受け取ったり、動作状況を見てタイミングを計ったりすることがあります。
特に CPU と GPU の「動作と描画のタイミング取り」は重要で、入力から
どれくらいの遅延を許容するのか、CPU と GPU がどれくらい並列に
動くのか、設計時に把握しておく必要があるでしょう。

たとえば CPU 側で何らかのデータを受け取る場合、GPU 処理のタイミング
を何も考えないと、お互いに同期のための Block が発生してしまいます。
やはりきちんとフレームを制御しながら Double Buffering にするなど
いろいろ工夫が必要です。

GPU 側の動作状況を調べるには、Direct3D9 なら IDirect3DQuery9 を使います。
GPU のコマンドにいわゆる Fence の挿入が可能で、普段見えない GPU の
動きを調べることができます。

Direct3D10 の場合は ID3D10Query です。API が異なりますが主要な機能は
D3D9 とほとんど変わらず同じように使えるようです。
一部の同期用 (EVENT等)コマンドは Begin が無いのも一緒。
でも API が違います。

IDirect3DQuery9
・Issue( D3DISSUE_BEGIN )
・Issue( D3DISSUE_END )
・GetData()

ID3D10Query
・Begin()
・End()
・GetData()

D3D9 の場合 RADEON で TIMESTAMP が取れなかったりと GPU によって
機能の違いがありましたが、D3D10 ではこの辺大丈夫そうですね。

Direct3D 10 の便利な点 DEVICELOST

Vista + Direct3D 10 になって API 回りも一新、便利な機能が増えています。
特に OS との親和性が高まったおかげか、管理周りの負担が減りました。
これは非常に歓迎すべきことです。
例えば D3D9 までは常に頭を悩ませていた DEVICELOST 対策が、D3D10 では
ほぼいらなくなりました。

D3D10 の新機能も別に使わないし、能力的にも DirectX9 までで満足しているし、
といった場合でも、これはきっと気になるポイントでしょう。

Windows 上では 1つのアプリケーションが GPU や VRAM を占有するわけでは
ないので、常にリソースの競合が発生します。
ほぼ占有可能なフルスクリーンモードでも、いわゆる ALT+TAB で
Task を切り替えるとリソースを明け渡さなければなりません。

このとき DirectX9 以前の API は D3DERR_DEVICELOST を返し、リソースが
他の Task に取られたため実行できないことを訴えます。
こうなったら描画動作の続行をあきらめるしかありません。
TestCooperativeLevel() を呼び出して再び利用可能になるのを待ち続けます。
利用可能な状態に戻ったことを確認したら、VRAM 内容や各種 GPU ステートを
戻してプログラムが継続できるよう復帰させる必要があるわけです。

もちろん DEVICELOST はフルスクリーンモードに限らず Window Mode でも
起こります。例えばスクリーンセーバーなど、突然他のアプリが全画面を
占拠して描画を始めることも十分ありえるからです。

D3D9 の場合でも、D3DPOOL_MANAGED を使えば VRAM 内容の復帰までは自動で
行ってくれるようになっています。これはこれでかなり手間が減りました。
それ以外のリソースは、明示的にリセットしたり作り直しです。
例えば RenderTaget 用の Surface とか、ID3DXEffect とか、
IDirect3DQuery9 など。

Direct3D 10 になると Vista がより深く Direct3D を活用するおかげか
この辺の GPU リソースもしっかり管理してくれます。
DEVICELOST 時の後始末をアプリケーションが行わなくても、ALT-TAB の
切り替えからでも、スクリーンセーバーからでも、きちんと復帰して動作を
続けてくれます。(ちょっと感動)

ただ、アプリケーションが完全にバックグラウンドに切り替わってしまったのに、
何もしないで CPU を消費し続けるのはあまり好ましいとはいえません。
画面描画可能な状態かどうかは、やっぱりアプリケーション側でも
責任を持ってきちんと判別しておく必要があります。

Direct3D 10 では SwapChain の Present() が D3DERR_DEVICELOST
の代わりに DXGI_STATUS_OCCLUDED を返します。
フルスクリーンモードからの切り替わり、スクリーンセーバー、ユーザーの
切り替えなど、バックグラウンドに回る可能性はいくらでもあるわけです。

GPU の状態を調べる ID3D10Query も、DEVIELOST 等によってインターフェース
を作成しなおす必要がなくなりました。
ただその代わり、途中で DEVICELOST 相当の状態が発生すると計測値の正当性が
失われてしまいます。この状況は TIMESTAMP_DISJOINT で調べれば判定可能
なのですが、従来は無かった動作だけに新たな処理が必要になりそうです。

Direct3D 10 シェーダー4.0サンプルプログラム

Direct3D 10 の ShaderModel4.0 は非常に自由度が高く、かなりいろいろな
ことができます。3.0 以前の制約の中で結構がんばってコードを書いた経験が
あるなら、この柔軟性にはちょっとした感動を覚えるかもしれません。

特にマニュアルを読んでいて衝撃を受けたのがこれ

Using the Input-Assembler Stage without Buffers (Direct3D 10)

マニュアルの下記の場所にあります。
+ DirectX Graphics
 + Direct3D 10
  + Programming Guide
   + Pipeline Stages
    + Input-Assembler Stage
     + Getting Started with the Input-Assembler Stage
      + Using the Input-Assembler Stage without Buffers (Direct3D 10)

SV_VertexID を元に VertexShader の中で頂点を選択して描画しています。
つまり、VertexBuffer も IndexBuffer も無く、InputLayout も設定せずに
ポリゴンが描画できてしまうのです。

このシンプルさに惚れました。

自分でも同じように、頂点バッファ無しにポリゴン描画できるシェーダーを
作ってみました。fx を読み込んで描画するだけで、一切リソースを作らなくても
こんな感じの Cube を表示することができます。

形状を生成しているのは下記の部分です。
ついにシェーダーでこんなトリッキーなコードが走るようになりました。

float3 VS_Main( uint id : SV_VertexID, uint sid : SV_InstanceID ) : POSITION
{
 uint _map[]= {
  101733320,
  104889305,
  56280874,
  125360034,
 };
 id= _map[id&3]>>(((id&60)>>2)*3);
 float3 pos;
 pos.x= id & 1 ? 1 : -1;
 pos.y= id & 2 ? -1 : 1;
 pos.z= id & 4 ? 1 : -1;
 float2 ss;
 sincos( sid * 0.5236f, ss.x, ss.y );
 pos.xy+= ss * 5;
 return mul( float4( pos, 1 ), WorldToView ).xyz;
}

シェーダー全部でもこれだけです。
 ・cube.fx

描画している C++ 側はこんな感じです。

g_iEffect->GetTechniqueByIndex( 0 )->GetPassByIndex( 0 )->Apply( 0 );
g_iDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
g_iDevice->DrawInstanced( 36, 12, 0, 0 );

g_iSwapChain->Present( 0, 0 );

ソースリストと実行ファイルは
 ・「HYPERでんち」 オリジナルサンプルプログラム
に掲載しましたので、興味ある方がいましたらどうぞお試しください。

vi の話

まったく 3D とは関係ないし、とても個人的なことで恐縮ですが
たまには開発環境に関するメモも記しておきたいと思います。

普段開発等で使ってるテキストエディタは vi です。
当初 vi を選んだ理由は単純に

 当時使っていた非力なミニコンでは、一番軽いエディタが vi だったため

です。(フルスペックの Emacs は非常に重くて、使っていると他の人に迷惑が
かかったらしい)

他にも端末の都合 (Ctrl+n 等の、Ctrl を併用したキー操作がリピート
できないキャラクタ端末だった) もありますが、
vi の選択は比較的消極的な理由でした。(emacs は憧れでした)

そんなわけで vi を使い始め、慣れてからは結局長いこと使い続けています。
日本語で文章を書いたりマニュアル作成もずっと vi です。

vi だけでなく emacs 系の操作もそれなりに使ってます。shell の行編集は
emacs mode ですし、そして何よりこの blog の初エントリが emacs 風の
キー操作に設定することですから。

WZ Mobile で Emacs もどき

WindowsMobile 等、vi が動かない処理系などでは結構 emacs 系のキーアサイン
で使っています。(PocketWZ3.0 も)

その非常に古くからある古典的なエディタの vi なのですが、ここ最近、
むしろ若い人の方が好んで vi (vim) を使っていて驚くことがあります。

確かに vim がものすごい勢いでパワーアップしており、その変貌ぶりには
目を見張るものがあります。

昔 vi を使っていた経験のある人に向けた、オリジナルを再現するためだけの
単なる vi クローンでは無いわけです。
新しい機能を次から次へと取り込んで、新機能や使いやすさだけでも使う価値
は十分。

昔から知っているオリジナルのユーザーにも、そして便利な機能を求める新しい
ユーザーにも、どちらにも魅力的なエディタとなっているのでしょう。

vi クローン系エディタはこれまでいくつも作られてきましたが、まず最初の
難関がオリジナルの再現度でした。ちょっとしたコマンドの組み合わせによっ
て複雑な動作が可能な vi は、些細な機能の違いも大きな差に感じることが
あります。

最初比較的使ったクローン系は stevie でした。
再現度も割り切りがあって画面更新のアルゴリズムはあまり優秀じゃない
ものの、軽量なので何度も移植したり手を加えたりと、
長い間お世話になりました。

画面分割機能を持った vi として xvi もありました。
これも簡易日本語化して移植したことがありますが、それ以外に極端に目立った
特長は無く、このときは結局カスタムされた stevie を使い続けていました。

elvis は emacs 系のノウハウが活かされており完成度は高かったのですが、
最初 vi らしさの1つである “行折り返し” が無く、なかなか常用に踏み切れ
ませんでした。これも移植したことがあります。

満を持して登場した vim にはやっぱり決定打となるべき魅力がありました。
再現度も高く ex モードまであって、オリジナルの vi にも無かった
Multi Level Undo ができること。visual select ができたり、プリプロセッサ
#if~ の対応も matcing fence できたり、新機能だけでも非常に便利な
エディタになっていました。

主に使い始めたのは vim3.0 からです。これもやはり日本語化移植を
行いました。(後にこれらのパッチを土田さんがまとめてくださいました)

ちなみに当時の vim は 8bit 透過ではなくて、内部コード 7bit で設計
されていました。最上位 8bit 目が visual mode の選択フラグとなっており、
反転表示するための描画情報だったのです。

そのため、8bit 透過を売りにする他のエディタに比べると、visual mode 用
データを別メモリに展開したりなどなど日本語化は結構手間がかかりました・・。

現在の vim の最新版は
http://www.vim.org/
こちらからダウンロードすることができます。
当たり前のように多国語対応なので、そのままで日本語が通ります。
便利です。

さまざまなツールやマクロ、syntax ファイルも公開されています。
例えば Shader fx (HLSL) 用の syntax を install すれば、fx の予約語など
きれいに色分け表示されます。
専用の syntax でなくても、とりあえず .fx を cpp の設定に追加するだけでも
割ときれいにいきます。

普段 fx や doxygen 用の設定を入れていますので、機会があればこの辺りの
設定などもご紹介します。