Archives

October 2007 の記事

元の記事エントリ
Direct3D 10 ShaderModel4.0 ピクセル単位の半透明ソートを行う

少々わかりにくかったかもしれませんので補足します。

実際のシーンのレンダリング時に、フレームバッファの各ピクセル単位で
描画履歴を全部保存しています。実際に書き込まれた色とZ深度、そして
そのピクセルに何回描いたか。
これを浮動少数 float4 ×2~4 に encode して詰め込み、最大 6回分
格納します。

最後に encode された情報を全部 decode して、順番にピクセルごとに
blend しなおすわけです。

これらの処理は各ピクセルごとに独立しています。回数判定もピクセル
単位なので、ポリゴンの重なりや Draw 命令の回数等とは関連しないし
影響を受けることもありません。
またこの処理は Hardware の Blend 機能だけで実現する必要があります。

blend image

32bit float には演算によって 8bit ×3個まで値が格納できます。
IEEE754 の単精度の仮数部は 23bit ですが実際は 24bit の情報量が
あるためです。
3回以上値が加算されてしまうと、デコードするときに不要な情報が混ざり
ノイズとなります。Blend 演算だけで必要なピクセルの選択も行わなければ
なりません。
これは浮動少数単精度の制限を使います。3回以上描き込まれたピクセルの
値は仮数部の精度の範囲外に押し込み、必要な値だけ残るようにします。

まとめると
・float に複数の値を正確に畳み込んで、あとから取り出す手段
・Blend 機能だけで値を encode する手段
・Blend 機能だけで履歴(回数)による値の選択と切捨てを行わせる手段
が必要となるわけです。


AlphaToCoverage との違いは次のとおりでしょうか。

・粒状感が出ない
・ブレンドアルゴリズムを任意に持てる (SrcAlpha+InvSrcAlpha 以外も可能)
・最後に Shader でブレンドするので、Hardware Blend 機能より高度な
 演算も実装できる

MRT を増やして追加パラメータを格納すれば、ソート後のブレンド時に
ピクセルごとに演算手法の選択もできるかもしれません。

またアルゴリズム的に大変応用が利くので、フレームバッファに情報を
蓄積していく技としてさまざまな手法に活用することができます。

Z剥がしの方法とは違い、シーンのレンダリングは一度で済むのでそこそこの
速度で動作しています。

欠点は
・現在最大6回までしか保持できない
・浮動少数演算の丸め対策で現状 7bit 精度になっている(6 layerのみ)
・保存する分だけメモリと 帯域を かなり 使う
・並べ替えなど、動的分岐の多い重いシェーダーが走る
などなど。


追記 2007/11/02: Direct3D 10 ShaderModel4.0 Stencil Routed A-Buffer とお詫び も見てください。


半透明描画時に、ピクセル単位でソートを行うシェーダーを考案しました。

wheelhandle_ss10 per pixel alpha sorting

とりあえずデモプログラムをご覧ください。(ソース付)
実行には DirectX10 環境 (Vista+GPU) と
DirectX SDK November 2007 の Runtime が必要です。

wheelhandle_ss10t.zip

[SPACE]   pause 一時停止
[3]       3 layer mode
[6]       6 layer mode
[S]       sort ありなし切り替え
[U]       + teapot 追加
[D]       - teapot 削除

ピクセル単位でソートしているので、順番が正しく保たれるだけでなく
半透明ポリゴンが交差しても矛盾しません。

こちらがソートありで

wheelhandle_ss10 per pixel alpha blending with sort

ソートなしにするとこうなります。

wheelhandle_ss10 per pixel alpha blending without sort

ソートしないと、teapot のオブジェクト間だけでなく、自分自身の取っ手や
ふたの重なりにも矛盾が生じていることがわかるかと思います。


●特徴

この手法の特徴は本当にソートしてブレンドしていることと、GPU だけで処理が
完結していることです。CPU は何もしていません。

またシーンのレンダリングは一回で済み、レイヤー単位でレンダリングしなおす
必要はありません。


●欠点

ピクセル単位で最大 layer 数(重なり回数)制限があります。
2種類のシェーダーを作成しています。それぞれ pixel 単位で 3枚までのものと、
6枚までです。

MultiRenderTarget かつ 128bit pixel を使っているため、それなりにメモリと
帯域を消費します。速度についてはまだ未評価です。

今の方法では格納できる値に制限があるため、ソート時に参照する Z の精度に
限界があります。14~16bit なので、交差面の境界が荒くなる可能性があります。


●ソートが必要な理由

Direct3D 10 では GeometryShader の導入によって、より自由度が高まりました。
ポリゴンの動的な追加削除が可能で、また StreamOutput を使ってジオメトリ
の更新も GPU だけでできるようになっています。

反面ジオメトリの決定や描画において CPU を介さないということは、CPU に
よる半透明ソートができなくなることも意味しています。

これを解決する手段として、ソートが不要な加算半透明だけ用いる方法があります。

また Direct3D では、MultiSample + AlphaToCoverage を使う方法が有望と
されています。これはアルファブレンドの代わりに高密度の点描を行い平均化
するものです。

今回のデモ ss10 のアルゴリズムは全く異なるアプローチで、本当にピクセルを
ソートしてしまいました。


● 3 layer シェーダー

・8bit のフルカラー画素値を用いることができます。
・半透明描画の重なりはピクセルごとに 3枚までです。
・透明度(Alpha)は各ピクセル単位で独立した値を持つことができます。
・3枚を超える重なりは、最後に描画された 3pixel が用いられます。Z 値を
 考慮した選択ではないので、重なりが多いと不定の色に見えることがあります。
・MRT が 2枚なので 6 layer より効率はよくなっています。
・ソート時の Z 精度が 16bit あるためで 6 layer より実用的です。


● 6 layer シェーダー

・R G B A 各チャンネルは 7bit カラーに制限されます。
・その代わり半透明描画の重なりはピクセルごとに 6枚まで対応可能です。
・透明度(Alpha)は各ピクセル単位で独立した値を持ちます。
・6枚を超える重なりは、最初に描画した 3pixel と最後に描画された 3pixel
 が用いられます。6枚を超えると 3layer 同様に不定の色に見えることがあります。
・MRT が 4枚必要で、それなりにメモリを帯域を消費します。
・ソート時の Z 値が 14bit なので、交差したポリゴンの境界などの精度が
 3layer に劣ります。



●原理とアイデア

MRT (MultiRenderTarget) と Blend 機能だけを使い、レンダリングした
ピクセルの蓄積を行っていることが最大の特徴です。

レンダリング時に、直前の結果を即時反映させることができるのは
RenderTarget か DepthStencil しかありません。これらの機能の組み合わせ
だけで、描画したピクセルの選択と判定、蓄積のためのエンコードを実現する
必要があります。いくら Shader に自由度があっても、同じパスで出力を
受け取ることができないからです。
なお今回は DepthStencil は使用していません。

Direct3D 10.0 (ShaderModel4.0) では、128bit 浮動少数フォーマット
R32G32B32A32_FLOAT による Blend が可能です。これを利用しています。

RTc= RTc + Pc * RTa
RTa= RTa * Pa

RTc = RenderTarget color
RTa = RenderTarget alpha
Pc = Input Pixel color
Pa = Input Pixel alpha (U:2^8, D:2^-8)

下記の表は、CPU で Pixel 値のシミュレーションを行ったものです。
0x10 から順番に 0x11, 0x12 ~ とカラー値を同じピクセルに重ねていきます。
pixel ~ が書き込まれたフレームバッファの画素値、decode ~ がデコード
して取り出したカラーを表しています。

step 0
pixel U: hex=41800000 float=16
pixel D: hex=41800000 float=16
decode U:  10
decode D:  10

step 1
pixel U: hex=45888000 float=4368
pixel D: hex=41808800 float=16.0664
decode U:  11 10
decode D:  11 10

step 2
pixel U: hex=49908880 float=1.18402e+006
pixel D: hex=41808890 float=16.0667
decode U:  12 11 10
decode D:  12 11 10

step 3
pixel U: hex=4d989088 float=3.19951e+008
pixel D: hex=41808891 float=16.0667
decode U:  13 12 11 00
decode D:  20 12 11 10

step 4
pixel U: hex=51a09891 float=8.62193e+010
pixel D: hex=41808891 float=16.0667
decode U:  14 13 12 20 00
decode D:  00 20 12 11 10

step 5
pixel U: hex=55a8a099 float=2.3176e+013
pixel D: hex=41808891 float=16.0667
decode U:  15 14 13 20 00 00
decode D:  00 00 20 12 11 10

step 6
pixel U: hex=59b0a8a1 float=6.21563e+015
pixel D: hex=41808891 float=16.0667
decode U:  16 15 14 20 00 00 00
decode D:  00 00 00 20 12 11 10

step 7
pixel U: hex=5db8b0a9 float=1.66354e+018
pixel D: hex=41808891 float=16.0667
decode U:  17 16 15 20 00 00 00 00
decode D:  00 00 00 00 20 12 11 10

デコード結果を見ると、Blend 機能を使った積和演算のみでも、最初の 3値と
最後の 3値が正しく保持されていることがわかります。

アルゴリズム U と D のどちらかを使えば 3 layer が実現可能で、両方組み
合わせることで 6layer が実現できます。
このように、制限があるのは演算アルゴリズム上の問題なので、MRT 数を
増やしたとしても単純にレイヤー数が増えるわけではありません。

画素の割り当て下記のとおり。

Algorithm-U / MRT0,1

MRT-0      X    Y    Z   W
-----------------------------
Pixel0-2   R    G    B  ExpU
-----------------------------

MRT-1      X    Y    Z   W
-----------------------------
Pixel0-2   ZH   ZL   A  ExpU
-----------------------------

Algorithm-D / MRT2,3

MRT-2      X    Y    Z   W
-----------------------------
Pixel3-5   R    G    B  ExpD
-----------------------------

MRT-3      X    Y    Z   W
-----------------------------
Pixel3-5   ZH   ZL   A  ExpD
-----------------------------


●浮動少数演算の丸め問題

Blend の演算機能と浮動小数値の特性を使って、ピクセルの蓄積と適切な
値の選択を行っています。このとき非常に厄介な問題が、浮動少数演算時の
丸め処理です。

入力 3 値まではこの問題が発生しないため、当初実現できたのは 3 layer
のみでした。3 値を越えると桁あふれによって追い出された bit が丸め込まれ、
上位 bit に影響を与える可能性があります。

例えば

Algorithm-U: C B A

と入力された段階で D を入力すると A が切り捨てられます。このとき A の
値の重さによって上位 D C B に影響が出ます。例えばすべて A B C D が
すべて 0xff だった場合、あふれた 0xff の繰り上がりの 1 によって
上位すべて 0 になってしまいます。影響は無視できません。

この問題を回避するために、失われたピクセル値が何であったのか推測する
必要があります。もし丸め込まれていたら decode 時に減算すればいいわけです。

捨てられたピクセルは情報として保持されていませんが、同時に Algorithm-D
を用いることによって、6 値までなら失われた値を相互に補完することが
できそうです。

step3
Algorithm-U: C B A
Algorithm-D: C B A

step4
Algorithm-U: D C B
Algorithm-D: C B A

step5
Algorithm-U: E D C
Algorithm-D: C B A

step6
Algorithm-U: F E D
Algorithm-D: C B A

この場合、step5 なら U の捨てられた値は D の B で参照でき、step6 なら
D の C で参照することができます。

同じように Algorithm-D でも入力値を捨てたことによるまるめこみが入ります
が、Algorithm-U から求めることができます。

一見うまくいきそうですが、双方同時にまるめこみが発生した場合に残念ながら
適切な値をとることができませんでした。おそらく実現できるのは 5 layer
までと考えられます。U と D で反転した値を入力しておくことで、同値の
ずれを割り出すことができます。

この問題を解決ができなかったので、6 layer シェーダーでは値を 7bit に
小さくすることで、とりあえず桁上がりが発生しないようにしています。
8bit フルに格納できなかったのが残念です。


●今後に向けて

Direct3D 10.1 / ShaderModel4.1 では、Blend 機能が大幅に拡張されます。
特に MRT 単位で独立した Blend パラメータを設定でき、また UNORM/SNORM
フォーマットでの Blend も可能となります。
これにより、今よりも実装も実現もずっと楽になると考えられます。
layer を増やせるかもしれません。

とりあえず実現が目標だったので・・速度最適化などはほとんど行っていません。
特に並べ替えの実装はあまりに手抜きなので、まだまだ改善の余地は多く
残されていると思います。

シミュレーションのあと実際にシェーダーとして実装したところ、予想と異なる
挙動がありました。CPU と GPU による浮動少数演算の違いだと考えられます。
ss10 ではごまかしが入っているので・・改善しないと。

layer オーバー時に、Stencil を使って最前面の pixel だけ選択できないか
も考えていました。Stencil Test の結果と Depth Test の結果の組み合わせ
によって Pixel を捨てるかどうか自由に決められればできそうです。
現状は Stencil Test の結果だけで決まり、Depth Test との組み合わせは
Stencil の更新方法の選択のみなのでうまくいきませんでした。


