Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験

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 としてレンダリングできました。

Direct3D 10 ShaderModel 4.0 で整数アクセス2、異なる View を活用

前回書いた整数テクスチャの扱いに関して少々追加です。
レンダリングで更新する時は整数処理を行いますが、デバッグなどで
画面に描画する時は 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	InputTextureUI;
Texture2D	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 に補間した値で受け取って
いるためテクスチャのサイズを乗算する処理が入っています。

Direct3D 10 ShaderModel 4.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 );

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

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

Texture2D	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 はもうしばらく気づかず見落としていたかもしれません。

新型 PSP (PSP-2000) とゲームアーカイブス

新型 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) ゲームアーカイブス

Direct3D 10 HLSL Effect/FX リソース設定のはまり

●前置き

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 関連のステート設定についてもきちんと
把握しておく必要があるかもしれません。