WindowsMobile em1key 1.26 書き忘れた変更点

Ascii 遠藤さんの 「遠藤諭の東京カレー日記」
再び em1key 関連のお話が載りました。

Advanced W-ZERO3 [es] 親指(続編)
キーボードの限界小ささ(1)

今度はそれぞれキーボードに合わせてカスタマイズした、
実際の設定ファイルが公開されています。
em1key のカスタマイズ情報はそれほど多くないので、
実際に使っている方の実例として参考になります。

em1key v1.26 で Advanced W-ZERO3[es] に(一応)対応しましたが
内蔵キーボードとの切り分け共存が 100% できているわけではなく、
Fn を押した記号と親指シフトの定義が干渉することがあるようです。

とりあえず oyayubiwm の方に機能の有効・無効をいつでも
切り替えられる機能がついているので、こちらでの対処を
お願いします。具体的な操作を質問いただいたので、
下記ページにも追記しておきました。
oyayubiwm

em1key v1.26 では他にも変更点がありました。
ドキュメントへの追記と script マニュアルの更新をすっかり
忘れていまして、あわてて更新をしておきました。

script 命令の ifpc, ifce, ifsw, ifnsw ~ endif が
ネスト対応になっています。(いまのところ 32段まで)

ifpc
ifce
ifsw 
ifnsw 
endif
define  

これらの命令は C 言語でのプリプロセッサに相当します。
script ファイルを em1key が読み込んだ時点で処理されるので、
if ~ 等で除外されたブロックはバイトコード化されず、
メモリにも常駐せず、負担にならないようになっています。
動的な分岐は IF_~ 系の命令です。

em1key スクリプトマニュアル v1.26.0
em1key カスタマイズ情報

Direct3D 10 Shader4.0 ID3D10Include の活用

引き続き HLSL/Effect のコンパイル周りの話です。
core API 側の Shader Compiler で #include に対応するには
ID3D10Include の準備が必要です。
これはもともと Direct3D9 の D3DX にあったもので、
機能も使い方もいっしょのようです。

ただ、D3DX10 の Shader Compiler API を使う分には内部で
勝手に include 処理をやってくれますし、下記エントリで書いた
ように D3DX 側 API を使った方が良いので、普通に使う分には
あまり気にする必要は無いかもしれません。
>・Direct3D 10 Shader4.0 APIによってコンパイラが違う

一応試してみました。

class fxInclude : public ID3D10Include {
public:
    fxInclude()
    {
    }
    ~fxInclude()
    {
    }
    virtual HRESULT __stdcall  Open(
                D3D10_INCLUDE_TYPE inctype,
                LPCSTR filename,
                LPCVOID parentdata,
                LPCVOID* ppdata,
                UINT* pbyte )
    {
        UINT   bsize= __FileSize( filename );
        void*  ptr= __Alloc( bsize );
        if( ptr ){
            if( __Load( ptr, filename, bsize ) ){
                *ppdata= ptr;
                *pbyte= bsize;
                return	S_OK;
            }
            __Free( ptr );
        }
        return	E_FAIL;
    }
    virtual HRESULT __stdcall  Close( LPCVOID ppdata )
    {
        __Free( const_cast( ppdata ) );
        return  S_OK;
    }
};

使っているところ。

ID3D10Blob* blobEffect= NULL;
ID3D10Blob* blobError= NULL;
fxInclude  incFunc;
D3D10CompileEffectFromMemory(
    memory,
    size,
    fxname,
    cpp_defines,// def
    &incFunc,	// inc
    0,		// HLSLflag
    0,		// FXflag
    &blobEffect,
    &blobError
);

#include の度にファイル名を持って callback されるので、
必要なメモリ領域を作ってあげるだけです。
不要になったら Close() を呼び出してくれます。

ID3D10Include を自前で用意する目的として考えられるのは、
include path 検索への対応でしょうか。