今回の DirectX SDK November 2007 では、ReleaseNote を見ただけでは
Graphics 周りだとほど大きな更新がなかったように見えます。

ところがこの更新は、一般の開発者にとっては非常に大きな、とても
意味のあるものとなりそうです。

マニュアルが大幅に刷新されています。

これまでいくつか指摘してきたマニュアルの不備や不具合なども修正され、
説明不足だった点もかなり手が入っているようです。
Direct3D 10.1 など開発や更新自体がひと段落し、マニュアルや解説の
方にも手が回るようなってきたのでしょうか。
と思ったら、build 番号もかなり増えています。
DirectX SDK Version 一覧

マニュアルの更新によって、Direct3D 10.1 の機能もかなり見えてきました。
Shader の説明でも、4_1 で追加された機能がわかりやすく表記されています。

D3D 10.1 / ShaderModel 4.1 の新機能といえば TextureCubeArray がありますが
他に Texture のサンプリング命令として Gather() があるようです。
これは一度のサンプリングで、テクスチャの 4ピクセルの値をまとめて参照
することができます。4 つのピクセルの R だけが、一度に xyzw として
読み出されるわけです。

ATI の RADEON X1900 系の機能に精通している方ならすぐに思い当たる
ものがあるかと思います。
かつて ATI が、NVIDIA ShadowMap に対抗して用意した ShadowBuffer 用の
サンプリングアクセラレータ機能が、まさに Gather() そのものでしょう。
4pixel 同時にサンプリングしてもハード的にコストがかからないので、
これはかなりよい機能だと思います。

実はこちらのエントリで DXGI_FORMAT の詳細を調べたときに、
機能フラグとして D3D10_FORMAT_SUPPORT_SHADER_GATHER が
存在していることを発見しました。このときはマニュアルやヘッダを検索
しても他に GATHER は出てこないので意味がわかりませんでした。

おそらく、このフラグがついているフォーマットだけが Gather() 命令で
利用可能なのだと思われます。(10.0 では無し)
Direct3D 10 DXGI_FORMAT の機能対応一覧
Direct3D 10.0 DXGI_FORMAT_SUPPORT

こちらで調べているような アセンブラ命令の定義の意味も、
マニュアルに乗るようになりました。


他に個人的に大きなトピックとして、DDS の詳細項目の追加があります。
DDS は比較的古いフォーマットです。Direct3D8 で FourCC が拡張されて
Direct3D9 のフォーマットも全部格納できるようになりました。
DDS Texture format memo

ですが、Direct3D10 の全フォーマットを格納することができませんでした。
今回 Direct3D 10 のフォーマットに対応すべく拡張方法が定義されました。

基本的には先頭の DDSURFACE ヘッダ 128 byte は互換性があります。
その直後に 20byte の DDS_HEADER_DXT10 ヘッダが追加された形となるようです。

つまりヘッダは合計 148byte 、少々中途半端です。この追加ヘッダの存在を
識別する方法がまだ不明です。また DDS_HEADER のうち未使用となった
dwCaps がちょうど 20byte あるので、ここに DDS_HEADER_DXT10 が収まるの
だったらしっくりきます。
ヘッダの種別は dwMagic で区別するようにみえますが、この具体的な値が
書かれていないようです。

とにかくこれでようやく TextureArray も dds ファイルとして格納できる
ようになりそうです。
付属の dxtex.exe (DirectX Texture Tool) はまだ D3D10 フォーマットに
対応していないようです。


早いけど出たみたいです。
DirectX SDK - (November 2007)


ZEROProxy を使うと ZERO3 や EM・ONE が iPod touch のモデムになる
そうです。
伊勢的新常識 ZEROProxy
iPod touch は非常に薄くて小さいし、よさそうですね。

iPod touch と ZERO3/EM・ONE の間は無線LAN でつながります。
EM・ONE を WM6 にアップグレードする前に一度 WindowsXP のノートで
試してみました。

(1) EM・ONE (S01SH v1.03a) に ZEROProxy を入れる
(2) 無線LAN のアドホック接続で、EM・ONE と NotePC を接続する

Bluetooth で EM・ONE をモデム化するのと似ています。
だけど 無線LAN 方が Bluetooth より速いので、Bluetooth 接続よりも
速度が出ていました。
emobile の通信速度も Bluetooth より速いので、うまく安定すれば
ZEROProxy + アドホック接続は、ノートPC ユーザーにもお勧めな通信手段に
なるかもしれません。
(直後に EM・ONE のアップグレード発送+ノートPCを手放したので
実用レベルでは試してないです)


残念ながらブラウザでアクセスポイント経由の接続しかサポートして
いない PSP では、直接 ZERO3/EM・ONE につながらないと思われます。
今後のファームでアドホックのインターネット接続をサポートしてくれれば、
たぶん使えるようになるでしょう。

直結が無理でも、無線LAN のアクセスポイント経由だと使えます。

emone psp

無線LAN AP 経由で EM・ONE+ZEROProxy とつないで、
PSP のブラウザでインターネットにアクセスできました。

ここでは持ち歩ける小型の 無線LAN アクセスポイント を使っています。
Logitec SkyLink LAN-PWG/APR
USB 電源で動くので、外付けのバッテリーをつないでいます。
EM・ONE と上のセットをかばんの中に入れておけば、電車の中でも
ゲームをしているふりをしつつネットを見たりできると思います。

LAN-PGW/APR はルータですが、ZEROProxy を使うのでアクセスポイント
モードで使っています。

Proxy 経由なので、残念ながらゲームでのネット接続はおそらくできない
でしょう。リモートプレイも TCP 9293 が必要らしいので、この構成では
つながりませんでした。
リモートプレイをする(インターネット経由)


以下設定手順の詳細

●ルータ/アクセスポイントの設定

(1) アクセスポイント(AP)モードにします
(2) 無線LANの基本的な設定を行います。
  ・SSID の設定
  ・セキュリティの設定(WEP等)
(3) DHCPサーバーが有効になっていたら off にします。

ルータの初期 IP アドレスは仮に 192.168.1.200 としておきます。


●EM・ONE (WM端末) の設定

(1) ZEROProxy をインストールします。

(2) 無線LAN を ON にしておきます。

(3) 無線LAN の設定を行います。
  ・設定→接続→ネットワークカード→ワイヤレス
  ・ワイヤレスネットワークの構成から、新しい設定の追加
  ・ネットワーク名 に SSID を入れる
  ・接続先 社内ネットワーク設定
  ・ad-hoc にはチェックを入れない
  ・次へ
  ・認証: 任意(ルータに設定したものと合わせる)
  ・次へ
  ・ネットワーク認証の構成: IEEE 802.1x~のチェックをはずす

(4) IP アドレスを登録します
  ・設定→接続→ネットワークカード→ネットワークアダプタ
  ・社内ネットワーク設定
  ・内蔵ワイヤレスLAN を選択
  ・「指定したIPアドレスを使用する」
  ・IPアドレス: 192.168.1.2 (必要に応じてアレンジしてください)
  ・サブネットマスク: 255.255.255.0

EM・ONEα(S01SH2) で実験していますが、ZERO3 等他の WindowsMobile
端末でもほぼ同様だと思います。


●PSP の設定

(1) 設定→ネットワーク設定→インフラストラクチャーモード

(2) [新しい接続の作成] → 手動で入力する

(3) 無線LAN の設定をします
  ・SSID: ルータに設定したもの
  ・ワイヤレスLAN セキュリティ設定 : ルータの設定に合わせる
  ・アドレス設定: カスタム
  ・IPアドレス設定: 手動
  ・IPアドレス: 192.168.1.3
  ・サブネットマスク: 255.255.255.0
  ・デフォルトルータ: 192.168.1.2
  ・プライマリDNS: 192.168.1.2
  ・プロキシサーバー: 使用する
  ・プロキシサーバー アドレス: 192.168.1.2
  ・プロキシサーバー ポート番号: 8080
  ・インターネットブラウザ: 起動しない
  ・接続名: 任意


●接続実験

(1) ルータの電源をON
(2) ZEROProxy を起動する
(3) EM・ONE でダイヤルアップ接続する (HSDPA)
(4) EM・ONE で無線LANを ON、ルータ(アクセスポイント)に接続する
(5) PSP でブラウザを起動する。上で設定した接続を選ぶ。


関連エントリ
PS3/PSP emobile EM・ONE 経由でリモートプレイ


追記: この構成の接続は実用性は皆無です。機材があったので当時試してみただけですが、決してお勧めしません。ゲームはできずブラウザだけで速度も遅く、EM・ONE そのまま使ったほうが速いです。


2008/12/26 追記: WMWifiRouter を使った接続設定はこちらです。 WMWifiRouter の場合ブラウザ以外も使用できます。



M・ONEα(S01SH2) の d3dmcaps の結果を送っていただいたので
更新しました。halmako さんありがとうございました。
Direct3D Mobile DeviceCaps 一覧

XScale ドライバなのか相変わらずで、やはり GoForce 5500 は
使われていないようです。
唯一の違いは SubVersion が 1→2 と増えていること。
この変化は Advanced W-ZERO3[es] (WS011SH) とまったく同じなので、
これが WindowsMobile6 対応版のドライバなのだと思われます。

d3dmcaps 自体 WM5 の SDK でコンパイルしているので、本当は WM6 SDK
での変更点がないか調べておいた方がよかったかも。

よく見たら NVIDIA のサイトでも EM・ONE が紹介されています。
NVIDIA EM・ONE
NVIDIA GoForce 5500
もっと活用できるようにして欲しいところです。

関連エントリ
SHARP EM・ONE の 3D 機能


携帯電話と連動可能な腕時計が出ています。

アイバートM(エム)製品情報
アイバートM(エム)

日本には SPOT (Smart Watch) がないので、こういうのはもっと出てきて
ほしい気もします。
もしかして Bluetooth や携帯との連動がなかったとしても、常時表示で
普通のカラー液晶画面の腕時計は初めてかな?と思って調べてみました。

カラーだけど普段は画面が消えていたりとか、固定のカラー液晶や
その積層タイプだったらいろいろあったかと思います。
Fossil Japan BIGTIC - SCROLLING
CASIO 液晶の色が変わる"デュプレックスLCディスプレイ構造"搭載
MP4 WATCH

と思ったらありました

カシオ、カラー液晶モニタを搭載した新型リストカメラ

CASIO リストカメラのカラー液晶版 WQV-10。
STN 液晶 2001年、これが世界初だったそうです。


Fossil から有機ELタイプの新作が出ていました。
自己発光なので、やはり LED タイプのように普段は消えていて
ボタンで呼び出すのでしょうか。
Fossil Japan JR9464

そういえば i:VIRT の初代も有機ELでした。これも常時点灯しておらず
ボタン操作によって画面を呼び出すタイプだったようです。
i:VIRT 製品仕様


これまで動作確認程度にしか使っていなかった 64bit 版 Windows ですが、
本格的に移行してみたら結構快適でした。家の環境も 64bit に完全移行
してしまいした。

2GB 7000円くらいで増設メモリも買ってきて、合計 4GB のメモリも
しっかり認識しています。
VirtualPC で RAM 1GB 以上割り当てるなんて贅沢もできます。


64bit 環境でも基本的に 32bit アプリケーションがそのまま動きます。
64bit 化の恩恵を受けるには 64bit 専用で build しなければなりませんが、
必ずしもすべてのアプリが焦って対応する必要は無さそうです。

問題となるのはドライバ関連と dll の互換性です。
32bit アプリケーションは 32bit dll しか読めず、64bit アプリケーション
が呼び出せるのも 64bit dll だけです。

たとえば IE の x64 版は 32bit 用に配布されているプラグインが使えない
ので、この場合は 32bit 版 IE を使うことになります。よって 64/32bit
両バージョンが存在するアプリでは、プラグインも2種類作っておく必要が
あるでしょう。


64bit といっても正確には IA64 もあるので、ここでは x64 を指していると
思ってください。Windows 内部では x64 は AMD64 と表現されることもある
ようです。

 WIN64 (64bit)
   x64  (AMD64 と表記されることもある)
   IA64

 WIN32 (32bit)
   x86


完全な環境移行のために em1keypc も 64bit に対応してみました。
em1keypc 1.28 for PC

em1key はもともと WindowsMobile 用に作ったキーカスタマイズツールです。
定義ファイルの script により、応用の1つとして 親指シフト入力
できるようになります。

従来の em1keypc も一応 64bit 環境で動作しました。ただキー置き換えは
有効なものの、64bit アプリケーションでは IME 状態との同期がとれません。
手動で em1keypc の状態も切り替える必要がありました。

x64 版も bulid したので、両方常駐させることで一応 32bit アプリケーションでも
64bit アプリケーションでもどちらでも動作するようになります。

  em1keypc.exe    (x86 32bit アプリ用)
  em1keypc64.exe  (x64 64bit アプリ用)

全く同じプログラムを 2つ起動しなければいけませんが、これは上で書いた
とおり dll を共有できないことに起因します。手抜きです。
とりあえず x64 で動かしただけの暫定対応なので、この辺は今後の課題です。

