月別アーカイブ: 2007年6月

D3D10/DX10 のリソースなど

D3D8~D3D9 と比べて API の構造も使い方も用語も一新された D3D10 ですが
自由度があがったこともあり、とりわけリソース周りがわかりにくくなりました。

データ自体非常に汎用性があって、各種バッファの統合が行われました。
つまり「何にでも使えるようになった」わけです。
今までは作成時に用途もある程度決め打ちしていて、バッファとはデータ
そのものであって同時に使い方も表しています。
D3D10 ではデータと用途を分離して考えます。

例えば同じバッファでも IASetVertexBuffers() に渡せば頂点とみなされ、
IASetIndexBuffer() に渡せば Index としてアクセスできます。
頂点と index を、全く同じバッファに格納しておくこともできると
マニュアルには書かれています。
MSDN Resource Types (Direct3D 10)

同じようにテクスチャも、Texture1D/2D/3D はデータ構造をあらわすデータに
過ぎません。それらをどのようなアクセスするか、あらためて View を作る
必要があります。
サンプルを見ても、Texture は初期化時以外はほとんど出てきません。
D3D9 までの Texture と同じような扱われ方をしているのは D3D10ResourceView
の方です。

まとめ
・Resource には Buffer と Texture の 2種類存在する
  ・Buffer Resource
  ・Texture Resource

・Buffer と Texture にはそれぞれ Typed と Typeless がある。

・Buffer
  ・最小構成単位を element と呼ぶ。
  ・element は Float3 / Int4 のように最大で 4 component (4ch)
  ・異なるフォーマット(型)の element を混在できる。

・Texture
  ・最小構成単位を texel と呼ぶ。
  ・texel は element と同じように最大 4 component (4ch)
  ・miplevel 毎に subresource を持ち、さらに array できる。
  ・sampler をつかった filtering や multisample できる。

subresource は D3D9 までの Surface に相当するようです。

MSDN Choosing a Resource (Direct3D 10)
こちらに各用途に利用できるリソース一覧があります。
よく見ると RenderTargetView に Texture3D が使えます。
D3D10 では VolumeTexture へのレンダリングにも対応しているようです。
D3D10_TEX3D_RTV
この構造体で RenderTarget に W 方向の位置を与えることができるようです。

DX9 までだったらまず CapsViewer を起動して、マニュアルに書かれてあっても
本当に使えるものなのかどうか確認する必要がありました。将来のための拡張用
であって、現行ハードでは NO になってることが多くてよくがっかりします。
D3D10 だと Caps が無いので大丈夫なんでしょう。

shader から見たリソースは、ConstantBuffer 以外は ShaderResource になる
ようです。例えば tbuffer は、HLSL 上と API 上は cbuffer と全く同じように
構造体の変数としてバッファにアクセスできます。ところが内部の命令的には
ConstanBuffer より Texture に近く、アクセス時に ld 命令が発行されています。

Texture1D tex1;
Buffer buf1;
tbuffer { float4 tbParam; };

float v= tex1.Load( 0 ).x;
float v= buf1.Load( 0 ).x;
float v= tbParam.x;

これらの命令はどれも ld で、未使用であっても必ず 1 element (1 texel)
単位で読み込まれていました。表記上似ていても直接アドレッシング指定できる
ConstantBuffer とは異なっています。

EM・ONEのペン紛失と各種PDAのスタイラス

PDA のペンをなくしたのは、おそらく覚えている限り igeti MI-P2
(MI Zaurus) 以来です・・。
ポケットに入れて持ち歩いていたら、どこかで EM・ONE のスタイラスを
落としたようです。たまに勝手に抜けていることがあったし、
机の上でも細くて見つけづらいなとは常々思ってました。

まあでもだけど EM・ONE にはポインティングデバイスがあるし、
いままでも、ほとんど操作にスタイラス使わなかったし、
別になくても良いかなと思ってほっときました。

ところが困ったことに肝心なときにリセットができません。
操作には全く使わなかったスタイラスも、リセットにだけは
必要だったんですね。

家にあったスタイラスを集めてみました。

左から順に
 12.0cm  HP Jornada720 (H/PC)

 10.9cm  SHARP MI-110M
  9.6cm  SHARP igeti MI-P2 (伸ばしたとき)
  9.0cm  SHARP MI-C1

 10.7cm  IBM WorkPad 30J
 10.7cm  Palm m100

 11.0cm  HP iPAQ h3630 (Compaq)
  9.8cm  HP iPAQ h2210
  8.0cm  SHARP W-ZERO3[es] WS007SH

  8.7cm  Nintendo DS Lite
  7.5cm  Nintendo DS

帰ってきた John Carmack と新 Engine

WWDC2007 で、id Teck 5 という新エンジンが紹介されました。
もう「さすが」というしかありません。

・PC Watch WWDC 2007基調講演詳報(1)
・4Gamer.net【Mac】John Carmack氏,WWDC 2007の基調講演で新エンジンを披露

GeForce8800GTX は 768MByte もの VRAM を搭載しており GTS でも 640M です。
RADEON も 2900XT は 512M からスタートしており、今年中には 10万未満の
ハイエンドビデオカードも 1G が当たり前になるのではないか、
という勢いです。