もしくは File にこだわらず、特定の include キーワードに
反応して別のコマンドを挿入したり pragma のように動作を
変えることも出来そうです。
自分で Effect のテキストを読み進める処理が不要なので、
意外に使えるかもしれません。

というわけで

 #include “output_disassemble”
 #include “output_preprocess”

と記述したシェーダーは、デバッグテスト用に disassemble や
preprocess 結果をファイルに書き出すようにしてみました。

テスト&デバッグ中は毎回出力して欲しいけど、完成した
シェーダーの場合は余計なファイルを作られても困ります。

今までは debug build の場合無条件に両方書き出していたので、
不要なファイルが多数生成されていました。これで、完成した
シェーダーは余計なことをしなくて済むようになります。
あまりきれいな方法じゃないけどちょっと便利になりました。

Direct3D 10 Shader4.0 シェーダーのコンパイル

再び HLSL (Effect) のコンパイル関連です。
Direct3D 10 Shader4.0 APIによってコンパイラが違う
で少々調べたので、もう少し説明してみます。
マニュアルに詳しく書かれていないこと、わかりにくいこと、
実際に試したことなどなどをメモしていきます。

API まとめ

・Compile 系 API  HLSL/fx → Blob のバイトコード

ID3D10Effect/D3D10CompileEffectFromMemory()
ID3D10Shader/D3D10CompileShader()
D3DX10/D3DX10CompileFromFile()
D3DX10/D3DX10CompileFromMemory()
D3DX10/D3DX10CompileFromResource()
   
・Create 系 API  バイトコード → インターフェース

ID3D10Device::CreateVertexShader()
ID3D10Device::CreatePixelShader()
ID3D10Device::CreateGeometryShader()
ID3D10Device::CreateGeometryShaderWithStreamOutput()
ID3D10Effect/D3D10CreateEffectFromMemory()
ID3D10Effect/D3D10CreateEffectPoolFromMemory()
D3DX10/D3DX10CreateEffectFromFile()
D3DX10/D3DX10CreateEffectFromMemory()
D3DX10/D3DX10CreateEffectFromResource()
D3DX10/D3DX10CreateEffectPoolFromFile()
D3DX10/D3DX10CreateEffectPoolFromMemory()
D3DX10/D3DX10CreateEffectPoolFromResource()
D3DX10/D3DX10CreateAsync~

・Disassemble 系 API  インターフェース → Blob の disassemble リスト

ID3D10Shader/D3D10DisassembleShader()
ID3D10Effect/D3D10DisassembleEffect()
D3DX10/D3DX10DisassembleShader()
D3DX10/D3DX10DisassembleEffect()

・Preprocessor 系 API  HLSL/fx → Blob のテキスト

ID3D10Shader/D3D10PreprocessShader()
D3DX10/D3DX10PreprocessShaderFromFile()
D3DX10/D3DX10PreprocessShaderFromMemory()
D3DX10/D3DX10PreprocessShaderFromResource()

必要な機能がほとんどそろっています。fxc.exe はコマンド
ライン引数のパースだけで、あとは D3DX10 を呼び出している
だけのようです。

D3DX10 の D3DX10Create~ は HLSL からのコンパイルだけでなく、
コンパイル済みデータ(fxo)を受け取ることも出来ます。

Preprocessor は Include や Macro 等のテキスト処理を行い、
結果を返してくれる便利な関数です。まず、シェーダー
コンパイル時にマクロの適用がきちんと行われているか確認
することができます。

またシェーダーにこだわらず、script 系言語の汎用のテキスト
処理フィルタとして流用できるかもしれません。

ただ一旦パーサにかけたテキストを再構築して出力している
らしく、無駄な空白や空行が除去され各キーワードがスペース
区切りに置き換わっています。
わかりやすいし処理しやすいものの、#line 等のディレクティブも
失われてしまいます。
本当のプリプロセッサとして活用する場合は、エラー時の行番号
を求めることが出来ず、デバッグしづらくなるのが難点でしょう。