oyayubiwm を入れてから
・em1keypc を起動して [自動起動に設定] → [隠す]
・em1keypc64 を起動して [自動起動に設定] → [隠す]
これで 64bit 環境でも一応親指シフト入力できるようになりました。
(Vista x64 のみ確認しています、XP x64 は未確認です)


oyayubiwm em1key用親指シフト入力スクリプト
em1keypc v1.28 for PC (x86,x64)
em1key WindowsMobile


imekeyset5 は WindowsMobile5 で日本語の漢字変換に関する操作を
カスタマイズするツールです。たとえば変換に使うキー、文節移動キー、
分節伸縮キー、等を任意のキーに設定することができます。

・ZERO3[es] のテンキー入力時に方向キーだけで漢字変換できるようにする。
 MSIME 利用時でも、ATOK と同じように操作できる。

・外部キーボードをつないでいるときに、カーソルキーを使わないでホーム
 ポジションから変換操作ができるようにカスタマイズする。

・使い慣れたほかの漢字変換エンジンの操作に合わせる。

などといった応用が考えられます。


PC の MS-IME では、コントロールパネルからこれらのキー割り当てを自由に
変更することができました。この機能は WindowsCE にも受け継がれており、
HandheldPC など初期の WindowsCE ではコントロールパネルから同様の
カスタマイズが可能です。

PocketPC になってからこの辺の IME 設定がごっそり削られていて、キー
割り当ての変更もできなくなっています。ただレジストリにはエントリが
存在しており、直接書き換えるときちんと機能します。
単に設定画面としてのインターフェースのみ削られていることがわかります。

imekeyset5 はこの IME のカスタマイズ用レジストリを直接書き換えている
だけです。キー割り当て表を import/export したり、style 選択といった
UI のみ作っています。実際のキー割り当てなど便利な機能は MS-IME が
もともと持っているものです。

そのためマニュアルにも書いていますが、設定が終わったらメモリ節約の
ためにアンインストールしても構いません。設定の変更が必要な時だけまた
インストールして使えば OK です。


今回 EM・ONE が WindowsMobile6 になったので動作確認してみました。
WM6 でも MSIME のバージョンは全く同じ 3.1 で、レジストリのエントリ
も構造も同一なようです。実際に imekeyset5 も動作し、カスタマイズ
できました。
同じ WindowsMobile6 の Advanced W-ZERO[es] (WS011SH) でも同様に動作
すると思われます。

imekeyset5 v1.21


●MSIME に切り替える

EM・ONE や W-ZERO3[es] は ATOK が入っています。WM5/WM6 の ATOK
ではカスタマイズ手段が無さそうなので、キー割り当てを変更するには
まず MSIME に切り替える必要があります。

設定→システム→ATOK設定

で「日本語入力にATOKを利用する」のチェックを外して再起動します。
これで MSIME に切り替わります。


●W-ZERO3[es] 向け設定手順

MSIME の場合、標準状態だとテンキー入力時に漢字変換ができません。
そのためカーソルキーだけで変換操作できるように設定できます。

(1) imekeyset5 を install して起動する
(2) [Phone Style追加] ボタンを押す
(3) 左上の Style 選択、プルダウンメニューから [ Phone ] を選ぶ
(4) すぐ右の [標準にする] ボタンを押す
(5) [EXIT] して リセットボタンを押す


●独自カスタマイズまでの流れ

(1) imekeyset5 を install して起動する
(2) 左上の Style 選択、プルダウンメニューから [ MSIME_PPC ] を選ぶ
(3) [Custom にコピー] を実行して複製する
(4) [Customを出力] でファイルに書き出す (export)
(5) 設定変更
(6) [Customに読込] で書き換えた設定を読み込む (import)
(7) 左上の Style 選択を [ Custom ] に切り替える
(8) [標準にする] ボタンを押す
(9) [EXIT] してリセットする


すでにカスタマイズしてあるファイルを読み込むなら、(4) と (5) を
飛ばして、複製後にすぐ (6) で読み込みます。
実際の設定例のサンプルがいくつか付属しています。

keyset_wm5egg.txt
keyset_wm5egg2.txt

など。

関連エントリ
EMOBILE EM・ONE (S01SH) の α化


WindowsMobile6 にアップグレードした EM・ONE (S01SH2) を使ってみました。


●フォントの描画

初期状態でのフォント描画速度が改善されていました。
WM5 の EM・ONE (S01SH) の場合は、FontCache の設定で増やしておかないと
最初非常に低速でした。フォントキャッシュ容量は Smart-PDA.net Pocketの手
等のソフトを使って設定することができます。

WM6 の EM・ONE (S01SH2) の場合は、W-ZERO3[es] などと同じように、
特に設定しなくても十分な速度で動いています。


●リモートデスクトップ

リモートデスクトップで PC (Vista) につないでみました。

emone remote

特に LAN 設定をしなくても、USB 等の ActiveSync 接続でもつながります。
今回は Bluetooth 経由なので遅いですが、それなりに動きます。

Windows 側では、あらかじめ システム→リモート から
「リモートデスクトップ」の接続を有効にしておく必要があります。

オプションの「リモートデスクトップを画面に合わせて調整する」と
「全画面表示」にチェックしておくと 800x480 の画面になります。
全画面時は (OK) で切断できました。

EM・ONE はマウスをつなぐと快適です。

emone remote

さらに外部キーボードもつないでいます。ただキーコードの違いからか
入力できない文字があったので、実用するにはもう少しきちんと調べる
必要がありそうです。


●名刺リーダー

この日のために(?)たまっていた名刺を読み込ませてみました。
内蔵のカメラで撮影するだけで連絡先に名刺の内容を登録してくれます。

認識は思ったより時間がかかり 1枚あたり 15~30秒 ほど待たされる
ことがあります。読み込みは結構正確で 社名、名前、メールアドレス、
URL などさまざまな情報を読み取って識別してくれます。

読み込んだ情報は ActiveSync で同期し、PC の Outlook 上で確認と
誤認識の修正を行いました。以下気がついたことなど

・EM・ONE のカメラは左端なので、位置合わせに少々慣れが要ります。

・シャッター音がした後もしばらくは動かさないほうがいいようです。
 若干タイムラグがあります。

・縦長の名刺でも、横向きのまま文字認識することができました。

・ロゴマークなどに書かれている文字も認識しようとします。不要な
 文字が混ざることがあるため、不要な部分はできるだけうつさない
 方がいいようです。


WindowsMobile6 へのアップグレードのためメーカー送りになっていた
EM・ONE が届きました。予想よりもずっと早い仕上がりでした。

EMOBILE EM・ONE WindowsMobile6 アップグレード申し込み
ZERO3[es] に 小型 Bluetooth アダプタ PTM-UBT3S をつないでみた

5日の申し込みで11日の発送なので、実質8日間で戻ってきたことになります。
最初に電話した申込日から数えてちょうど2週間です。
外側は一緒なのに、中身が変わっただけでなんとなく新機種を購入した
気分です。

変更点は WindowsMobile6.0 へのアップグレードです。
結果として EM・ONE α (S01SH2) 相当となり、S01SH2 のマニュアルや
CD-ROM も付属していました。

ぱっと見てわかるメニューの違いは下記のとおり。

● Today 画面
・JAJAH のリンク
  IP 電話です。
  プリインストールではなく、タッチすることでアプリのダウンロード
  &インストールが行われます。

●設定画面
・S01SH情報
  S01SHバージョン: 2.00

・External GPS
・Windows Update
・暗号化


●プログラム
・3D Box ダウンロード (URLのショートカット)
・Messenger
・Sprite Backup
・Windows Live
・名刺リーダ
・リモートデスクトップモバイル

おそらく Advanced W-ZERO3[es] WS011SH とほぼ同じだと思われます。
これで WindowsMobile6 でのプログラムの動作確認がしやすくなりました。
imekeyset5 の対応もできそうです。


昨日の「64bit 開発設定のメモ」
Executable files: に DXSDK の bin\x64 を path を追加しましたが、
よく考えると x86 (32bit) 上で実行する分には不要なものでした。
訂正させていただきます。


実際に Vista x64 をメイン PC にインストールしました。
現在 Windows Vista x86 (32bit) が入っているので 2つ目の HDD に
入れてみます。

HDD 1 - C: Vista x86 (32bit)
HDD 0 - D: Vista x64 (64bit) install 予定

OS が起動した状態から install できなかったので、いったん DVD から
boot します。

インストール予定の HDD 0 側はあらかじめ 100G ほどの空きを作って
おきました。特に HDD を初期化しなくても新規相当で install 可能で、
もとからあったフォルダやファイルなどもそのまま残ります。


install 後、とりあえず Vista x86 と x64 の dual boot となります。
Windows Vista では、どちらで起動してもシステムが入っている方が
C: ドライブに割り当てられます。

x86 (32bit) で起動すると x64 側ドライブ(HDD 0)が D:
x64 (64bit) で起動すると x86 側ドライブ(HDD 1)が D:

システムドライブが固定されるため、それぞれツールなどをインストール
しても混乱せず、混用されないのでこの方が便利です。


アプリケーションは OS 毎に個別のインストールになりますが、
作成したデータなどは共有したいところです。
起動する OS によってフォルダのドライブが異なるため、データは一意に
アクセスしづらくなります。

たとえば x86 で C:\DATA にデータが格納してあった場合、x64 で起動
すると D:\DATA になります。

そこで、両方で共通にアクセスする可能性のあるフォルダは
symbolic link を作りました。
コマンドプロンプトから下記のように実行します。

mklink /d C:\DATA D:\DATA

これで x64 側でも C:\DATA が作られます。実際のデータは D:\DATA に
格納されますが、使ってる分には完全に C:\DATA でアクセスできます。

mklink 実行時に権限が無いといわれることがあります。その場合は
コマンドプロンプトを管理者権限で起動しておきます。スタートメニュー
から起動するとき、右ボタンの「管理者として実行」を選択します。

以前だったらこうなることを想定して、普段から仮想ドライブを作って
おいたりしなければいけないところです。Vista の symbolic link の
方がフォルダ単位で柔軟に対応できるし慣れてるので扱いが楽です。


64bit 環境でも 32bit のアプリケーションがそのまま動作するので、
今のところは特に不具合もなく使えています。普段使ってるツールも
動くし乗り換えてもいいかもしれません。まだ1日しか使ってないけど。

x64 専用アプリに切り替えなくても、たとえばコンパイラの設定も
x86_amd64 (32bit 用 64bit コンパイラ) のままでも動きます。


Program Files も2種類できていました。

C:\Program Files
C:\Program Files (x86)

実行するアプリケーションによっていくつかの環境変数が切り替わる
ようになっています。

MSDN 64-Bit Windows WOW64 Implementation Details

たとえば環境変数 ProgramFiles は、64bit アプリでは
C:\Program Files を指していますが 32bit アプリケーションでは
ProgramFiles は C:\Program Files (x86) になっています。

同じように PROCESSOR_ARCHITECTURE も必要に応じて AMD64 や x86
になります。


急に乗り換えたのは Maya plug-in を x64 に対応させるためでした。
そろそろメモリが厳しいということで、グラフィックデザイナーの環境が
64bit に移行しつつあります。


Windows で x64 用の build 環境を作る時のメモです。

●VisualStudio 2005 IDE

MSDN 方法 : Visual C++ プロジェクトを 64 ビット プラットフォーム用に設定する

構成マネージャで新しい Platform を追加するだけでいいようです。
WindowsCE の CPU 追加と同じような感じ。
その後 Options の Projects and Solutions → VC++ Directories でも
Platform を選択してフォルダなどの確認や追加をします。
32bit 版 Vista で作業しているので、クロス開発用バイナリ x86_amd64 が
選択されていました。
DirectX SDK 用に下記の設定を追加します。必ず一番上にしておきます。

Executable files:
 $(DXSDK_DIR)Utilities\Bin\x64

Inclue files:
 $(DXSDK_DIR)Include

Library files:
 $(DXSDK_DIR)Lib\x64


●コマンドライン用

MSDN 方法 : 64 ビットの Visual C++ ツールセットをコマンド ラインから有効にする

vcvarsall.bat の中を見ると各環境用の個別のファイルを呼び出して
いるだけでした。それぞれ専用の環境変数設定が用意されています。

x86        32bit 用
amd64      64bit 用
x86_amd64  32bit 上で 64bit バイナリの作成 (クロスコンパイル)

環境設定用 bat ファイル

x86       VC\bin\vcvars32.bat
adm64      VC\bin\amd64\vcvarsamd64.bat
x86_adm64  VC\bin\x86_amd64\vcvarsx86_amd64.bat

ひとつの Makefile で各環境用のバイナリを作るなら、これらの設定も
Makefile に記述することになるでしょう。

x64 の場合 lib のそれぞれのパスに lib\amd64 を追加するだけで、
または DirectX SDK のように x86 を x64 に置き換えます。
binary path の場合は上記のようにクロスコンパイルを含めた 3 種類の
構成があります。x86_amd64 では x86 用 bin も必要なので、その前に
x86_amd64 用パスを追加します。

