em1key Bluetooth keyboard RBK-2000BTII の設定 その3

以前こちらのエントリで、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

EMOBILE EM・ONE WindowsMobile6 アップグレード申し込み

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週間くらいで返ってきたようです。

Direct3D 10 Shader4.0 ジオメトリシェーダーで QUADLIST の描画

描画するプリミティブの形状は通常 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

Direct3D 10 HLSL の asint/asuint/asfloat 命令

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 の方が有利なので、整数演算の多用は
パフォーマンスとの相談になってくるかもしれません。この点は
要注意です。

Direct3D 10 HLSL で再帰呼び出しの展開

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