Disassemble/Preprocessor など Blob のテキストで返す API は
‘\0’ 終端でかつ、ID3D10Blob::GetBufferSize() が ‘\0’ を
含めたサイズを返す点に注意です。

例えばマニュアルの D3D10DisassembleEffect の説明には下記の
プログラム例が載っています。(下記はマニュアルからの引用)

LPCSTR commentString = NULL;
ID3D10Blob* pIDisassembly = NULL;
char* pDisassembly = NULL;
if( pVSBuf )
{
    D3D10DisassembleEffect( (UINT*)
    	l_pBlob_Effect->GetBufferPointer(),
        l_pBlob_Effect->GetBufferSize(), TRUE, commentString,
	&pIDisassembly );
    if( pIDisassembly )
    {
        FILE* pFile = fopen( "effect.htm", "w" );
        if( pFile)
        {
            fputs( (char*)pIDisassembly->GetBufferPointer(), pFile );
            fclose( pFile );
        }
    }
}

もしこれを次のように書くと、テキストファイルの終端に ‘\0’ が
含まれてしまいます。
(__Write は指定バイト数ファイルに書き込む仮想 API とする)

__Write( pIDisassembly->GetBufferPointer(),
		pIDisassembly->GetBufferSize() );

D3DX10CompileFromFile() は開始関数名の指定があるので、一見
Shader コンパイル専用の関数に見えます。実際は Effect(fx) の
コンパイルも可能です。この場合関数名 pFunctionName には NULL
を渡します。

Compile 系 API に渡す D3D10_SHADER_MACRO は、#define ~ に
相当する定義リストを渡すためのものです。この定義リストは
定義内容を文字列で渡す必要があり、かつ NULL 終端です。
マニュアルの D3D10_SHADER_MACRO の記述例では size [1] の配列を
使っていてこの NULL 終端が含まれていないので注意です。

以下マニュアルより引用

D3D10_SHADER_MACRO Shader_Macros[1] = { "zero", "0"  };

実際に使う場合はこんな感じです。

D3D10_SHADER_MACRO Shader_Macros[]= {
	{  "zero", "0"  },
	{  "TEXLAYER", "2"  },
	{  "POINTLIGHT", "8"  },
	{  NULL, NULL  },
};

HYPERでんち の方もいくつか更新しました。
シェーダーの世代ごとの違い
DirectX SDK バージョン一覧

Direct3D 10 Shader4.0 APIによってコンパイラが違う

同じシェーダーで、かつ同じコンパイルオプションを指定しているのに、
生成されたシェーダーのバイナリが完全に同一になりません。
こんな症状にしばらく悩んでいました。

判明した結論は下記のとおり。

「Direct3D10 は使用する API によって HLSL コンパイラが異なっている」

この違いは April2007 から生じており、当時のリリースノートを
良く読むとそれらしいことが書いてありました。
(マニュアルの「What’s New in the April 2007 DirectX SDK」)

以下、HLSL Compile 系 API の違いなどを説明しながら調べた結果を
書いてみます。

DirectX10 では Effect(fx) や HLSL 関連の API も core に含まれる
ようになりました。そのため必ずしも D3DX を使う必要は無く、
エフェクトのコンパイルから生成まで core API だけで実現する
ことができます。

それでも D3DX には Shader や Effect のコンパイル関連の API が
数多く用意されています。core API とほとんど同じ名前で似たものが多く、
あまり違いが無いように見えます。
では D3DX の Compile 系関数を使う利点はどこにあるのでしょうか。

  (1) Compile → Create の手順が一度に出来る。
  (2) ファイルやリソースから直接読み込める。
  (3) 非同期実行できる。(ID3D10ThreadPump)
  (4) #include に対応している。

core 側の API は Compile と Create の API が分かれています。
例えば ID3D10Effect の場合

 1. D3D10CompileEffectFromMemory( .. )
 2. D3D10CreateEffectFromMemory( .. )