x86
  C:\Program Files\Microsoft Visual Studio 8\VC\bin

amd64
  C:\Program Files\Microsoft Visual Studio 8\VC\bin\amd64

x86_amd64
  C:\Program Files\Microsoft Visual Studio 8\VC\bin\x86_amd64
  C:\Program Files\Microsoft Visual Studio 8\VC\bin

あとはコンパイルオプションの変更など。
ほぼ同じ指定が可能で -arch:SSE2 は不要。

__asm ははじかれたけど、intrin.h は大丈夫そうです。


非常にいまさらな感じで、とっくに常識かもしれませんが、文字列
リテラルに用いる _T() と TEXT() はどこが違うのか気になったので
調べてみました。

Windows で文字や文字列を扱う場合、TCHAR (_TCHAR) 型を良く用います。
これはコンパイル時のオプションによって char と wchar_t に定義内容
が変わります。
TCHAR 型を使用しておくと同一ソースコードでも比較的容易に文字列の
扱いを切り替えることができます。

特に NT 系の API では、文字列として両方受け付けるよう関数が2セット
用意されていて、混在することが可能です。
9x 系の時代は MBCS による表現が用いられていたので、Local かつ
当時から引き継いでいるプログラムなどはそのまま char 型として扱う
ことができます。

逆に WindowsCE は wchar_t しか受け付けないので、CE とのコード共有を
考えると TCHAR 型への対応は重要でした。
ゲーム機でも Dreamcast では WindowsCE を選択できたので、
WindowsCE を使う場合は WCHAR 等の wide 型が用いられました。

TCHAR と同じように文字列定数や文字定数も切り替える必要があります。
その場合に用いられるのが _T() や TEXT() マクロです。この両者は同じ
意味だといわれています。

例えば _T("initdata.dds") とか TEXT("maze.fx") など、どちらで
記述しても特に差はありません。

_T() の実際の宣言は VC/include の tchar.h にありました。
定義部分だけ抽出すると下記のようになります。

// tchar.h
#ifdef  _UNICODE
#define __T(x)      L ## x
#else
#define __T(x)      x
#endif

#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)

シンボル _UNICODE の定義によって __T() を切り替え、それを _T() と
_TEXT() に定義しなおしています。2段階定義になっているのは、おそらく
マクロ展開のためにプリプロセッサのパスを最低2回通すためでしょう。

 例えば #define DDSFILE "initdata.dds" と定義した場合
 _T(DDSFILE) だと L"initdata.dds" になりますが
 __T(DDSFILE) の場合は LDDSFILE となります。


TEXT() の定義は VC/PlatformSDK/include の winnt.h の中にありました。

// winnt.h
ifdef UNICODE
#define __TEXT(quote) L##quote      // r_winnt
#else
#define __TEXT(quote) quote         // r_winnt
#endif

#define TEXT(quote) __TEXT(quote)   // r_winnt

定義されている意味は同じです。こちらはシンボル UNICODE の定義に
よって __TEXT() の内容が変わり、それをさらに TEXT() に定義
しなおしています。

_TEXT() は tchar.h だけど TEXT() と __TEXT() は winnt.h など、
双方シンボルが衝突しないように微妙に使い分けられているようです。


_T() と TEXT() はそれぞれ定義している場所が違うので、include して
いるヘッダによっては必ずしも両方使えるとは限らないようです。
また UNICODE、_UNICODE の両方を必ず定義するか、または定義しない
ようにしないと定義内容が異なってしまいます。

ヘッダを調べると、一部のヘッダ (OleDlg.h, MSAcm.h, pdh.h) では
UNICODE と _UNICODE の同期を取るコードが含まれていました。

OleDlg.h
// syncronize UNICODE options
#if defined(_UNICODE) && !defined(UNICODE)
        #define UNICODE
#endif
#if defined(UNICODE) && !defined(_UNICODE)
        #define _UNICODE
#endif

ただしこれらのヘッダは必ず include されるとは限らないので、
結局は VisualStudio が挿入するコマンド行シンボル
/D "UNICODE" /D "_UNICODE" に依存しているようです。


ちなみに Windows の wide character は UNICODE の 16bit エンコー
ディングで、MBCS では ShiftJIS が使われていました。
UTF-8 など、UNICODE でも MBCS の可能性があります。
文字まわりはいつも苦労するところですが、個人的にはプログラム中
でも UTF-8 を使うことが結構あります。

プログラム中で扱う文字コードの他に、ソースコードの文字をどうしようか、
といった悩みもあります。以前は OS の言語によってソースを ShiftJIS
とみなしてしまう仕様があって、0x5c(\) の処理でよく問題が
出ていました。

マニュアルを見ると VisualStudio2005 では、BOM さえあれば UTF-8
でも大丈夫そうです。
MSDN コンパイラおよびリンカでの Unicode のサポート

試してみました。テキストエディタでソースを ShiftJIS で開いて

// 表
 Error

とか書いて保存します。UTF-8 で開くと

// [95]\
 Error

となります。文字としては不正ですがとりあえずやってみます。
bom つきで保存するとコンパイルエラーにはならず、行末の \ が
有効となっていることがわかります。(vim で :se bomb )
bom 無しで保存すると ShiftJIS とみなされ、行末の \ が無視
されてコンパイルエラーになります。(vim で :se nobomb )
きちんと UTF-8 を認識しています。

さらに、UTF-8 かつ BOM 付きで実際にコンパイルされた文字コードを
見てみます。

wchar_t* wstr= L"あい";
char*    str= "あい";

ソースコードは 0xe3 0x81 0x82 0xe3 0x81 0x84 で UTF-8 です。

wstr の表示出力は 0x3042 0x3044 0x0000
str の表示出力は 0x82 0xa0 0x82 0xa2 0x00

結局 UTF-16(UCS-2) と ShiftJIS に変換されていました。
UTF-8 そのままは通らないようです。


DXGI_FORMAT_R11G11B10_FLOAT は RenderTarget として使えましたが、
DXGI_FORMAT_R9G9B9E5_SHAREDEXP はレンダリングできませんでした。
Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験(2)
Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験

また Direct3D10/DirectX10 では Pixel 形式も頂点形式もインデックスも
統合されています。BC1 (DXT1) は画像圧縮用フォーマットなので、
これは頂点形式に使えるのでしょうか。

フォーマット Type 毎に対応している機能や、どの用途に使えるのか
ID3D10Device::CheckFormatSupport() で調べることができます。

各フォーマットの機能一覧を作成してみました。

Direct3D 10.0 DXGI_FORMAT 機能対応一覧


表の BF は CreateBuffer() で作成できるかどうか、
1D ~ 3D はそれぞれ CreateTexture1D() ~ 3D() で作成できるかどうか、
Texture として使えるかどうかを意味しています。

BF = ID3D10Buffer
1D = ID3DTexture1D
2D = ID3DTexture2D
3D = ID3DTexture3D


VB, IB, SO は、VertexBuffer, IndexBuffer, StreamOutput や
InputLayout の定義に使えるかどうかです。これを見ると、やはり
BC1~BC5 (DXT) や、R8G8_B8G8/G8R8_G8B8 (YUV) などの圧縮形式は
画像以外に使えないことがわかります。

R9G9B9E5_SHAREDEXP はだめですが、R11G11B10_FLOAT は頂点でも
使用できるようです。

IndexBuffer に使えるのは
R32_UINT (32bit), R16_UINT (16bit) しかありませんでした。

VB = InputLayout (VertexBuffer)
IB = IndexBuffer
SO = StreamOutput


SL, SS, SC は、シェーダー中のリソースアクセス手段を示しています。
SL は Sampler を使わないで直接 Load() 命令が使えるかどうか、
SS は Sampler 経由の Sample() 命令が使えるかどうかです。

同じように SC は SampleCmp() 系命令に対応しています。わかりやすく
言えば、SC が付いたフォーマットはハードウエアシャドウマップ
(NVIDIA ShadowMap) に使えるということです。

SL = Load()
SS = Sample() / SampleGrad() / SampleLevel()
SC = SampleCmp() / SampleCmpLevelZero()


MI は MipMap です。ほとんどすべてのフォーマットが使用可能に
なっています。MA は Mipmap の自動生成対応かどうか。

MI = MipMap
MA = MipMap 自動生成


RT が付いたものは RenderTarget に使えます。整数形式を含めて
レンダリング可能な形式は結構多いようです。ただし Blend 可能な
なのは BL が付いたものだけです。Blend 対応フォーマットはそれほど
多くありませんが、32bit FLOAT もしっかり blend 対応になっている
など D3D9 世代と比べたらかなりの贅沢さです。

DS は DepthStencil 用フォーマットです。DepthStencil はまだまだ
特殊なフォーマットであることがわかります。

MT が付いたものはさらに MultiSample 対応になります。
DI (Display) は単なる RenderTarget ではなく、FrontBuffer として
使用可能なフォーマットを意味しているようです。

RT = RenderTarget
BL = Blend 対応
DS = DepthStencil
MT = MultiSample RenderTarget
DI = Display


その他の機能シンボルの意味は下記のとおりです。

CL = CPU で Lock (Map)できる
CW = 他の format に cast できる
MR = MultiSample Resolution 対応
ML = Multisample テクスチャを Load() 可能


表を見ていると 1bit 形式の DXGI_FORMAT_R1_UNORM はかなり特殊な
フォーマットであることがわかります。2D Texture にしか使えず
Mipmap もありません。RenderTarget もだめ。


またこのフォーマット機能表を確認しておくと、Direct3D 10.1 の
機能拡張の意味が見えてきます。
まず block-compressed (BC?) texture へのレンダリングができる
ようになるとのこと。

さらに Blend 対応の拡張があります。10.0 だと下記のものだけですが、
これが DirectX 10.1 ではすべての UNORM、SNORM で使えるように
なるそうです。

R32G32B32A32_FLOAT
R32G32_FLOAT
R32_FLOAT
R16G16B16A16_FLOAT
R16G16_FLOAT
R16_FLOAT
R11G11B10_FLOAT
R10G10B10A2_UNORM
R8G8B8A8_UNORM
R8G8B8A8_UNORM_SRGB
R8G8_UNORM
R8_UNORM
A8_UNORM



D3D10/DX10 の新しいテクスチャフォーマットの続きです。
Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験

DXGI_FORMAT_R11G11B10_FLOAT の 11bit / 10bit FLOAT は、
指数部が 16F と同じだったので 16F との相互変換は比較的容易です。
符号を落として下位bit を切り詰めるだけで十分かもしれません。
実際に相互変換してみました。

まず DXGI_FORMAT_R11G11B10_FLOAT への変換です。16F に変換した後
下位 bit の切り落としを行っています。符号が無いので入力が負数の
場合は 0 にクランプしています。

DWORD F32toF11( const float* f32 )
{
    WORD  _f16[3];
    D3DXFloat32To16Array(
            reinterpret_cast<D3DXFLOAT16*>( _f16 ), f32, 3 );

    for( int i= 0 ; i< 3 ; i++ ){
        if( _f16[i] & 0x8000 ){
            _f16[i]= 0;
        }
    }

    return  (((DWORD)_f16[0] >>  4) & 0x000007ff)
           |(((DWORD)_f16[1] <<  7) & 0x003ff800)
           |(((DWORD)_f16[2] << 17) & 0xffc00000);
}

次に DXGI_FORMAT_R11G11B10_FLOAT から 32F に変換してみます。
同じように一旦 16F を経由しています。

void F11toF32( float* f32, DWORD f11 )
{
    WORD   _f16[3];
    _f16[0]= (WORD)((f11 << 4) & 0x7ff0);
    _f16[1]= (WORD)((f11 >> 7) & 0x7ff0);
    _f16[2]= (WORD)((f11 >>17) & 0x7fe0);

    D3DXFloat16To32Array( f32,
            reinterpret_cast<D3DXFLOAT16*>( _f16 ), 3 );
}

D3DXFLOAT16 のメンバはマニュアルでは WORD Value となってますが、
実際には小文字の value でした。しかも protected だったので
上の例では cast でごまかしています。


DXGI_FORMAT_R11G11B10_FLOAT と同様、Direct3D10/DirectX10 でもう
1つ追加された新フォーマットがあります。

DXGI_FORMAT_R9G9B9E5_SHAREDEXP

こちらも調べてみました。5bit の exponent を持つことから、
R11G11B10_FLOAT 同様により広い範囲をカバーできます。ただし共有
されているため極端にスケールの異なる component を持つことは
できないでしょう。

FLOAT のグループではないことと、フォーマット名に E の成分も記載
されていることから、exponent の演算はシェーダーを併用するのでは
ないかと予想していました。

ところが実際に試すと E5 の成分がきちんと反映された値が返ってきます。
またシェーダーでは E5 の値を直接読み取ることができず、w は存在
しませんでした。(w を読み取ろうとすると 1.0 固定となる)
また RenderTarget にはできません。

R9G9B9E5_SHAREDEXP の各 bit の意味は下記の通りです。

E5    B9        G9        R9
01111 111111111 111111111 111111111

