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

HP TouchSmart IQ800 に GeForce 9600M GS ドライバを入れる

HP TouchSmart IQ800 で Multi touch を試すには Windows7 が必要ですが
ノート用 GPU を使っているせいかグラフィックドライバがうまく入らない
ことがあります。

HP TouchSmart PC IQ800

GeForce 9600M GS に対応しているはずの 186.03 もそのままでは install できません。
Vista 用ドライバも使えるので、もともと付属している純正ドライバは入れられます。
プリインストールされている Vista のドライブパーティションを残してあるなら
\hp\drivers\nVidia_Graphics がそれです。

IQ827jp の場合付属しているのは WindowsVista x86 (32bit) なので、上記ドライバも
x86 のみです。Windows7 x64 を入れる場合は使えませんでした。
HP のサイトから IQ817jp 用の Vista x64 ドライバを落とせますがこれもだめです。

Windows7 beta のときはこんな感じでドライバを入れました。

一応 186.03 も inf を書き換えれば何とかなりそうです。
もともと GeForce 9600M GS に対応していると書かれているので
inf ファイルからそれらしき記述を探します。
まず nvac.inf を調べてみます。

NVIDIA_DEV.0648.** というシンボルが 9600M GS のようです。

デバイスの ID が一致しているかどうか確認します。

デバイスマネージャー → Display adapters
   → Standard VGA Graphics Adapter → プロパティ
details タブ Hardware Ids を見ると ID がわかります。& が区切り。

VEN = 10DE
DEV= 0648
SUBSYS= 900F1043

DEV が 0648 なので上で調べたシンボルと一致しています。
対応する SUBSYS が無いためそのままインストールできなかったのだと思われます。
9600M GS 相当の定義エントリ「%NVIDIA_DEV.0648.**% = ~」の行を複製し、
「**」の番号を別の重複しない値 (今回は18) に書き換えます。
SUBSYS_ の後ろを 900F1043 に置き換えることで一応インストールできる
ようになりました。

%NVIDIA_DEV.0648.18% = Section005, PCI\VEN_10DE&DEV_0648&SUBSYS_900F1043

行を複製した部分は 3カ所です。そのうち SUBSYS を書き換えたのは 2カ所。
nvac.inf 以外にも inf ファイルは多数存在し、他にも 9600M GS の記述がみられます。
本当に nvac で良いのか根拠は全くないです。
何らかの問題が生じる可能性もあるので、もし試す場合は必ず自己責任でお願いします。
早く公式な Windows7 ドライバが出てくれると良いのですが。

Aero にするにはドライバインストール後にシステム評価の再計測が必要です。

WDDM 1.1

CPU: 6.0
RAM: 6.0
AERO: 6.3
GAME: 6.3
HDD: 5.9

関連エントリ
Windows7 Multitouch API (3)
Windows7 とマルチタッチ / HP TouchSmart PC IQ800

Direct3D11 Windows7 RTM と DebugLayer

Windows7 を RTM 版に入れ替えたら D3D11(beta) のプログラムが動かなくなって
いました。DirectX SDK March2009 + D3D11 を使用しており、RC まではきちんと
動いていたものです。

止まっているのが CommandList まわりの呼び出しで関係ない関数に飛んでいます。
lpVtbl がずれているような感じ。
単に beta と互換性がなくなっているだけかもしれません。
新しい Windows7 SDK には D3D 関連のヘッダや lib も含まれているため、
こちらを使えば RTM 版 dll が用いられます。
d3dx11 や d3dcompiler は無いので、この辺を使っている場合は DirectX SDK も
併用することになります。core ではないためおそらく大丈夫だと思います。

でも結局 Windows7 SDK の d3d11.lib に切り替えてもだめ。
コンソールをみると d3d11.dll だけでなく最後に d3d11_beta.dll も読み込まれて
いました。この両者が混在しているのはあまりよい感じではありません。

DirectX SDK サンプルは動くしサンプルをビルドしても大丈夫です。
サンプルとの違いは DXGI1.1 を使っていることくらいでしょうか。
試しに DXGI1 に戻してもやっぱり動きません。
DXGI1.1 は Windows SDK 側の dxgi.lib で使用できるはずなので、問題ないと
思います。

lib の検索順番や、リンクする lib の組み合わせをいろいろテスト。
気になるのは Windows SDK 側の d3d11.lib のみ使うようにしても、必ず最後に
d3d11_beta.dll が読み込まれてしまうことです。
誰が読み込んでいるのだろうと調べているうちに DebugLayer が原因ではないかと
思いつきました。