と2ステップ必要になります。API が分かれることで、
コンパイル済みバイナリをファイル保存できるし、
コンパイルされたバイナリを読み込めば Compile 無しに Create
だけ通すことも出来ます。

Compile 後のバイナリをそのままファイルに書き出せば、
fxc.exe で作った fxo ファイルと全く同じものができました。

ちなみに API は FromMemory しかなく、FromFile も FromResource
もありません。以前は D3DX に含まれていたことから、わかり
やすいように関数名だけ継承したものと思われます。

D3DX の場合は洗練された汎用性よりも便利さ優先で、下記の命令で
いきなり ID3D10Effect のインスタンスを生成することが出来ます。

 D3DX10CreateEffectFromFile( … )
 D3DX10CreateEffectFromMemory( … )
 D3DX10CreateEffectFromResource( … )

メモリだけでなく直接ファイルやリソースから作ることも出来ます。

ちなみにマニュアルにはミスがあって、最後の引数

  HRESULT *pHResult

が正しく載っていないものがあります。これは ID3DX10ThreadPump で
非同期実行したときに、実行結果を受け取るためのバッファです。
非同期実行しない場合は関数の戻り値で結果がわかるので、この
パラメータは NULL で構いません。

また core API は内部で勝手にファイルアクセスされると困るので、
HLSL 内の #include ディレクティブに対応していません。
#include を意図したとおりに機能させるには、自前でインプリメント
した ID3D10Include が必要です。
#include 以外のプリプロセッサコマンドはそのまま通ります。

D3DX の関数では何もしなくても、内部で勝手に ID3D10Include を
用意してくれます。
もちろん自分で定義した ID3D10Include を渡すことも出来ます。

マニュアルを見ると FromFile の場合のみ自動で #include を処理
してくれるようなことが書いてあります。FromMemory/Resource
ではユーザー定義の ID3D10Include に頼らなければいけないように
見えます。ところが実際に試してみると、

 D3DX10CreateEffectFromMemory()
 D3DX10CompileFromMemory()
 D3DX10PreprocessShaderFromMemory()

どれも ID3D10Include 無しに #include 可能でした。実際、下記の
コードの代わりに

D3DX10CreateEffectFromFile(
	"sysdef.fx",
	NULL,	// macro
	NULL,	// include
	"fx_4_0",
	0,	// HLSL flags
	0,	// FX flags
	g_iDevice,
	NULL,	// EffectPool
	NULL,	// ThreadPump
	&iEffect,
	&blobError,
	NULL	// HResult
	)

FromMemory を使った次の書き方をしても動きました。

const char*	memory= "#include \"sysdef.fx\"\n";
D3DX10CreateEffectFromMemory(
	memory,
	strlen( memory ),
	fxFileName,
	NULL,	// macro
	NULL,	// include
	"fx_4_0",
	0,	// HLSL flags
	0,	// FX flags
	g_iDevice,
	NULL,	// EffectPool
	NULL,	// ThreadPump
	&iEffect,
	&blobError,
	NULL	// HResult
	)

FromResource() もそのままで #include 処理出来るかもしれません。

このように、一見重複に見えるけど D3DX 側には便利な関数が
それなりの理由をもって用意されているわけです。
と、最初はこのくらいに考えていました。

ところが実際は、大きく分けて下記の2種類の HLSL コンパイラが
共存していることになります。

・core API が呼び出す HLSL コンパイラ
・D3DX が呼び出す HLSL コンパイラ

ID3D10Shader の D3D10_SHADER_DESC Creator を見るとこの両者の
違いがわかります。

・D3D10CompileEffectFromMemory() の場合
     Microsoft (R) HLSL Shader Compiler

・D3DX10CompileFromMemoryD3DX()  (August2007) の場合
     Microsoft (R) HLSL Shader Compiler 9.19.949.1104

D3DX が呼び出すコンパイラは C:\Windows\System32 以下にある
dll です。それぞれ内部のバージョン番号も記してみました。