指数部は 5bit なので、16F や R11G11B10_FLOAT と同じように 15
が 0 の offset 15 と仮定します。このとき R9G9B9 を 0 にすると
真っ黒になりました。

上のように R G B すべての bit を 1 にするとほぼ (1.0, 1.0, 1.0)
相当になっています。ただ厳密に一致せず、それぞれわずかに 1.0
より小さな値になります。

この結果からわかるのは、指数部は 16F や R11G11B10_FLOAT ほぼ同じ
内容で合っているのではないかということ。
また R9G9B9 は IEEE754 等の浮動少数の仮数に近いものではなく、
9bit の値がそのまま格納されていることです。511 が厳密に 1.0 に
ならないため、R8G8B8_UNORM のような UNORM 変換 1/511 倍ではなく
1/512 倍している可能性があります。

次に 0xc0040201 を入れてみたところ完全に 1.0 に一致しました。

// 0xc0040201
E5    B9        G9        R9
11000 000000001 000000001 000000001

これは上記のように各 component が 1 で指数は 24 になっています。
offset は 15 なので 24-15 = 9、つまり 1 * (2^9) = 512 相当に
なります。簡単に書けば 1<<9 です。

同様に 0x84020100 でも (1.0, 1.0, 1.0) になります。こちらは
最上位に合わせており次のようになります。

// 0x84020100
E5    B9        G9        R9
10000 100000000 100000000 100000000

16-15 = 1、256 * (2^1) = 512 です。
よって offset 15 の exponent とみなすよりも、offset 16 で正規化
し、最上位 bit を隠していない浮動少数とみなした方がしっくりくる
かもしれません。


16F も R11G11B10_FLOAT も R9G9B9E5_SHAREDEXP も指数部は全く同じ
5bit となっていました。それぞれ exponent の機能はほぼ同一なので、
この仕様はもしかしたらハードウエア的な都合で決められたものなの
かもしれません。


EM・ONE を WindowsMobile6 アップグレードのため発送しました。
EMOBILE EM・ONE WindowsMobile6 アップグレード申し込み
2007/10/05 の受付開始の朝にすぐ申し込んで、用紙が届いたのが
2007/10/10。2007/10/11 発送でいつ届くか楽しみです。

しばらく EM・ONE が使えなくなったので、久しぶりに W-ZERO3[es] (WS007SH)
を使っています。久しぶりに使うと ZERO3[es] は妙に使いやすく感じます。
カーソルキーが押しやすいのと、長いこと使って慣れていたせい
かもしれません。

最近は Bluetooth キーボード を持ち歩いているので Bluetooth 接続だけは
何とか実現したいところ。

試しに、以前購入した PTM-UBT3S をつないでみました。

変換コネクタが巨大に見えてしまいます。

PTM-UBT3S

↑PTM-UBT3S 本体の角は引っかかりそうだったので やすりで削っています。

つないだところ。

PTM-UBT3SとZERO3[es]

Bluetooth の設定機能を呼び出すために、こちらの星羽さんの
WakeBT Ver1.01 を使わせていただきました。
[自作] Bluetooth機能を呼び起こす WakeBT Ver1.01
インストールして実行するだけなのでとても簡単でした。便利です。

あとは EM・ONE と同じ手順で、設定画面からペアリングすることが可能です。
とりあえず確認したのは下記の 2つです。

・PC との ActiveSync 接続
・Bluetooth キーボード RBK-2000BT2 の接続

便利になりました。
音声系のデバイスは持っていないので、ヘッドセットとかは試してないです。

ちょっと気になったのは、このアダプタをつないでいると ZERO3 が
オートパワーオフしなくなること。ここだけ注意です。
どこかに設定があるのかもしれません。待ち受けだといいのかも。

関連エントリ
小型 Bluetooth アダプタ Princeton PTM-UBT3S
Bluetooth キーボード Rboard for Keitai RBK-2000BT2
em1key Bluetooth keyboard RBK-2000BTII の設定 その3


Direct3D10/DirectX10 ではいくつかの新しいフォーマットが追加されて
います。その 1つに DXGI_FORMAT_R11G11B10_FLOAT があります。
32bit で 3チャンネル持っていて、かつ FLOAT なのでどんな構造を
しているか興味あります。
調べてみました。

見つけた資料はこちらです。
NVIDIA next-gen-dx10-games-develop06.pdf

各 component 毎に 5bit の exponent があると書かれているので、
RGB がそれぞれ 6e5, 6e5, 5e5 であると考えられます。
符号はありません。
実際にバッファを作って描画してみました。

その結果、0x781e03c0 がちょうど ( 1.0, 1.0, 1.0 ) であることが
わかりました。この値を 2進数に変換すると

7    8    1    e    0    3    c    0
0111 1000 0001 1110 0000 0011 1100 0000

さらに 11bit 11bit 10bit に分解すると

B          G           R
0111100000 01111000000 01111000000

となります。B が上位で 10bit、残りが 11bit です。
exponent が各 5bit で、そのうち 4bit が立っています。

指数部は 5bit の 0~31 で、上記のように center の 15 が 0 であると
考えられます。この構造は fp16 と同じです。fp16 については以前
下記のページに書きました。
DDS Texture format memo
仮数部は最上位の 1 が省略されているので全部 0 です。

DXGI_FORMAT_R11G11B10_FLOAT はテクスチャだけでなく RenderTarget
として実際にレンダリングできることも確認できました。
これはかなり使えそうなフォーマットです。


他に指数を持たない似たような型として次のフォーマットもあります。

DXGI_FORMAT_R10G10B10A2_UNORM
DXGI_FORMAT_R10G10B10A2_UINT

この 10bit 型は DirectX9 にもありました。
だけど D3D10/DX10 になって新しいのは、UNORM だけでなく整数型 UINT が
使えることと、NVIDIA のビデオカードでもちゃんと使えることです。
こちらも RenderTarget としてレンダリングできました。


前回書いた整数テクスチャの扱いに関して少々追加です。
レンダリングで更新する時は整数処理を行いますが、デバッグなどで
画面に描画する時は RenderTarget の 0.0~1.0 に変換する必要があると
下記エントリで書きました。
Direct3D 10 ShaderModel 4.0 で整数の世界
これを DirectX10(D3D10) の機能を使って自動化できます。

まずリソースの作成を DXGI_FORMAT_R8G8B8A8_UINT ではなく、
DXGI_FORMAT_R8G8B8A8_TYPELESS で宣言しておきます。
初期データの渡し方などは同じです。

D3D10_TEXTURE2D_DESC  t2ddesc;
t2ddesc.Format= DXGI_FORMAT_R8G8B8A8_TYPELESS;
  ~
ID3D10Texture2D*  riTexture2D= NULL;
iDevice->CreateTexture2D( &t2ddesc, &initdata, &riTexture2D );

その後、同じリソースから ShaderResourceView を 2個作成します。
こちらは型を明確にして UINT と UNORM にします。

// ShaderResourceView の作成
D3D10_SHADER_RESOURCE_VIEW_DESC	srvdesc;
srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT; // 整数アクセス
srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D;
srvdesc.Texture2D.MostDetailedMip= 0;
srvdesc.Texture2D.MipLevels= 1;
iDevice->CreateShaderResourceView( riTexture2D, &srvdesc,
	iResourceViewUINT );

srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UNORM; // 固定少数化
srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D;
iDevice->CreateShaderResourceView( riTexture2D, &srvdesc,
	iResourceViewUNORM );

これでシェーダーからは、iResourceViewUINT 経由でアクセスすると
0~255 の整数値として読み込むことができ、iResourceViewUNORM で
アクセスすると、従来どおり 0~1.0 の少数値で受け取ることが
できるようになります。

例えばシェーダー側では次のように宣言しておきます。

Texture2D<uint4>	InputTextureUI;
Texture2D<float4>	InputTextureF;

エフェクトの変数設定はこんな感じで。

iEffect->GetVariableByName( "InputTextureUI" )->AsShaderResource()->
	SetResource( iTextureBufferUINT );
iEffect->GetVariableByName( "InputTextureF" )->AsShaderResource()->
	SetResource( iTextureBufferUNORM );

受け取るシェーダー側です。

// PixelShader で整数としてアクセスする場合 (0~255)
float4 PS_Update( noperspective float4 Pos : SV_POSITION,
        noperspective float2 UV : TEXCOORD ) : SV_Target
{
    float2  pixsize;
    InputTexture.GetDimensions( pixsize.x, pixsize.y );
    uint2   uvpos= (uint2)( UV.xy * pixsize.xy );
    return  InputTextureUI.Load( uint3(uvpos.xy,0) )* (1.0f/255.0f);
}

// 浮動少数で受け取れるので乗算が不要 (0~1.0)
float4 PS_View( noperspective float4 Pos : SV_POSITION,
        noperspective float2 UV : TEXCOORD ) : SV_Target
{
    float2  pixsize;
    InputTexture.GetDimensions( pixsize.x, pixsize.y );
    uint2   uvpos= (uint2)( UV.xy * pixsize.xy );
    return  InputTextureF.Load( uint3(uvpos.xy,0) );
}

便利です。さすがに良く考えられています。

注意点は、以前のエントリ で書いたように同一のリソースを複数の View
として設定するため、リソースが握られたままになって衝突が
おきやすいことです。
Direct3D 10 HLSL Effect/FX リソース設定のはまり

上記の PixelShader では、UV を 0.0~1.0 に補間した値で受け取って
いるためテクスチャのサイズを乗算する処理が入っています。


Direct3D10/DirectX10 の ShaderModel4.0 で追加された新機能に
整数演算があります。テクスチャフォーマットにも SINT, UINT など
整数形式が追加されていて、入出力も演算も一通り整数だけの処理が
できるようになりました。
実際に試してみました。

今回使用したフォーマットは DXGI_FORMAT_R8G8B8A8_UINT です。
これまで使われてきた DXGI_FORMAT_R8G8B8A8_UNORM と違うのは、
読み書き時に 0~255 を 0.0~1.0 に変換しないことです。
直接 0~255 の数値(しかも整数)として扱うことができます。

厳密な色コードの判定ができるので、特定の色を抜く、置換する
などといったカラーキー処理がしやすくなります。

また 0/1 だけでよい 2値のフォントデータなどは、各bit に畳み込んで
おくことで効率よくデータを保持することができます。DXT1 で 1pixel
あたり 4bit なので、さらに 1/4 までデータが小さくなると考えられます。

ただし整数読み込みだとフィルタはかかりませんし、D3D10/DX10 では
DXGI_FORMAT_R1_UNORM という 1bit 形式のテクスチャも使えるので、
こちらを使ったほうが良いかもしれません。

// ファイル読み込み (August2007 うまくいかない)
D3DX10_IMAGE_LOAD_INFO	info;
memset( &info, 0, sizeof(D3DX10_IMAGE_LOAD_INFO) );
info.MipLevels= 1;
info.Usage= D3D10_USAGE_DEFAULT;
info.BindFlags= D3D10_BIND_SHADER_RESOURCE|D3D10_BIND_RENDER_TARGET;
info.Format= DXGI_FORMAT_R8G8B8A8_UINT;
info.Filter= D3DX10_FILTER_NONE;
info.MipFilter= D3DX10_FILTER_NONE;
D3DX10CreateShaderResourceViewFromFile( iDevice,
		TEXT("rgba8.dds"),
		&info, NULL, &iTextureBuffer[i], NULL );

UINT でデータを読み込む場合は、D3DX10 を使うと上記のようなコードに
なるでしょう。D3DX10_IMAGE_LOAD_INFO を使ってフォーマットを指定
しています。ところがこれ、うまく動きません。(August 2007 SDK)

UINT であっても内部的に 1/255 倍されてしまうらしく、バッファには
0 か 1 の値が書き込まれてしまいます。
自前でファイルを読み込んで Texture2D を作成すると正しく動作したので、
D3DX10 側の問題かもしれません。(違っていたらごめんなさい)

自分で作成する場合は下記のようになります。ファイルロード部分は
省いています。

// Texture2D の作成, USAGE_DEFAULT なので初期データを必ず与える
ID3D10Texture2D*	riTexture2D= NULL;
D3D10_TEXTURE2D_DESC	t2ddesc;
t2ddesc.Width= *width= phead->dwWidth;
t2ddesc.Height= *height= phead->dwHeight;
t2ddesc.MipLevels= 1;
t2ddesc.ArraySize= 1;
t2ddesc.Format= DXGI_FORMAT_R8G8B8A8_UINT;
t2ddesc.SampleDesc.Count= 1;
t2ddesc.SampleDesc.Quality= 0;
t2ddesc.Usage= D3D10_USAGE_DEFAULT;
t2ddesc.BindFlags= D3D10_BIND_SHADER_RESOURCE|D3D10_BIND_RENDER_TARGET;
t2ddesc.CPUAccessFlags= 0;
t2ddesc.MiscFlags= 0;

// SysMemPitch の設定を忘れないように
D3D10_SUBRESOURCE_DATA	initdata;
initdata.pSysMem= phead->DataBody;
initdata.SysMemPitch= *width / sizeof(DWORD);
initdata.SysMemSlicePitch= 0;