D3D11Create~() で D3D11_CREATE_DEVICE_DEBUG を指定していると、
Debug でも Release build でも最後に d3d11_beta.dll が読み込まれています。
とりあえずこのフラグ指定を消すと Windows7 RTM で動作しました。
やはり d3d11_beta.dll との互換性が問題だと思われます。

DirectX SDK がリリースされるまで待った方が良さそうです。

OpenGLES2.0 の頂点

OpenGLES の頂点データは各頂点要素毎の配列を登録します。
頂点座標や法線など、頂点の構成要素は Direct3D では element、OpenGL では
attribute と呼ばれているようです。

glVertexAttribPointer( vloc, 3, GL_FLOAT, GL_FALSE, sizeof(vec3), v_data );
glVertexAttribPointer( nloc, 3, GL_FLOAT, GL_FALSE, sizeof(vec3), n_data );
glVertexAttribPointer( tloc, 2, GL_FLOAT, GL_FALSE, sizeof(vec2), t_data );

Direct3D の場合は基本的にパックされた頂点データを用います。
いわゆる AOS で、対する OpenGL はベクター単位の SOA になります。
とはいえ実際には Direct3D でも複数の頂点ストリームを与えることが出来るので、
OpenGL のように要素毎に配列を分離することが可能です。

また OpenGL の場合も index は共有なので頂点の位置や配列の大きさも同数です。
stride を指定すれば、Direct3D のようなパックした頂点データのポインタを登録
することができます。
どちらも出来ることや使い方にほとんど差が無くなっています。

struct vtype {
    float x, y, z;
    float nx, ny, nz;
    float tu, tv;
};
glVertexAttribPointer( vloc, 3, GL_FLOAT, GL_FALSE, sizeof(vtype), &vp->x );
glVertexAttribPointer( nloc, 3, GL_FLOAT, GL_FALSE, sizeof(vtype), &vp->nx );
glVertexAttribPointer( cloc, 2, GL_FLOAT, GL_FALSE, sizeof(vtype), &vp->tu );

普段 Direct3D 向けにデータを出力している関係上、座標系と同じように D3D と同じ
データが使えれば楽出来ます。
問題はどちらが効率がよいのかと言うこと。
D3D 形式の頂点の持ち方で速度が落ちないかどうかが心配な点です。

キャッシュの利用効率を考えると、一見インターリーブされた D3D タイプの方が
良さそうに見えます。
一度にアクセスするであろうデータが適切な局所性を持つからです。
頂点は index によるランダムアクセスになる可能性があるので、SOA の配列の効率が
必ずしも最善とは限りません。でも GPU は 1頂点分まとめてデータを読み込む
ことがわかっているので、一カ所に集まっていた方がキャッシュを活用できます。

以前そう考えて最適化のつもりで試したものの、逆に遅くなってしまうハードウエア
がありました。インデックスも個別に持てたので、頂点ストリーム毎にキャッシュを
持つなどの特殊な仕組みだったのかもしれません。もしそうなら D3D タイプの
インターリーブは同じデータが重複してキャッシュに乗ることになってしまいます。

ただこれは特殊なケースかもしれません。いろいろ見てみると、やはり最適化手法
としてインターリーブを推奨している場合もあるようです。
普通は D3D と同形式でデータを扱って問題ないと思われます。
これでデータもライブラリコードも D3D と全く同じように扱う準備が出来ました。

関連エントリ
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理

Direct3D Matrix の並びと SSE 命令 (2)

先日の SSE 命令の実行速度を試してみました。
あまり厳密なものではなくて、掲載したコード 3種類をそのままループで回しただけです。

● Core2 Duo P8600 2.4GHz Windows7 x86

x86  SSE1       17581 (msec)  mulps+addps
x86  SSE3       20623         haddps
x86  SSE4.1     17799         dpps

● Core i7 940 2.93GHz Windows7 RC x64

x86  SSE1       14320         mulps+addps
x86  SSE3       11716         haddps
x86  SSE4.1     13682         dpps

x64  SSE1       17269         mulps+addps
x64  SSE3       11076         haddps
x64  SSE4.1     13681         dpps

単位は時間で値が小さい方が高速です。

Core2 実行時、SSE3 hadd で遅くなってしまい少々焦りました。
昨日これで良いって書いたばかりなのに。結局元のコードの方が速いという結果に。
Core i7 の場合は正反対で hadd が最速になっています。

おかしなことに x64 のみ SSE1 で極端に速度が落ちています。
実際に x64+SSE1 の展開されたコードを見てみると、x64 で増えたレジスタを最大限
使おうとしてループの先頭で全部レジスタにロードしていました。
その分転送などの命令が他のケースよりも増えています。