PC のメインメモリ容量も増加する一方で、値段が下がったこともあって
1GByte くらいは当たり前。
Vista 向け PC だと 2G あたりがいい値段です。
32bit OS ではもう窮屈ですが、もしかしたら VRAM も近いうちに 32bit
の限界を突破してしまうかもしれません。

家庭用ゲーム機でも Xbox360/PS3 は 512M のメモリを搭載しており、
その大半はおそらく VRAM として使われているのではないかと思われます。

実際 GeForce8 では 1枚で 200~400MByte を超えるテクスチャを貼った
モデルがリアルタイムですんなり動いてしまうので、
もう古い常識は通じないなと思ってました。
(例えば 2048×2048 の 32F (1pixel 128bit = 16byte) の CubeMap など)

だけど、それをいったい何に使うか、どう使っていくかといったビジョンが
あるかというとまた別で、ただただハードの進化に必死についていっている
だけでもあります。

400MByte のテクスチャごときで感心しているときに、この John Carmack
の新エンジンは発想の常識を超えて、上限をすっかり無くしてしまった
ようです。なるほど。

20GByte ものテクスチャ領域を使うなんて、現状のハードウエアスペックを
考えるとそんな馬鹿なと思われるかもしれませんが・・
確かに全シーンをすべてユニークなテクスチャで覆うことができたらば、
あんなこともできるようになるし、こんなこともできるし、
今までに無いライティングエンジンの設計もできるし、
夢がものすごく広がります。

メモリが増えたからといって、そして今後増えたとしてもこんな発想は
さっぱりできなかったし、ただただ「さすが」としかいえません。
限られた帯域で実際どれだけ転送が間に合うのかいろいろ気になりますが、
実際動いているのもすごいわけです。
やっとマルチテクスチャが一般になりつつあるときに発表された
DoomEngine もそうでしたが、想像し得る未来のスペックを先取りした
開発力はやっぱりすごい。

D3D10/DX10 RADEON HD2900XT

これまで RADEON は PixelShader の slot 数にわりと小さ目の上限があって、
シェーダーが巨大化すると全部入りきらなくなったり、multi pass に
分けなければならなかったりと結構対応に困ることがありました。

例えば ShaderModel2.0 は 1.0 に比べると飛躍的な拡張が行われましたが、
ps2.0 の命令数は演算で 64slot、texture 命令込みで 96slot
しかありません。

ps1.0 の 8命令+4命令 (1.4 で 8+6) に比べるとかなり大きいものの、
従来1命令で演算できた _bx2 が無くなっていて、ps1.0 よりも余分に
命令数を消費することがあります。
swizzle も VertexShader ほど自由度がなく、出力レジスタが固定で
最後に代入や alpha 合成が入ったりして意外に制限になったりします。

Xbox1 だと ps1 だけど xmma/xdd 等の複合命令があって 1slot に 2演算
畳み込めたり、ファイナルコンバイナでも演算を追加できたり、
alpha に別演算を入れることで実質 2~3倍程度の演算ができたことを
考えると、これが思ったほど余裕がありません。
per pixel lighting やら Shadow Mapping とかやりだすと
あっという間に 64+32 slot 使い切ってしまいます。

shader には loop 命令があるので、本当は命令 slot の上限とは関係なく
もっと長い shader 命令を走らせることが可能です。
ところが ps2.0~ps3.0 の場合 constan register のアクセスに index
参照使うことができないので、異なる constant register を変数参照する
場合は全部ループ展開されてしまいます。

この 96命令の ps2.0 は、RADEON で 9700→X800 とかなり長く続いたため、
ほとんど制限の無い GeForce6~7 と比べて少々厄介でした。

GeForce6 は ShaderModel2.x に見切りをつけて、RADEON よりも 1世代先に
ShaderModel3.0 に移行してしまいます。

・RADEON 9700 (sm2) – GeForceFX5800 (sm2x)
・RADEON X800 (sm2) – GeForce6800 (sm3)
・RADEON X1800 (sm3) – GeForce7800 (sm3)
・RADEON HD2900 (sm4) – GeForce8800 (sm4)

ps3.0 になると上限は最小 512命令に拡張されます。
GeForce6800~ はほぼ上限無しの 512より長い shader を走らせることが
可能でした。

RADEON X1800 でやっと ShaderModel3.0 対応になりますが、PixelShader は
512 命令が上限です。96命令に比べると大幅に増えたものの、GeForce6~
とかでべらぼうに長い shader に慣れてしまうと、これが 512命令の上限にも
あっさり引っかかってしまいます。

特に Shadow 関連のシェーダーは、たとえ事前演算でも PCF で巨大に
なりがちです。
さらに視線交差を求めるレリーフマップ等の機能をまじめにシーン内の
一般のシェーダーに組み込むと、あっという間に slot を埋め尽くします。

先日登場した RADEON HD 2900XT は、GeForce8800 同様待望の
ShaderModel4.0 対応です。
さらに制限も減っており、機能も大幅に拡張されています。