iDevice->CreateTexture2D(
		&t2ddesc,
		&initdata,
		&riTexture2D
	);

// ShaderResourceView に変換する
D3D10_SHADER_RESOURCE_VIEW_DESC	srvdesc;
srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT;
srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D;
srvdesc.Texture2D.MostDetailedMip= 0;
srvdesc.Texture2D.MipLevels= 1;
iDevice->CreateShaderResourceView(
	riTexture2D,
	&srvdesc,
	iResourceView
	);

riTexture2D->Release();

レンダリングも試したので、RenderTargetView も作っておきます。

ID3D10Resource*	riResource;
iResourceView->GetResource( &riResource );
D3D10_RENDER_TARGET_VIEW_DESC	rtvdesc;
memset( &rtvdesc, 0, sizeof(D3D10_RENDER_TARGET_VIEW_DESC) );
rtvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT;
rtvdesc.ViewDimension= D3D10_RTV_DIMENSION_TEXTURE2D;
iDevice->CreateRenderTargetView( riResource, &rtvdesc, &iRenderBuffer );

シェーダー側で整数値のままテクスチャから読み込むには Load() を
使います。(マニュアルはちょっとしたミスもあるようです。Return Value
が None になってますがこれは間違いでしょう。)
整数値テクスチャの読み込みでは Sample~() 系は使用できず、
サンプラーを通さないのでフィルタの類もかかりません。

uint4	color_00= InputTexture.Load( uint3(uvpos.xy,0) );

Load() の場合、読み込みアドレスはピクセル座標で指定します。
これは一般的な UV 座標の 0~1.0f ではなく、0~imagesize-1 までの
ピクセル位置になります。そのため補間された UV から変換したり、また
画像全体を読み込む場合はあらかじめイメージサイズがわかっていなければ
なりません。画像サイズを調べるには こちら で紹介した GetDimensions() を使います。

float2	pixsize;
InputTexture.GetDimensions( pixsize.x, pixsize.y );

画像全体が不要な場合、例えば任意の 128x128 pixel だけアクセスする
ような場合は、逆に 0~1.0 の UV 値と違って画像サイズを調べる必要が
ありません。
またスクリーンに対して 1対1 で転送を行う場合、PixelShader の
SV_POSITION で受け取ったスクリーン座標をそのまま渡すことができます。
Load() の引数が uint3 なのは、最後に MipLevel の指定が必要だからです。

読み込むテクスチャがどのフォーマットを返すのか、テクスチャの
宣言にも型の指定が必要です。この宣言は下記のようになります。
(なぜかマニュアルに説明がありませんでした)

Texture2D<uint4>	InputTexture;

整数型でレンダリングする場合は、PixelShader の戻り値も整数で宣言します。

uint4 PS_Main( noperspective float4 Pos : SV_POSITION,
	noperspective float2 UV : TEXCOORD ) : SV_Target
{
 ~

レンダリングできました。きちんと整数のまま読み書きできています。

一般的にフレームバッファは DXGI_FORMAT_R8G8B8A8_UNORM 等の形式を
使うので、画面に描画してテストする場合は変換が必要です。
0~255 を 0.0f~1.0f にマッピングし、float4 で返します。

このへん、上記のシェーダー側の表記方法や機能、設定など、結構マニュ
アルに抜けやミスがあります。HLSL 部分のマニュアルは DirectX9 と
共有されており、D3D9 や ShaderModel 1~3 の解説もマージされています。
そのせいか、肝心の ShaderModel 4.0 の機能がわかりにくくなっています。
唯一の手がかりは実際に試すことです。1つ1つ試してエラーメッセージや
変換されたコードからから機能を類推しなければなりません。
今回、今までのシェーダー出力の調査や解析が役に立ちました。
asm 出力でテクスチャ宣言に型が埋め込んであるのを知っていなければ、
Texture2D<uint4> はもうしばらく気づかず見落としていたかもしれません。


新型 PSP (PSP-2000) を入手してしまいました。
といっても結構前です。発売日のすぐ直後に、店頭で売っているのを
見つけたのでついつい。

新型で一番嬉しかったのは、
軽いことでもなく(確かにこれはすごいのですが)、
TVに出力できることでもなく、
読み込みが早いことでもなくて、
方向キーが、最初からちゃんと斜めに入ることです。

以前持っていた PSP は初期型だったので、過去にこんな感じで
苦労していました。
PSP の十字キーが良くなった
PSPで十字キーが斜めに入らない理由


今年になってからは、ゲームアーカイブス のおかげでかなり PSP の
稼働率が上がっています。
Wikipedia ゲームアーカイブス

ゲームアーカイブスでできるのは、PS1 のソフトをダウンロードして
PSP 上で走らせられる、それだけのことです。

思いがけない過去の名作に出会うという、ソフトのよさももちろん
ありますが、むしろ良くできているのはトータルで見た時の使いやすさ
かもしれません。

早い話、今までのゲーム機と比べて非常に便利なのです。

 ・ディスク入れ替え不要で複数のソフトを入れておける
 ・手に入りにくいソフトでも気軽に買える
 ・比較的安価
 ・データダウンロードなのでパッケージやメディアで場所をとらない
 ・メモリカードでソフト本体もセーブデータも一緒に管理できる
 ・PS1 のソフトを常に持ち歩けて、いつでもサスペンドできる
 ・マニュアルもソフト内で読めるので無くさない
 ・PS3 本体でも動くしセーブデータも相互にコピーできる

などなど。
今までのゲームソフトにあった「不便」がかなり無くなっています。
据え置き CD が iPod になったように、ゲームソフトで本当のデジタル
コンテンツらしさを、ようやく実現できたのではないか、とそう感じます。
(そういえば昔、携帯電話のゲームでも同じことを思いました。)


他にも、コンテンツのダウンロード販売として柔軟な点も魅力です。

 ・一度購入すればいつでも再ダウンロードできる
 ・PS3 本体 HDD にソフトが蓄積されるので、いつでも入れ替えできる
 ・メモリカードの内容を PC で簡単にバックアップできる
 ・各ゲームを PC にコピーして保存、入れ替えもできる
  (異なるPSP本体にはコピーできない)

過去に携帯の乗り換えミスで何度もゲームを失ったことを思えば、
柔軟で融通の利くデータ管理は魅力です。

セーブデータの転送も PS3, PSP 間で相互にできるし、PS3 経由で
アダプタを使えば PS1 のメモリーカードとのやり取りもできるし、
自由度は高いです。
ただ、セーブブロックを 1つ1つコピーするのは結構手間がかかり、
どちらが本当に新しいデータなのか たまに わからなくなります。

iTunes + iPod のように、USB でつないで自動でセーブデータを
同期できたらもっと便利になりそうです。もし簡単に同期できるなら、
本当の意味で「外に持ち出して続きをプレイ」が実現しそうですね。


ゲーム本体も、メモリカードの容量が足りなくなったら入れ替える
ことができます。まるでオーディオプレイヤーのように。

これは非常に便利なので、普通のパッケージソフトや新作もぜひ
ダウンロード版を選べるようして欲しいところです。
ゲームは欲しいけれど、パッケージやメディアが場所をとって
たまっていくのはそろそろ避けたいので。CD を購入しても、一度
iTunes に読み込ませたらほとんど触らなくなるのと同じように。


以下、新型への乗換えを行った時のメモです。

●メモリカードの乗り換え

 先に 同じ PSP 本体を使って、より容量の大きいメモリカードに
 乗り換えてみました。
 PC 上でカードの内容をコピーするだけで、ゲームアーカイブスや
 セーブデータを含めて簡単に乗り換え出来ました。

 PSP 自体に USB ストレージ機能があるので、一旦 PC を経由して
 コピーしても大丈夫です。ついでに PC にバックアップも取れます。

 (1) PSP を USB モードにして PC と接続
 (2) PC 上にカードの中身を全部バックアップコピー (PC ← PSP)

 (3) PSP の接続を解除

 (4) PSP のメモリカードを新しいものに入れ替え
 (5) また USB モードで PC に接続
 (6) バックアップコピーを全部書き戻す (PSP ← PC)


●PSP 本体の乗り換え

 異なる PSP 本体では、同じメモリカードを使うことができませんでした。
 セーブデータなどのデータはそのまま持っていけますが、
 ゲームアーカイブスのゲームは起動しません。

 新しい PSP 本体を使って再認証が必要です。具体的には PS3 から
 コピーしなおすだけでした。

 PS3 で購入したゲームデータは PS3 本体の HDD にも保存されます。
 また一度購入したデータはもう一度ダウンロードすることも出来ます。
 PS3 上で展開したら、PSP をつないで △ボタンから「コピー」を
 選ぶと再認証した後 PSP にゲームを転送します。

 巨大なデータの転送しなおしになるので少々時間はかかりますが、
 再ダウンロードしなくて良い分比較的楽でした。

 機器認証は1アカウントにつき最大5台とのことなので、4回以上乗り換えた
 場合にどうなるかわかりません。
 (もしかして PS3 の分も含まれているのでしょうか?)


なお現在は PS3 が無くても、PC からゲームの購入ができるようになって
います。こちらは試していないので、PC を使った場合の再認証とか
再転送とかその辺についても把握しておりません。
PLAYSTATION Store(PC) ゲームアーカイブス


●前置き

D3D10/DX10 のレンダリングは、入力と出力に同じものを指定することが
できません。

同じバッファ (Texture) を、RenderTarget と ShaderResource(Texture)
に同時に設定できず、もし設定しても入力側が強制的に 0 (NULL) 相当に
なります。少々厳密すぎる気もしますが、自己レンダリングは動作結果を
保障できないので当たり前といえば当たり前です。

非常にありがたいことに Direct3D10 では、間違って設定しても
DebugLayer が親切に教えてくれます。


ただ判定が厳密すぎるために少々問題になることもあります。

例として、テクスチャへのレンダリングを考えてみます。次のように
読み込み用と書き込み用の View を作成してあるものとします。
iTextureBuffer と iRenderBuffer は、同一の Resource Buffer を
参照していると思ってください。

ID3D10ShaderResourceView*  iTextureBuffer[2]; // テクスチャ
ID3D10RenderTargetView*    iRenderBuffer[2];  // レンダーターゲット

// iTextureBuffer[0] と iRenderBuffer[0] はどちらも Resource[0]
// iTextureBuffer[1] と iRenderBuffer[1] はどちらも Resource[1]

下記の "InputTexture" は Effect(fx) 内で Texture2D 宣言された変数で、
入力するテクスチャを意味しています。登録用インターフェースを
iInputTexture にキャッシュしておきます。

ID3D10EffectShaderResourceVariable* iInputTexture=
    iEffectModel->GetVariableByName( "InputTexture" )
        ->AsShaderResource();

描画時のセットアップを次のようにします。

// list1
// 入力テクスチャの設定
iInputTexture->SetResource( iTextureBuffer[0] ); // ←入力 (A)
iEffectModel->GetTechniqueByName( "Main" )
      ->GetPassByIndex(0)->Apply(0);

// 出力の設定
iDevice->OMSetRenderTargets( 1, &iRenderBuffer[1], NULL ); // ←出力 (B)

Effect の変数に登録したパラメータは Apply() で反映されます。
これで Draw() を発行すると、Resource[0] を読み込んで Resource[1]
を更新することができます。

Resource[1] ← Resource[0]


その直後に、今度は入力と出力を入れ替えてレンダリングします。

Resource[0] ← Resource[1]

// 入力テクスチャの設定
iInputTexture->SetResource( iTextureBuffer[1] );
iEffectModel->GetTechniqueByName( "Main" )->GetPassByIndex(0)
      ->Apply(0); // ←できない, (B)でBusy

// 出力の設定
iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL );


このとき、Resource[1] は前のレンダリングで RenderTarget として
登録されています。すでに Busy なので、入力テクスチャとして設定する
ことができません。
(ちなみにこれらの衝突は正確には Draw の実行タイミングで検出されます)


衝突を回避するには、先に RenderTarget のステートをクリアしておくか
別のターゲットを登録して参照をはずす必要があります。

// 入力テクスチャの設定
ID3D10RenderTargetView* iRTVNull= NULL;
iDevice->OMSetRenderTargets( 1, &iRTVNull, NULL ); // 参照外しのクリア

iInputTexture->SetResource( iTextureBuffer[1] );
iEffectModel->GetTechniqueByName( "Main" )
      ->GetPassByIndex(0)->Apply(0); // ←できる

// 出力の設定
iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL );

SetResource と SetRenderTarget の順番を変えても同じで、今度は
入力テクスチャとして参照されているため RenderTarget への登録が
できなくなります。

// 出力の設定
iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL ); // ←できない, (A)でBusy

// 入力テクスチャの設定
iInputTexture->SetResource( iTextureBuffer[1] );
iEffectModel->GetTechniqueByName( "Main" )
      ->GetPassByIndex(0)->Apply(0);

このステート設定順の問題は以前のエントリでも触れました。
Direct3D 10 Streamと同時Resource