このテストは局所的な小さいループかつ、使用したデータもストリーム入出力ではなく
同じ領域へのたたみ込みでした。
それゆえすべてのデータがキャッシュに乗った状態だと考えられます。
レジスタ間の転送命令が増えるよりもおそらくキャッシュに乗ったメモリから
読み直した方が速いのでしょう。

実際に SSE 命令が使われるケースだとストリーム処理が多いため、必ずしも
このような結果にはならないと思われます。

haddps , dpps の実行性能は CPU によって逆転することがわかりました。
この両者は比較的似たようなコードに展開されています。
ループ内の命令数も同数でした。
プロセッサによって異なるため、やはり指針としては命令数を減らすことを考えて
書くのが良さそうです。

関数は intrinsics 命令で記述しためコンパイラは inline 展開しており、
複数の関数に渡って並べ替えなどの最適化が行われています。
intrinsic 命令がどのように展開されるかだけ把握しておけば asm を使わない方が
良いかもしれません。x64 だと使えないし。
_mm_setr_ps とかは結構命令数食います。

関連エントリ
Direct3D Matrix の並びと SSE 命令
Intel AVX その3 命令
D3D10 row_major column_major
SSE についてのメモ(2) SSE4など

Direct3D Matrix の並びと SSE 命令

Matrix の並びには row major と column major の2種類あります。
たまにどっちがどっちかわからなくなりますが、row major が「三」で
column major が「川」。
演算上はどちらも違いは無くデータ配列が異なっているだけです。
特にシェーダーのコード上は大差ないです。演算もデータ構造も使う側に委ねられて
いてどちらでも使えます。

シェーダーコンパイラ fxc は /Zpr, /Zpc のオプションで並びの方向を指定する
ことが出来ます。
このオプションによって、たとえば vector * matrix の乗算が dp (dot product)
に展開されるか mad に展開されるか切り替わります。
どっちの設定だろうが乗算の前後を入れ替えれば良いだけなので、正直自分の記述と
デフォルトが異なっている場合にオプションで入れ替えればよい、とそのくらいの
認識でした。

fxc のデフォルトは /Zpc で column-major になっています。
上の演算であれば mad に展開されます。

シェーダーの場合 dp, mad どちらでも基本的に演算命令数は変わらず、効率の差は
生じません。ただ mad の方が vector の w=1 の代入が簡略化されるケースがある
ため小さくなることがあります。
これがそのまま GPU ネイティブコード上でも影響があるかどうかはまた別です。
AMD stream のように swizzle 時に直接 w に 1 を書き込めるなら差は無くなると
思います。

; opos= mul( mat, float4(v.xyz,1) );

; /Zpr
mov r0.xyz, cb0[4].xyzx
mov r0.w, l(1.000000)
dp4 o0.x, cb0[0].xyzw, r0.xyzw
dp4 o0.y, cb0[1].xyzw, r0.xyzw
dp4 o0.z, cb0[2].xyzw, r0.xyzw
dp4 o0.w, cb0[3].xyzw, r0.xyzw

; /Zpc
mul r0.xyzw, cb0[1].xyzw, cb0[4].yyyy
mad r0.xyzw, cb0[0].xyzw, cb0[4].xxxx, r0.xyzw
mad r0.xyzw, cb0[2].xyzw, cb0[4].zzzz, r0.xyzw
add o0.xyzw, r0.xyzw, cb0[3].xyzw

Direct3D ではもともと column major の形式で D3DMATIRX が定義されていました。
D3D9 以前、固定機能パイプラインでは API に渡すデータ並びを共通化する必要が
あったためと思われます。
そのため自前のライブラリでもほぼ D3DMATRIX 互換で使っていました。

typedef struct _D3DMATRIX {
    union {
        struct {
            float   _11, _12, _13, _14;
            float   _21, _22, _23, _24;
            float   _31, _32, _33, _34;
            float   _41, _42, _43, _44;

        };
        float m[4][4];
    };
} D3DMATRIX;

ただし実際はターゲットハードウエアやライブラリの仕様に合わせたり、最適化の
ために row major 形式に変換することが多かった思います。
シェーダーの演算上は大差なくても contant buffer の利用効率に影響があるからです。
昔は Constant Buffer が非常に小さかったため、index で参照する matrix は
4×3 でないとほとんど入りませんでした。
そのため Bone として使う geometry 用の matrix 乗算ルーチンをライブラリに
加えています。これは乗算結果を転置の 4×3 で返す専用命令です。

そろそろ D3DMATRIX 互換性もいらないかなと思い、最初から全部 row major で
持つようにライブラリを書き換えてみました。