まだ ShaderModel3.0 用シェーダーしか試していませんが、GeForce 向けに
作った 512 命令を超える巨大な Shader もすんなり動作しました。
不思議なことに NVIDIA ShadowMap もそのままで動作しています。

1世代ずれなかったおかげか、まだまだ ShaderModel4.0 を使い切った
shader も特に無いせいかもしれませんが、、、
とりあえず GeForce6~ のように制限なく ShaderModel3.0 が走る
RADEON が登場したわけです。
なんだか同じ土俵に来たな、という感じがします。

ShaderModel4.0 側の機能は実はまださっぱり試していません。
これからです。
PixelShader でも index 付きループにたたみこめるし、
必要なら広大な buffer を任意にアクセスできるし、
メモリアクセスに sampler を使わなくてもいいし、
ShaderModel4.0 では速度以外の制限がほとんどなくなった気がします。

CPU情報を調べる MultiCore/Thread

Windows 上でどれだけ CPU Core があっていくつの Thread をハードウエアで
同時に実行できるのか取得する方法を調べてみました。
CPU または Core の数だけなら

GetSystemInfo()

を使って取得することが可能です。また GetCurrentProcessorNumber() で現在の
Thread の Processor が識別できますし、割り当て可能な CPU のマスクは
GetProcessAffinityMask() を使って取得可能になっています。

ただしこれだけでは HyperThread なのか独立した CPU Core なのか区別が
付きません。そこまできちんと識別するには GetLogicalProcessorInformation()
を使います。
・MSDN GetLogicalProcessorInformation
ただしこの API がサポートされているのは Vista 以降か XP x64 Edition 以降、
Windows Server 2003 以降など比較的新しい OS に限定されているようです。

GetLogicalProcessorInformation() で得られた構造体リスト
SYSTEM_LOGICAL_PROCESSOR_INFORMATION
には、各プロセッサの状態や Numa の情報、Cache 情報なども含まれます。
Core の情報だけ調べるには

SYSTEM_LOGICAL_PROCESSOR_INFORMATION のメンバ Relationship の値が
enum 定数 RelationProcessorCore の場合だけ参照します。
このとき ProcessorMask に各 Core に対応する実行可能なハードウエアスレッド
が bitmask として格納されているので、この bit 位置を調べることで対応する
HyperThread がわかります。

(1) GetLogicalProcessorInformation() で複数の
  SYSTEM_LOGICAL_PROCESSOR_INFORMATION のリストを得る。

(2) リストのうち Relationship の値が RelationProcessorCore のものが
  本来の CPU Core に相当する。(HT含まず)
  ただし本当に CPU が複数のってるのか MultiCore なのかはここでは
  わからない。

(3) (2)の場合のみ SYSTEM_LOGICAL_PROCESSOR_INFORMATION の ProcessorMask
  を調べると、その Core が対応する CPU ID (同時実行可能な Thread 数)
  がわかる。

例えば HT 可能な Pentium4 CPU が 2つ乗ってる場合、同時に実行可能な
ハードウエアスレッドは 4つであり、GetSystemInfo() でも CPU が 4つある
ものとしてみなされます。

GetProcessAffinityMask() は 4つ分の bit が立った 0x0000000f を返します。

このとき GetLogicalProcessorInformation() で得られる Core 情報の構造体は
2つで、それぞれが

SYSTEM_LOGICAL_PROCESSOR_INFORMATION.ProcessorMask= 0x00000005;
SYSTEM_LOGICAL_PROCESSOR_INFORMATION.ProcessorMask= 0x0000000a;

の値をとります。このことから GetSystemInfo() や GetCurrentProcessorNumber()
で得られるプロセッサの番号は下記のように対応していることがわかります。

0= Core0 の HT0
1= Core1 の HT0
2= Core0 の HT1
3= Core1 の HT1

実際のコードにしてみるとこんな感じです。

static int GetThreadInformation( DWORD* cpuinfo )
{
 const int MAX_INFOBUFFER= 32;
 SYSTEM_LOGICAL_PROCESSOR_INFORMATION infobuffer[MAX_INFOBUFFER];

 DWORD retlen= 0;
 GetLogicalProcessorInformation( NULL, &retlen );
 if( retlen > sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)*MAX_INFOBUFFER ){
  return 0; // error
 }

 if( !GetLogicalProcessorInformation( infobuffer, &retlen ) ){
  return 0; // error
 }

 SYSTEM_LOGICAL_PROCESSOR_INFORMATION *infop= infobuffer;
 int coreCount= 0;
 int cpuCount= 0;
 for( DWORD size= 0 ; size < retlen ;
    size+= sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION), infop++ ){

  switch( infop->Relationship ){
  case RelationProcessorCore:
   {
    int htCount= 0;
    for( int i= 0 ; i< 32 ; i++ ){
     if( infop->ProcessorMask & (1<       cpuinfo[i]= ( htCount++ << 8) + ( coreCount );
      cpuCount++;
     }
    }
    coreCount++;
   }
   break;
  default:
   break;
  }
 }
 return cpuCount;
}