D3DCompiler_33.dll    9.18.949.0015 (April2007)
D3DCompiler_34.dll    9.19.949.0046 (June2007)
D3DCompiler_35.dll    9.19.949.1104 (August2007)

D3DX の Compile 系 API を呼び出したときだけこれらの dll が
読み込まれていることが確認できます。
core API が呼び出しているコンパイラは上記よりもさらに古い
バージョンとなるわけです。

April2007 のリリースノートを見ると bug fix や最適化など
いろいろ修正が入ったようです。

すべての API で新しいコンパイラに置き換わらないのは、
おそらく core 側に入ったことで、安易に変更出来なくなった
ためでしょう。

ちなみに fxc.exe のコマンドラインには

Microsoft (R) D3D10 Shader Compiler 9.19.949.1075

と表示されますが、生成されたバイナリは 9.19.949.1104 となり
D3DX 側のコンパイラが呼ばれていることがわかります。

実際にコンパイルされた結果を見ると、わかる範囲でですが
若干テンポラリレジスタの割り当てが変更されており、
ConstantBuffer もシェーダーで参照している分しか宣言しない
ようになっていました。

●結論

D3DX 側関数を使う理由にもう1つ

  (5) 最新の HLSL コンパイラを使うことが出来る

がありました。HLSL のコンパイルは fxc.exe か D3DX の関数を
使いましょう。core API 側の関数を使うと古いバージョンの
コンパイラを呼び出してしまいます。
(そしてSDKのリリースノートはちゃんと読んでおきましょう・・)

AMD SSE5 Shader のような新しい命令

AMD が新しい命令セット SSE5 を発表したそうです。

AMD、新たなx86拡張命令セット「SSE5」~「Bulldozer」コアに搭載予定

こちら のページから資料を見ることができます。

3オペランド命令は扱いやすいので素直にうれしいですね。
命令セットをざっと眺めてみると、16bit fp のサポートも
あるみたいです。これはいい!

CVTPH2PS  fp16×4 → fp32×4
CVTPS2PH  fp32×4 → fp16×4

符号1、指数5、仮数10 の s10e5 で、Shader の half 型と一緒です。
相互変換命令によるサポートですが、GPU との相性もいいだろうし
HDR テクスチャの生成や変換も速くなるでしょう。

他にも shader 等ではおなじみの積和命令があります。例えば

FMADDPS  dest, src1, src2, src3

これは dest= src1*src2 + src3 の演算を行うもので、shader だと

mad  r0, r1, r2, r3

に相当します。でもこれ、3オペランドどころか 4オペランドです。
どうやら各フィールドは完全に独立しておらず、どこかの src
レジスタを dest と共有しなければいけないようにみえます。

FMADDPS  xmm1, xmm1, xmm2, xmm3/mem32
FMADDPS  xmm1, xmm1, xmm3/mem32, xmm2
FMADDPS  xmm1, xmm2, xmm3/mem32, xmm1
FMADDPS  xmm1, xmm3/mem32, xmm2, xmm1

よく読んでみると確かに、レジスタフィールドは
DREX.dest、ModRM.reg、ModRM.r/m の3箇所で、
残る1つのソースは dest と同じレジスタを使うと書いてありました。
あまり素直に喜べないかもしれません。

演算時の符号バリエーションとして次の4種類、それぞれ個別の
命令があるようです。

dest=  src1*src2 + src3
dest=  src1*src2 - src3
dest= -src1*src2 + src3
dest= -src1*src2 - src3

また整数演算用の4オペランド積和命令もあります。

これら以外にも、比較などいろいろ追加命令があります。
例えば PHADDBQ を使うと、8bit の値×8 の合計がいっぺんに求まります。
128bit レジスタは 16byte 相当なので、上位 8個と下位 8個の byte
値の合計2個になります。

8bit + 8bit → 16bit
16bit + 16bit → 32bit
32bit + 32bit → 64bit

と、加算3段階分です。