基本的には自前の Matrix を下記のように定義し直すだけで、メンバにアクセスして
いるコードはほぼそのまま動きます。

struct {
    float  _11, _21, _31, _41;
    float  _12, _22, _32, _42;
    float  _13, _23, _33, _43;
    float  _14, _24, _34, _44;
};

修正が必要なのは、部分的に vector みなしてアクセスしていた処理と、SSE 命令で
記述していた各種関数です。
4×4 の matrix 同士の乗算では修正は不要ですが、それ以外はいろいろ見直す必要が
ありそうです。
たとえば vector との乗算だと、mad ではなく dp 相当の水平演算になります。

SSE4.1 の dpps を使ってみました。SSE4 のヘッダは smmintrin.h。
実命令数がわかるように dest 側のレジスタを共通にしています。

// SSE4.1
__m128 xmm0;
xmm0= _mm_load_ps( &v.x );

xmm1= _mm_load_ps( &_11 );
xmm1= _mm_dp_ps( xmm1, xmm0, 0xf1 );

xmm2= _mm_load_ps( &_12 );
xmm2= _mm_dp_ps( xmm2, xmm0, 0xf2 );
xmm1= _mm_add_ps( xmm1, xmm2 );

xmm2= _mm_load_ps( &_13 );
xmm2= _mm_dp_ps( xmm2, xmm0, 0xf4 );
xmm1= _mm_add_ps( xmm1, xmm2 );

xmm2= _mm_load_ps( &_14 );
xmm2= _mm_dp_ps( xmm2, xmm0, 0xf8 );
xmm1= _mm_add_ps( xmm1, xmm2 );

Shader のように簡単に記述できますが SSE4.1 対応 CPU (Penryn 以降) でないと
動きません。
試しに SSE3 の haddps を使ってみました。

// SSE3
xmm0= _mm_load_ps( &v.x );

xmm1= _mm_load_ps( &_11 );
xmm1= _mm_mul_ps( xmm1, xmm0 );

xmm2= _mm_load_ps( &_12 );
xmm2= _mm_mul_ps( xmm2, xmm0 );

xmm1= _mm_hadd_ps( xmm1, xmm2 );

xmm3= _mm_load_ps( &_13 );
xmm3= _mm_mul_ps( xmm3, xmm0 );

xmm2= _mm_load_ps( &_14 );
xmm2= _mm_mul_ps( xmm2, xmm0 );

xmm3= _mm_hadd_ps( xmm3, xmm2 );
xmm1= _mm_hadd_ps( xmm1, xmm3 );

全く同じ命令数でした。
SSE3 対応 PC の方が多いしこっちの方が良さそうです。
SSE4 命令は命令長が長いので生成バイナリも小さくなります。
ただもっと複雑なケースだと、入力を自由に選択できたり出力マスク可能な dpps
の方が融通が利いて少ない命令で書けるようです。
以前はこんな感じ。

// SSE1 (column major)
xmm0= _mm_load_ps( &v.x );

xmm1= xmm0;
xmm1= _mm_shuffle_ps( xmm1, xmm1, _MM_SHUFFLE(0,0,0,0) );
xmm2= _mm_load_ps( &_11 );
xmm1= _mm_mul_ps( xmm1, xmm2 );

xmm3= xmm0;
xmm3= _mm_shuffle_ps( xmm3, xmm3, _MM_SHUFFLE(1,1,1,1) );
xmm2= _mm_load_ps( &_21 );
xmm3= _mm_mul_ps( xmm3, xmm2 );
xmm1= _mm_add_ps( xmm1, xmm3 );

xmm3= xmm0;
xmm3= _mm_shuffle_ps( xmm3, xmm3, _MM_SHUFFLE(2,2,2,2) );
xmm2= _mm_load_ps( &_31 );
xmm3= _mm_mul_ps( xmm3, xmm2 );
xmm1= _mm_add_ps( xmm1, xmm3 );

xmm0= _mm_shuffle_ps( xmm0, xmm0, _MM_SHUFFLE(3,3,3,3) );
xmm2= _mm_load_ps( &_41 );
xmm0= _mm_mul_ps( xmm0, xmm2 );
xmm1= _mm_add_ps( xmm1, xmm0 );

matrix 同士の乗算も水平演算命令で書けるかと思い少々考えてみましたがレジスタ
が足りなくてうまくいっていません。
x64 だとレジスタ数が倍あるため intrinsic で書いたとおりのコードに展開されます。
x86 では待避のためのスタックが使われていました。

関連エントリ
Intel AVX その3 命令
D3D10 row_major column_major
SSE についてのメモ(2) SSE4など