また ClearState() を使うとこれらのリソース参照をいっぺんにはずすことができます。
Direct3D 10 ClearState
ただし必要なものまで全部解除されてしまうので効率は悪くなります。


●本題

Effect(fx) を使って描画していると、このリソースの衝突がどうしても
発生してしまうことがあります。C/C++ のプログラムコード上は正しくても
うまくいかず、それだけでは原因がわからないのです。

// list2
iInputTexture->SetResource( iTextureBuffer[0] ); // (C)
iEffectModel->GetTechniqueByName( "Update" )
      ->GetPassByIndex(0)->Apply(0);

 ~描画など

// 入力テクスチャの設定
iInputTexture->SetResource( iTextureBuffer[1] ); // (D)
iEffectModel->GetTechniqueByName( "Main" )
      ->GetPassByIndex(0)->Apply(0);

// 出力の設定
iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL ); // (E)

list2 の (C) で Resource[0] を入力テクスチャとして参照し、一旦描画
しています。その後 (D) でテクスチャを変更して、別の Technique を使って
描画しようとしています。

InputTexture は iTextureBuffer[1] (Resource[1]) で置換されているし
Apply() もしているので、(E) のタイミングでは Resource[0] はフリーに
なったように見えます。

ところがシェーダーによっては (E) の SetRenderTarget() で衝突してしまう
可能性があります。


重要なのは描画に使っている Technique が異なっているということです。

ID3D10Effect 上では同一の変数&インターフェースとしてリソース登録が
できるものの、書き換えられる場所はシェーダーによって違うからです。


(1) シェーダーリソースの登録は、VS, GS, PS それぞれ別管理となっている

"Update" のシェーダーでは、InputTexture を VS と PS の両方で参照
しているかもしれません。Core API での Resource 登録は VS, GS, PS
それぞれ別なので、下記のように専用の登録 API があります。

ID3D10Device::VSSetShaderResources()
ID3D10Device::GSSetShaderResources()
ID3D10Device::PSSetShaderResources()

"Main" のシェーダーで、もし InputTexture の読み込みを PS だけで
行っているとしたら、(D) の Apply() で上書きされるのは
PSSetShaderResources() だけなのです。
そのため VSSetShaderResources() の参照が残ったままとなります。


(2) シェーダーによって、各リソース参照するスロット番号が異なっている。

"Update" の PixelShader が 2枚のテクスチャを参照しているとします。
このとき InputTexture が 0 と 1 のどちらのスロットに割り当てられる
のか予測できません。(Reflection を見るとわかる)

例えば "Update" では InputTexture は Slot 1 に割り当てられており、
"Main" の PixelShader では Slot 0 だとすると、
やはり (D) の Apply() では Slot 1 の参照は上書きされずに残って
しまうのです。


HLSL コンパイラは最適化によって、各シェーダーが本当に必要としている
リソースしかバインドしません。ID3D10Effect のステート管理も最小限の
更新だけで済むように最適化が行われているので、内部の動作をある程度
把握しておかないと、このような はまり に遭遇してしまうことになります。

個別に呼び出すとシェーダー単体は問題なく動作するし、C/C++ のコードも
問題が無いので原因がなかなか見つからないかもしれません。

注意点

 ・同じ Effect/Fx でも Technique や Pass が異なっていると違うシェーダー
  であること

 ・シェーダー(Technique/Pass)ごとに Apply() しないと、厳密には各種リソース
  のステートが上書きされないこと


よくあるのはこんなケースです。

 1. 本来はシェーダーでテクスチャを参照している

 2. デバッグのために一時的に Shader を書き換えて、固定値を出力して
  動きを確認しようとする。
  (例えば PS で強制的に return float4(1,0,0,1); とするなど)

 3. このときテクスチャの参照が外れたので、Effect は Resource 設定の
  上書きを行わずに前のステートが残ってしまう。

 4. 固定値を返すように書き換えただけなのに、突然関係ないシェーダーの
  描画で衝突検出が発生して驚く

Effect(fx) を使って、高レベルな API の理解だけで済ませてしまおう・・
として、思わぬはまりに遭遇することがあります。D3D10 ではある程度
Effect 内部動作や ID3D10Device 関連のステート設定についてもきちんと
把握しておく必要があるかもしれません。


以前こちらのエントリで、Bluetooth keyboard RBK-2000BT2 用の
カスタマイズファイル(em1key用)を公開しました。
em1key Bluetooth keyboard RBK-2000BTII の設定 その2

ところがこの設定を使ってカーソルキーのカスタマイズを有効にする
([↑] 位置を [/?] にする)と、ZERO3 や EM・ONE 内蔵キーボードの
カーソルキーも影響を受けます。[↑]が入れ替わってしまいます。

em1key 側の問題なので、カーソルキー部分も内蔵キーボードと外部
キーボードの分離判定を行うように修正してみました。
WindowsMobile 版のみです。

em1key v1.27

これでカーソルキー部分の入れ替えを行っても、本体内蔵のカーソル
キーは影響を受けなくなりました。設定ファイルの変更はありません。


しばらく使っていて気がついたのですが、RBK-2000BT2 では [Fn] との
併用で次のキー操作も使えるようです。

[F11]       [Fn] + [-_]     ファンクションキー
[F12]       [Fn] + [=+]     ファンクションキー
[PageUp]    [Fn] + [↑]
[PageDown]  [Fn] + [↓]
[Home]      [Fn] + [←]
[End]       [Fn] + [→]


またメーカーサイトでは、リュウド純正のキー変更ツール
「Windows Mobile版RBK-2000BTIIサポートソフト Ver.1.0」
が公開されています。
Rboard for Keitai ソフトウエア・ダウンロード
WindowsMobile 上でキーボードの刻印どおりにキー入力できるように
するためのもので、ASCII(英語)配列キーボードになります。

マニュアルを一通り読んでみました。組み込まれている機能を
考えると、一般的な Keyboard Hook を使っているようにみえます。
実際に起動してみるとやはり em1key と共存できませんでした。
[Fn] キーのカスタマイズなどキーボード特有の特殊なことはして
いないので、おそらく asciipatchwm とほぼ同等の置き換えではない
でしょうか。
[Fn]+[←]/[→] のソフトキーへの置き換えは、もともと [Home]/[End]
が割り当てられていたので、それを置換しているだけだと思われます。

逆にキーボードを選ばず動作する可能性があるので、もしかしたらこれも
一般的な ASCII(英語)配列キーボード用の配列補正ツールとして使えるかも
しれません。


関連エントリ
Bluetooth キーボード Rboard for Keitai RBK-2000BT2
em1key Bluetooth keyboard RBK-2000BTII の日本語カスタマイズ例
em1key Bluetooth keyboard RBK-2000BTII の設定 その2


EM・ONE (S01SH) の WindowsMobile6 アップグレードを申し込んでみました。

イー・モバイル、「EM・ONE α」にIP電話機能
EM・ONE向けのWM6アップグレード受付、10月5日開始
最新OS、Microsoft(R) Windows Mobile(R) 6への「EM・ONE」有償アップグレードサービスを提供開始 Microsoft(R) Windows Mobile(R) 5.0搭載機向け

手順はまず電話での申し込みです。

(1) カスタマーセンターへ電話
  自動応答だけど該当するものが無いのでオペレータ呼び出しへ

(2) 申し込み。一通り説明を受ける

(3) 書類の到着を待つ

(4) 届いた書類に記入して捺印。本体とともに郵送

  ・初期化されるので必要なデータはバックアップをとっておく。
  ・製品が入っていた黒い箱に入れて送る。
  ・郵送するものは本体のみ。下記のものは送らない。はずしておく。
    ・スタイラス
    ・バッテリー
    ・バッテリーのふた
    ・SIMカード(EM chip)
    ・miniSDカード
    ・ストラップ

(5) アップグレードが完了して届くのを待つ
  2週間ほどかかる予定


現在(3)の段階です。
料金は 9980円で送料込み。

以前 iPAQ h3630 を PocketPC 2002 にアップグレードしたときも
CD-ROM の送付だけで 7000円以上しました。
GENIO e550 はメーカーに送って RAM 増設込みを選んだので
2万円近くかかってます。当時の記録を見てみると、GENIO は
意外にも早くて 1週間くらいで返ってきたようです。


描画するプリミティブの形状は通常 IASetPrimitiveTopology() で
指定します。D3D10/DX10 では次の 9種類が定義されています。

D3D10_PRIMITIVE_TOPOLOGY_POINTLIST  // 点
D3D10_PRIMITIVE_TOPOLOGY_LINELIST   // 線
D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP  // 連続線
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST  // 三角形
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP // 連続三角形
D3D10_PRIMITIVE_TOPOLOGY_LINELIST_ADJ
D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ
D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ

ところがジオメトリシェーダーを使っている場合、
IASetPrimitiveTopology() の設定値は実際に描画する
プリミティブ形状と連動していません。

というのは、GeometryShader の出力プリミティブの種類はシェーダー
側で再定義されるからです。

PointSprite がその良い例で、入力 Topology は PointList でも
実際に描画される形状は TriangleStrip の板ポリゴンになっています。

つまりジオメトリシェーダーを使う場合の IASetPrimitiveTopology()
の設定値は、

・頂点ストリームを一度に読み進める量
・GeometryShader へ同時に入力されるパラメータ数

の決定にだけ使われると考えられます。

そこでジオメトリシェーダーへの入力頂点数にだけ注目してまとめると
次のようになります。

同時 1 頂点 POINTLIST
同時 2 頂点 LINELIST
同時 3 頂点 TRIANGLELIST
同時 4 頂点 LINELIST_ADJ
同時 6 頂点 TRIANGLELIST_ADJ

同時 5頂点が無いのが残念ですが、ほぼ 1~6 頂点までの汎用的な入力
頂点数として流用することが可能です。この考え方を使って
4頂点プリミティブや 6頂点プリミティブを定義することができます。

実際に 4角ポリゴン でモデルを作って描画してみました。
いわゆる QUADLIST 相当です。(D3DPT_QUADLIST ?)

ss07

いつものように、実行ファイル、ソース、シェーダー込みでダウンロード
できます。

モデルデータ側は四角形のあつまりなので、index list は 4つで
1ポリゴンとなります。

// torus.inc
static unsigned short _Index[]= {
    0, 1, 2, 3,	// 四角ポリゴン1
    4, 0, 3, 5,	// 四角ポリゴン2
    6, 4, 5, 7,	// 四角ポリゴン3
    8, 6, 7, 9,	// 四角ポリゴン4
      :

描画命令の発行は 同時4頂点入力の LINELIST_ADJ を使います。

// 描画 (ss07.cpp)
iDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_LINELIST_ADJ );
iDevice->IASetInputLayout( iInputLayout );
UINT	vsize= sizeof(VertexType);
UINT	voffset= 0;
iDevice->IASetVertexBuffers( 0, 1, &iVertexBuffer, &vsize, &voffset );
iDevice->IASetIndexBuffer( iIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );

iEffectModel->GetTechniqueByName( "Main" )->GetPassByIndex( 0 )->Apply( 0 );
iDevice->DrawIndexedInstanced( IndexCount, 16, 0, 0, 0 );

無駄に凝ったことをしているので DrawIndexedInstance() になってますが、
LINELIST_ADJ 以外はごく普通の描画コードです。
DrawIndexed~() に渡す値もそのまま頂点数 (Index) の個数になっています。
もし1枚だけ描くなら 4頂点です。

// 1枚だけ描く場合
iDevice->DrawIndexed( 4, 0, 0 );

VertexShader も PixelShader も通常の描画と全く同じものです。
ジオメトリシェーダーは次のとおりです。

typedef VS_OUTPUT   GS_OUTPUT;

[maxvertexcount(4)]
void GS_Main( lineadj GS_OUTPUT In[4],
		inout TriangleStream gsstream )
{
    gsstream.Append( In[0] );
    gsstream.Append( In[1] );
    gsstream.Append( In[3] );
    gsstream.Append( In[2] );
    gsstream.RestartStrip();
}

lineadj で 4頂点入力、TriangleStrip で四角形を描画します。
これで QUADLIST の描画が可能となります。

ファイルはこちらです。
wheelhandle_ss07t.zip


D3D10/DX10 の ShaderModel4.0 では整数演算を行うことができます。
この整数演算系の命令出力を見ていると、従来の浮動少数演算と
全く同じレジスタを使っていることがわかります。
Buffer やレジスタ、リソースアクセスは TYPELESS 宣言しなければ
型付けされますが、Shader の命令的には float だろうが int だろうが
特に区別が無いようです。

これは SSE 等の CPU 命令でも同じで、32bit ×4 の 128bit の値と
いうだけで特に動的な型情報はなさそうです。データの中身を利用する
側が便宜上区別しているだけに過ぎません。

例えば条件判定の結果によって 0.0f か 1.0f を代入したい場合、
HLSL コンパイラはこんなコードを出力します。

// HLSL
v.z= v.x < v.y ? 1.0f : 0.0f;

// asm
lt r0.x, v0.x, v0.y
and o0.z, r0.x, l(0x3f800000)

ここでは直後が ret なので o0 に代入されています。
上の v は float4 で宣言しています。

0x3f800000 は 32bit 浮動少数の 1.0f を bit 表現したものです。
r0.x が 0 なら o0.z もそのまま 0.0f になり、r0.x の bit が
すべて 1 なら 1.0f が代入されます。

つまり実数の 0.0f か 1.0f か選択するために整数演算の bit mask を
活用して最適化をはかっています。演算上整数データと浮動少数データの
区別がないことがわかります。

またこのことから lt 命令は結果を整数値で返し、さらに成立すれば
bit が全部 1 (-1)、不成立なら 0 を返すのだと予想できます。
(昔の basic 言語のように)

ちなみに、代入先の 0 と 1.0 を交換するとこんなコードになります。

// HLSL
v.z= v.x < v.y ? 0.0f : 1.0f;

// asm
lt r0.x, v0.x, v0.y
movc o0.z, r0.x, l(0), l(1.000000)

movc は条件付転送命令のようです。
条件成立時の値を 1.0f 以外にしてみます。

// HLSL
v.z= v.x < v.y ? 4095.0f : 0.0f;

// asm
lt r0.x, v0.x, v0.y
and o0.z, r0.x, l(0x457ff000)

条件式の結果からマスクで値を生成しているだけなので、どんな値でも
ペナルティが無いようです。
条件結果を整数で返す場合は次のようになりました。

// HLSL
uint c= v.x < v.y;

// asm
lt r0.x, v0.x, v0.y
and o0.x, r0.x, l(1)

HLSL/C言語の仕様的には条件式は -1 or 0 ではなく 1 or 0 なので、
こちらでも and 演算が入ってしまいます。

このような型を無視したデータの取り扱いを HLSL 上で行うためには
asint(), asuint(), asfloat() 命令を使います。
型変換ですがデータは変更しないので型キャストとは異なります。

キャストでは実際にデータを変換する命令に置き換わりますが、
これらの命令は実行時には何もしません。コンパイラに対して型が
変わったことを通知しているだけです。

例えば asfloat() の動作を C/C++ で書くとこんな感じです。

float asfloat( int b )
{
    return *(float*)(&b);
}

// もしくは
float asfloat( int b )
{
    union {
        float _f;
	int _i;
    } temp;
    temp._i= b;
    return temp._f;
}

asfloat を使うと 1.0f を直接代入する代わりに次のように記述できます。

float b= asfloat( 0x3f800000 );

この as~系命令はかなり使えそうです。
例えば RGBA32F のテクスチャやレンダーターゲットで、R, G だけ
float とみなし、B, A を uint とみなして処理することもできます。
データのパックや圧縮など、開いているチャンネルの有効利用にも
応用できそうです。
以前作った迷路のシェーダーも、これを使っていればもっと判定が
簡略化できました。
Direct3D ShaderModel4.0 Shaderで迷路作成
Direct3D 10 ShaderModel4.0 迷路の自動探索Shader

ただ頂点データに使うなど、バーテックスシェーダーや
ジオメトリシェーダー から ピクセルシェーダー へ値がわたるときは
難しいでしょう。異なる型で補間が発生してしまうので、型を混用した
ようなデータは向いてないかもしれません。

GPU によっては浮動少数演算に特化されていて、整数専用の演算は
まだ特殊な ALU を必要としている可能性があります。演算性能を
最大限引き出すには float の方が有利なので、整数演算の多用は
パフォーマンスとの相談になってくるかもしれません。この点は
要注意です。


HLSL では関数を再帰呼び出しすることができません。
>error X3500: '_Sub0': recursive functions not yet implemented

一応 asm 命令ではシェーダープログラムのサブルーチンコールは
存在していて、call ~ ret や label 等のニモニックもあります。
今まで調べた限りでは、現在の HLSL (Shader4.0) でこれらの命令が
使われるのは、唯一 switch 文の attribute に [call] を指定した
場合だけでした。

データスタックが無いので、ローカル変数の保護など、その辺の
実装でハードルが高いのかもしれません。

とはいっても、テスト中はジオメトリシェーダー等でほんの数段で
よいから再帰的にコードを記述したくなることがあります。
シェーダー関数は基本的にすべて inline 展開されるので、指定した
数だけ勝手に inline 展開してくれれば実現可能でしょう。
将来のコンパイラで実装してほしい機能です。
([recursive(4)] _Sub0( .. ) とか、こんな感じで)

というわけで手動で再帰を展開してみます。
まず再帰関数をマクロ定義します。

#define	_DEFFUNC(V0,V1)			\
float4 _Sub##V0( uint a, float4 col )	\
{					\
    if( --a > 0 ){			\
        return _Sub##V1( a, col.yzwx );	\
    }					\
    return col;				\
}

必要な段数だけ定義します。

_DEFFUNC(4,4)
_DEFFUNC(3,4)
_DEFFUNC(2,3)
_DEFFUNC(1,2)
_DEFFUNC(0,1)

呼び出しの例

float4 PS_Main( PS_INPUT In ) : SV_Target
{
    return  _Sub0( 2, In.Color );
}

再起呼び出しの代わりに、全く同じ定義内容の別名関数を呼び出して
いるだけです。この場合 _Sub0() から呼ばれる関数は _Sub1() で
以後終了条件まで増えていきます。そのため必要な再帰の数だけ別名で
定義しておく必要が生じます。

上の例では _DEFFUNC の最後は自分自身の呼び出しをしていますが、
定数による inline 展開では、その関数が実際に呼ばれない限り HLSL
コンパイラではエラーにならないようです。
もし

    return  _Sub0( In.Level, In.Color );

のような感じで、動的なパラメータを使った呼び出しにすると最後まで
展開する必要があるためエラーになります。
このときは終端だけ専用の関数を用意します。

float4 _Sub4( uint a, float4 col )
{
    return col;
}

_DEFFUNC(3,4)
_DEFFUNC(2,3)
_DEFFUNC(1,2)
_DEFFUNC(0,1)


実際は上の例の _DEFFUNC と違って、もっと複雑な条件を持った、
もっと長い再帰関数を記述することになります。その場合いちいち行末に
「\」をつけたマクロの連続行形式で書かなければならないのが少々難点です。


GeometryShader は、Shader3.0 以前はできなかったさまざまな用途に
応用することができます。

・面(primitive)単位の処理、エッジの処理
・隣接頂点の参照
・VertexShader の代わり
・頂点(primitive)の追加
・primitive の削除
・その他いろいろ

頂点シェーダーは 1頂点単位の変換なので、そのままでは他の頂点情報の
参照ができません。また動的な追加削除は矛盾を引き起こしてしまいます。
あらかじめ面単位に分割しておいたり、隣接頂点座標を1頂点に入れて
おいたりと、さまざまな工夫と前処理が必要でした。

D3D10/DX10 で追加されたジオメトリシェーダーは、このような面(プリミ
ティブ)ごとの処理を、データ側の加工無しに行うことができます。
(topology の adjacency data は別に情報が必要)

試しにポリゴンをばらばらにするシェーダーを作ってみました。

ss06

以前 Xbox1 のゲームでも同じようなシェーダーを作成し、シェーダー
だけで任意のモデルをばらばらに破壊する表現を用いたことがあります。
当時は ShaderModel1.0 だったので、あらかじめ情報を頂点に埋め込んで
おく必要がありました。専用コンバータを用意して、共有頂点を分割したり
回転中心からの距離を求めておいたりと、専用のモデルデータに
なっています。

今回はジオメトリシェーダーのおかげで、データ自体は何もいじる必要が
ありませんでした。普通に描画するデータをそのままシェーダーに渡す
だけで簡単(?)に破壊することができます。

なお Local/World 座標での演算が必要なので、Transform 自体も
ジオメトリシェーダーで行う必要があります。
そのため VertexShader は何もせず、頂点を GeometryShader に渡して
いるだけとなっています。

データは同じですが演算量は増加します。面ごとに 3頂点分の演算が発生
するので、普通に描画するよりはかなり重くなっているはずです。

破壊については、本当ならば StreamOutput を活用すべきところですが
使っていません。単純に面法線と重力方向にローカル回転させながら
吹っ飛ばしているだけです。

wheelhandle_ss06t.zip

いつものように ss06.exe で実際に実行することができます。
DirectX10 が走る環境と DirectX SDK August2007 Runtime が必要です。
今までと違って速度調整が入っていて、約 60fps 前後で固定するように
なっています。(追記: そのままだと速すぎるからです)


テクスチャから読み込んだピクセルの値が 0.1 付近であることを
判定しようとして、HLSL で当初こんなコードを書いていました。
テクスチャのピクセルサイズが不明なので、± 0.05 くらいの
誤差を許容しています。

int TestA( float4 color )
{
    return  color.x > 0.05f && color.x < 0.15f;
}

あまりにそのまんまなので、もう少しちゃんと書こうとして次の
ように修正してみました。

int TestB( float4 color )
{
    return  abs( color.x - 0.1f ) < 0.05f;
}

どちらが複雑度が高いか比較するためにアセンブラで確認してみます。
結果は上の TestA() 3命令で、下の TestB() が 2命令に展開されています。

// TestA
ge r1.x, r0.x, l(0.050000)
ge r1.y, l(0.150000), r0.x
and r1.x, r1.x, r1.y

// TestB
add r1.x, r0.x, l(-0.100000)
lt r1.x, |r1.x|, l(0.050000)

見てわかるとおり abs 命令がありません。lt 命令のソースオペランド
に直接絶対値指定らしき修飾子 |~| が記述されています。

Shader3.0 までは、abs は独立した命令 'abs' だったので便利に
なっています。良く考えたらもともと符号反転は出来たので、符号を
落とすだけの abs も簡単なのかもしれません。

ただし共通バイトコードでは単なる修飾子でも、ドライバレベル以降の
ネイティブコードでは独立した命令として実行される可能性があります。
NVIDIA の OpenGL 拡張命令から D3D10 Shader4.0 相当の ASM Shader
を調べてみるとしっかり ABS 命令が存在していました。


実際に比べてみます。もともと動作しているシェーダーに abs を挿入
してコンパイルし、消費する命令 slot 数が変化しないことを確認します。
比較しやすいように 300回ほどループさせて比べます。

// abs 無し
    loop 
      ige r2.x, r1.w, l(300)
      breakc_nz r2.x
      mad r1.xyz, v0.xyzx, v1.xxxx, r1.xyzx
      iadd r1.w, r1.w, l(1)
    endloop 

// abs あり
    loop 
      ige r2.x, r1.w, l(300)
      breakc_nz r2.x
      mad r1.xyz, |v0.xyzx|, |v1.xxxx|, r1.xyzx
      iadd r1.w, r1.w, l(1)
    endloop 

GeForce8800GTS で走らせたところ速度差が出ました。

10000~10200 (usec)   abs無し
14500~15400 (usec)   absあり

GeForce8800(G80) では実際には abs は個別の命令となっていて、
それぞれの絶対値演算で別の実行サイクルを消費しているように見えます。
つまり

 mad r1.xyz, |v0.xyzx|, |v1.xxxx|, r1.xyzx

は本当は 3命令相当で、さらに内部で追加の temp レジスタを消費
している可能性もあります。

このことから、Direct3D 上のバイトコードで命令 slot 数やレジスタ
数をみても、実行速度や最適化の目安に過ぎないということがわかります。
また HLSL コンパイラの段階では abs をコストフリーと考えて
最適化している可能性もあります。

将来ぎりぎりの最適化を行うようになったら、この辺は要注意ですね。


さて、最初に戻って TestA と TestB の比較ですが、ge ×2 + and の
TestA よりも、abs を使った TestB の方がずっと高速でした。
数値そのものは他の処理も含んでいるのであまり意味を持たないのですが、
差が生じていることはわかります。

26000 (usec)   TestA
15000 (usec)   TestB

もしかしたら単なる命令差よりも、文脈上 B の方が消費レジスタが1つ
少ないことが原因かもしれません。

例えば

E= A*B*C*D

という演算を行う場合、一般的な CPU や Shader1.0 世代の GPU では

(1) R1= A*B
(2) R1= R1*C
(3) R1= R1*D

よりも

(4) R1= A*B
(5) R2= C*D
(6) R1= R1*R2

の方が高速です。これは最適化のテクニックとしても良く用いられます。
その理由は (1)~(3) の演算にはすべて依存関係があり、前の演算結果が
が出るまでパイプラインがストールするからです。

(4) と (5) は依存関係が全く無いので、完全に並列演算が可能となります。
アウトオブオーダーなら C,D の準備が出来次第、(4) よりも (5) を
先に実行するかもしれません。

ところが今の GPU は逆であり、パイプラインストールは完全に他の
スレッドで埋めてしまいます。よって (4)~(6) は余計な R2 を消費する
分だけ逆にスレッド並列化を阻害し、(1)~(3) よりも低速になってしまう
わけです。

TestA と TestB の関係も同じで、TestA の方が最初の2命令に依存関係が
無いので、今までの感覚で見ているとついこちら方が良いコードに見えて
しまいます。