Archives

November 2008 の記事

結局 GeometryShader だけで描画できました。
これだと D3D11 でもハードウエアアクセラレートかかるので
回転アニメーションとかも余裕。

tess font gs

入力頂点は、最後の1つを複製して 6頂点単位に変更。
それぞれが float2 × 4 なのは変わらず。

・エッジ x4
・中心座標
・ダミー

描画 TOPOLOGY は D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ に変更。
これを triangleadj で受け取れば 6 頂点入力ができます。

HS/DS を NULL にして代わりに GS を設定するだけ。

iContext->RSSetState( iRS_WIREFRAME );
UINT	stride= sizeof(float)*2*4;
UINT	offset= 0;
iContext->IASetVertexBuffers( 0, 1, &iVBuffer, &stride, &offset );
iContext->IASetInputLayout( iLayout );
iContext->VSSetShader( iVS, NULL, 0 );
iContext->GSSetConstantBuffers( 0, 1, &iBufferV );
iContext->GSSetShader( iGS, NULL, 0 );
iContext->PSSetShader( iPS, NULL, 0 );
iContext->OMSetRenderTargets( 1, &iRenderTargetView, iDepthStencilView );
iContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ );
iContext->Draw( vdata0_size*sizeof(float)/(sizeof(float)*2*4), 0 );

各エッジを最大 8 分割と仮定すると最大
8 triangle × 3 × 4edge = 96 頂点出力される可能性があります。

各シェーダーを vs_4_0 gs_4_0 ps_4_0 向けにコンパイルすると
RADEON HD4850 で HARDWARE 動作しました。API は D3D11 のまま。

// gs_4_0
struct VS_OUTPUT {
	float2	vPos0	:	VSPOS0;
	float2	vC0	:	VSPOS1;
	float2	vC1	:	VSPOS2;
	float2	vPos1	:	VSPOS3;
};

cbuffer pf {
	float4x4	WVP;
};

struct DS_OUTPUT {
	float4	vPos	: SV_Position;
};

void addv( float2 pos, inout TriangleStream<DS_OUTPUT> stream )
{
	DS_OUTPUT	dout;
	dout.vPos= mul( float4( pos.xy, 0, 1 ), WVP );
	stream.Append( dout );
}

float2 UVtoPositionB( const VS_OUTPUT p, float t )
{
	float	t2= 1.0-t;
	float4	u= float4( t2*t2*t2, t2*t2* t*3, t2* t* t*3, t* t* t );
	return	 u.x*p.vPos0.xy +u.y*p.vC0.xy +u.z*p.vC1.xy +u.w*p.vPos1.xy;
}

void Curve4( float2 center, const VS_OUTPUT v,
			inout TriangleStream<DS_OUTPUT> stream )
{
	const int	step= 8; // = TessFactor
	float	t= 0;
	float	tstep= 1.0/step;
	float2	prevpos= UVtoPositionB( v, 0 );
	for( int i= 0 ; i< step ; i++ ){
		t+= tstep;
		float2	pos= UVtoPositionB( v, t );
		addv( prevpos, stream );
		addv( pos, stream );
		addv( center, stream );
		stream.RestartStrip();
		prevpos= pos;
	}
}

void Edge( float2 center, const VS_OUTPUT v,
			inout TriangleStream<DS_OUTPUT> stream )
{
	if( v.vC0.x > -1e4 ){
		Curve4( center, v, stream );
	}else{
		// 直線の場合
		addv( v.vPos0, stream );
		addv( v.vPos1, stream );
		addv( center, stream );
		stream.RestartStrip();
	}
}

// step(8) x 3 x 4 = 96
[maxvertexcount(96)]
void main(
	triangleadj VS_OUTPUT input[6],
	inout TriangleStream<DS_OUTPUT> stream )
{
	Edge( input[4].vPos0, input[0], stream );
	Edge( input[4].vPos0, input[1], stream );
	Edge( input[4].vPos0, input[2], stream );
	Edge( input[4].vPos0, input[3], stream );
}

最適化していないので、四隅と中心の頂点の計算が重複したままです。


関連エントリ
Direct3D11/DirectX11 (15) GPU を使ったアウトラインフォントの描画の(3)
Direct3D11/DirectX11 (14) GPU を使ったアウトラインフォントの描画の(2)
Direct3D11/DirectX11 (13) TessFactor とシェーダーリンクの補足など
Direct3D11/DirectX11 (12) テセレータのレンダーステート他
Direct3D11/DirectX11 (11) 互換性とシェーダーの対応表など
Direct3D11/DirectX11 (10) テセレータの補間
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分



↓手書きのフォントもベクタ表示
tess font 40

続きです。
前回表示したアウトラインフォント のシェーダー側の動作となります。

入力データは quad のエッジ情報です。通常は VertexShader に頂点情報を渡しますが、
接続する 2頂点+2コントロールポイント座標を一組としてシェーダーに渡します。

1つの quad を描画するためには 4辺分+中心座標が必要なので、これが 5組あります。
よって描画データは下記の通り。

// 1 quad 分
v0 c0 c1 v1
v0 c0 c1 v1
v0 c0 c1 v1
v0 c0 c1 v1
g0 0  0  0

 v0= x,y 頂点
 c0= x,y コントロールポイント
 g0= x,y 中心座標
 0= 0,0 未使用

ラインの場合コントロールポイントが不要なので c0, c1 は使いません。
中心座標は x,y のみなので、残りは全部 0 です。

カーブの場合: v0 c0 c1 v1
ラインの場合: v0 -1e5 0 v1

例
249.593597f,395.421600f,  302.652008f,346.582397f,  341.562408f,290.375214f,  366.312805f,226.812805f,
366.312805f,226.812805f,  -1e5f,0,  0,0,  324.124786f,219.500000f,
324.124786f,219.500000f,  298.250397f,282.316803f,  263.750397f,333.031189f,  220.624802f,371.656006f,
220.624802f,371.656006f,  -1e5f,0,  0,0,  249.593597f,395.421600f,
293.008575f,305.777374f,  0,0, 0,0, 0,0,


IA に渡す頂点の定義は次の通り。float2 × 4

D3D11_INPUT_ELEMENT_DESC	ldesc[]= {
{  "VPOS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0,
				D3D11_INPUT_PER_VERTEX_DATA, 0	},
{  "VPOS", 1, DXGI_FORMAT_R32G32_FLOAT, 0, sizeof(float)*2,
				D3D11_INPUT_PER_VERTEX_DATA, 0	},
{  "VPOS", 2, DXGI_FORMAT_R32G32_FLOAT, 0, sizeof(float)*4,
				D3D11_INPUT_PER_VERTEX_DATA, 0	},
{  "VPOS", 3, DXGI_FORMAT_R32G32_FLOAT, 0, sizeof(float)*6,
				D3D11_INPUT_PER_VERTEX_DATA, 0	},
};

これが 5 個なので D3D11_PRIMITIVE_TOPOLOGY_5_CONTROL_POINT_PATCHLIST
で描画します。

Vertex Shader は素通り

struct VS_INPUT {
	float2	vPos0	: VPOS0;
	float2	vC0	: VPOS1;
	float2	vC1	: VPOS2;
	float2	vPos1	: VPOS3;
};
struct VS_OUTPUT {
	float2	vPos0	: VSPOS0;
	float2	vC0	: VSPOS1;
	float2	vC1	: VSPOS2;
	float2	vPos1	: VSPOS3;
};

VS_OUTPUT main( VS_INPUT idata )
{
	VS_OUTPUT	odata;
	odata.vPos0= idata.vPos0;
	odata.vPos1= idata.vPos1;
	odata.vC0= idata.vC0;
	odata.vC1= idata.vC1;
	return	odata;
}


Hull Shader はカーブかラインか区別して異なる TessFactor を設定します。

#define	MAX_POINTS	5

struct VS_OUTPUT {
	float2	vPos0	: VSPOS0;
	float2	vC0	: VSPOS1;
	float2	vC1	: VSPOS2;
	float2	vPos1	: VSPOS3;
};

struct HS_OUTPUT {
	float2	vPos0	: HSPOS0;
	float2	vC0	: HSPOS1;
	float2	vC1	: HSPOS2;
	float2	vPos1	: HSPOS3;
};


struct HS_DATA {
	float	Edge[4]		: SV_TessFactor;
	float	Inside[2]	: SV_InsideTessFactor;
};

HS_DATA func( InputPatch<VS_OUTPUT,MAX_POINTS> ip )
{
	HS_DATA	pdata;
	const float	tf= 8.0;
	const float	lineFlag= -1e4;
	// ラインを区別する
	pdata.Edge[3]= ip[0].vC0.x > lineFlag ? tf : 1.0f;
	pdata.Edge[2]= ip[1].vC0.x > lineFlag ? tf : 1.0f;
	pdata.Edge[1]= ip[2].vC0.x > lineFlag ? tf : 1.0f;
	pdata.Edge[0]= ip[3].vC0.x > lineFlag ? tf : 1.0f;
	// 内部は 2固定
	pdata.Inside[0]= 2.0;
	pdata.Inside[1]= 2.0;
	return	pdata;
}

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(5)]
[patchconstantfunc("func")]
HS_OUTPUT main(
	InputPatch<VS_OUTPUT,MAX_POINTS> ip,
	uint id : SV_OutputControlPointID
	)
{
	HS_OUTPUT	odata;
	odata.vPos0= ip[id].vPos0;
	odata.vPos1= ip[id].vPos1;
	odata.vC0= ip[id].vC0;
	odata.vC1= ip[id].vC1;
	return	odata;
}


Doman Shader は生成された頂点がどのエッジか区別して、それぞれ異なるパラメータ
補間を適用します。
InsideTessFactor が 2.0 なので、Tessellator で分割されるのはエッジだけです。
上下左右のエッジは

・u が 0.0
・u が 1.0
・v が 0.0
・v が 1.0

で区別することができます。上のどれでもない場合は中心座標です。

例として 1辺だけ TessFactor[0] = 4 の場合を考えます。
このとき生成される頂点の uv は下記の 8点となります。

四隅
(0.0, 0.0)
(1.0, 0.0)
(0.0, 1.0)
(1.0, 1.0)

中心
(0.5, 0.5)

分割で挿入された点
(0.0, 0.25)
(0.0, 0.50)
(0.0, 0.75)

よって u が 0.0 の場合を区別すれば必要なエッジだけ取り出せることがわかります。

#define	CONTROL_POINT	5

struct HS_DATA {
	float	Edge[4]		: SV_TessFactor;
	float	Inside[2]	: SV_InsideTessFactor;
};

struct HS_OUTPUT {
	float2	vPos0	: HSPOS0;
	float2	vC0	: HSPOS1;
	float2	vC1	: HSPOS2;
	float2	vPos1	: HSPOS3;
};

struct DS_OUTPUT {
	float4	vPos	: SV_Position;
};

cbuffer pf {
	float4x4	WVP;
};

// カーブの場合
float2 UVtoPositionB( const HS_OUTPUT p, float t )
{
	float	t2= 1.0-t;
	float4	u= float4( t2*t2*t2, t2*t2* t*3, t2* t* t*3, t* t* t );
	return	 u.x*p.vPos0.xy +u.y*p.vC0.xy +u.z*p.vC1.xy +u.w*p.vPos1.xy;
}

// 直線の場合
float2 UVtoPositionL( const HS_OUTPUT p, float ux )
{
	return	p.vPos0 * (1-ux) + p.vPos1 * ux; // 実際は不要
}

float2 UVtoPosition( const HS_OUTPUT p, float ux )
{
	if( p.vC0.x < -1e4 ){
		return	UVtoPositionL( p, ux );
	}
	return	UVtoPositionB( p, ux );
}

[domain("quad")]
DS_OUTPUT main(
		HS_DATA ip,
		float2 uv : SV_DomainLocation,
		const OutputPatch<HS_OUTPUT,CONTROL_POINT> bpatch
	)
{
	DS_OUTPUT	dout;
	const float	EPC= 0.0f; // 誤差が問題になるなら増やす
	float2	pos= float2( 0, 0 );
	// 各エッジを切り分ける
	if( uv.y >= 1.0-EPC ){
		pos= UVtoPosition( bpatch[0], uv.x );
	}else if( uv.y <= 0.0+EPC ){
		pos= UVtoPosition( bpatch[2], 1-uv.x );
	}else if( uv.x >= 1.0-EPC ){
		pos= UVtoPosition( bpatch[1], 1-uv.y );
	}else if( uv.x <= 0.0+EPC ){
		pos= UVtoPosition( bpatch[3],   uv.y );
	}else{
		pos= bpatch[4].vPos0.xy; // 中心
	}
	dout.vPos= mul( float4( pos.xy, 0, 1 ), WVP );
	return	dout;
}

エッジの切り分けは、四隅の頂点が共有されている点に注意です。
どの uv 値をどのエッジに割り当てるのかは任意で構いませんが、四隅が共有されている
関係上、エッジの並びが連続していなければカーブが不連続になります。

またエッジ単位に Tess Factor のスケールを入れれば、長い辺など必要な場所のみ
より細かく分割するなどの応用ができるでしょう。


前回のアルゴリズムはまだ完全に形状を分割できるとは限らず、いくつか問題が生じる
ケースがあります。

(1) 分割時に対応する頂点が見つけられない場合がある
(2) アウトラインからはみ出さない位置に中心頂点を配置できない場合がある

このような状態に陥った場合、対象のプリミティブのエッジを分割して
頂点(アンカー)を追加することで対処可能です。

シェーダーを書き出していて気がつきましたが、これ GeometryShader だけでも実現
できそうですね。GeometryShader には 5 頂点入力がないので、頂点数さえ合わせれば
ハードウエアで実行できそうです。


関連エントリ
Direct3D11/DirectX11 (14) GPU を使ったアウトラインフォントの描画の(2)
Direct3D11/DirectX11 (13) TessFactor とシェーダーリンクの補足など
Direct3D11/DirectX11 (12) テセレータのレンダーステート他
Direct3D11/DirectX11 (11) 互換性とシェーダーの対応表など
Direct3D11/DirectX11 (10) テセレータの補間
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver


Direct3D 11 の Tessellator を使って、GPU だけでアウトラインフォントの描画を
行ってみました。
↓左からそれぞれ TessFactor (分割数) = 1, 2, 12

tess font 20

改良しました。

(1) 事前の変換処理を自動化した。
(2) isoline ではなく quad プリミティブを使ったテセレートを行っており、
  必要に応じて任意のエッジをテセレートできる。

まだ (1) がやっとなのであまり最適化していません。

前回
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など

tess font 21

↑左が前回のもの。2点 X-Y を挿入し、赤 (B-C-X-Y) と 緑 (D-E-X-Y) の 2つの
プリミティブに分解。

右が今回のもの。B-C-D-E で 1つの quad で表現され、必要なエッジのみ分割。
中央の X の位置は事前に求めておく


塗りつぶしたフォントの描画のために、事前にプリミティブに分割します。
思ったよりも難しい処理で、特に細長い三角形ができてしまうとアウトラインから
はみ出します。

tess font 22

↑では波線が本来のカーブです。A の上のエッジが沈み込んで対角線に重なっています。
カーブが極端になると対角線エッジの直線部分が表に出てしまいます。
(前回はこれをうまく解決できなかったので手作業だった)

いろいろと試行錯誤した結果がこれ↓です。

tess font 24
↑ましになったもの。

元↓はこちら。

tess font 23
↑だめな分割 (はみ出ているのはバグ)。

(白線=カーブ、青線=直線、ピンクの点=中心の頂点、水色の点=コントロールポイント)


(1) 角度が一番開いた頂点を求める。
(2) (1) の頂点から直線を引ける他の頂点を探す。
  (エッジと交差しない、かつ隣接でない、かつ文字の外側ではない)
(3) (2) の条件を満たす頂点の中で一番近い頂点と結ぶ。エッジを挿入して領域を分ける
(4) 4 頂点以下の領域ができたら取り除く。

すべての領域が取り除かれるまで再帰的に繰り返します。
比較的細長い領域への分割を避けてぶつ切りになります。

内外判定のために方向をそろえる必要があるため挿入したエッジは両面分必要です。

循環している可能性があるので、(3) で単一のエッジを挿入しても 2つの領域が
できるとは限りません。

(4) の場合くりぬいた領域が裏返しで繋がっている可能性があるため、4頂点以下の
パスのループでも閉じているかどうか判定が必要です。(対角線の可視など)
上の「だめな分割」の図は失敗しています。

領域毎に、アウトラインをじゃましない位置に中心座標を配置します。(上の図のピンクの点)
ただしアンカーが少なくカーブが極端な場合、最適な中心座標は求まらない可能性が
あります。まだ完全には解決していません。

tess font 25
↑ TessFactor = 8 のワイヤー表示です。


●データの準備

xml で対応ツールが比較的多いことと参照しやすいため、SVG / FXG フォーマットを
使用しています。
Scalable Vector Graphics (SVG) 1.1 Specification

含まれるカーブの種類は複数あり、直線部分も Line で出力されるので、変換時に
情報を保存しておきます。
現在エッジ情報は 座標ペア × 4 (Cubic Bezier時) で、Arc は未対応です。

v0.xy, c0.xy, c1.xy, v1.xy

Line 時は c0.xy, c1.xy は未使用。c0.x に範囲外の値を入れて、シェーダー内で
未使用(直線)かどうか判断できるようにしています。

(1) カーブの種類をを考慮してパスデータを 4 パラメータに変換
(2) エッジの方向をノーマライズする

(2) は内外判定のため。進行方向 v0 → v1 に向かって右側が必ず文字の外側など、
どちらかに統一されていれば OK。

次回に続きます。


関連エントリ
Direct3D11/DirectX11 (13) TessFactor とシェーダーリンクの補足など
Direct3D11/DirectX11 (12) テセレータのレンダーステート他
Direct3D11/DirectX11 (11) 互換性とシェーダーの対応表など
Direct3D11/DirectX11 (10) テセレータの補間
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver


●TessFactor と方向の対応

テセレータによる分割数は Hull Shader で指定します。

float	Edge[4]  : SV_TessFactor;
float	Index[2] : SV_InsideTessFactor;

quad の場合上記 6パラメータで指定します。このとき各 TessFactor とテセレータ
出力の uv 値との対応は下記の通り。

                    u          v
SV_TessFactor[0]    0.0        0~1.0
SV_TessFactor[1]    0~1.0     0.0
SV_TessFactor[2]    1.0        0~1.0
SV_TessFactor[3]    0~1.0     1.0

例えば u方向を右向き → 、v方向を上向き ↑ として 2D でレンダリングすると
TessFactor[0][1][2][3] は 左、下、右、上 の順番となります。

 (0,1)  [3]   (1,1)
  ┌─────┐
  │     │
  │     │
 [0]│     │[2]
  │     │
  │     │
  └─────┘
 (0,0)  [1]   (1,0)

InsideTessFactor は下記の通り。

SV_InsideTessFactor[0] uの分割 (u=0~1.0)
SV_InsideTessFactor[1] vの分割 (v=0~1.0)


● Dynamic Shader Linkage の補足

Dynamic Shader Linkage には 2つの大きな変更があります。

(1) シェーダー内の命令の分岐先を外部から切換えられるようになった
(2) HLSL の記述で java 風の class 定義ができるようになった

表面的には (2) の方が大きいように見えますが、本来の目的は動的シェーダーリンク
という名前の通り (1) の方です。
むしろ (1) を実現するために、さまざまな候補の中から (2) の実装方法を選択した
だけだといえるかもしれません。
(詳しくはこちら Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage)


シェーダーによって、ハードウエアのレンダリング動作を自分で記述することが
できるようになりました。
自由度が高まる反面、動作の種類毎に個別の異なるシェーダープログラムを用意
しなければならないという問題も生じました。

例えばライティングアルゴリズムによって数種類。基本的なものだけでも

・環境光源
・スカイライト
・平行光源
・点光源
・スポットライト

等複数存在します。
複数光源を照射する場合、オブジェクトの配置位置によって光源の組み合わせが変わる
かもしれません。例えば屋外だと平行光源が必要ですが屋内だと必要なかったりします。

・屋外用光源シェーダー
 太陽平行光源+環境スカイライト+エフェクト点光源

・屋内用光源シェーダー
 環境光源+点光源×1~4

など。

マテリアルによってはテクスチャをレイヤーで複数重ねたり、それぞれのブレンド式
を変えるかもしれません。
ノーマルマップやレリーフマップを持つ場合、付随して頂点に必要な情報も増えます。

他にも各レイヤー毎に独立した Texcoord (uv) を持たせるのか、テクスチャによって
は共有するのか、パラメータで生成するのかなど。

ゲームで実際に使うことを考えると、法線マップを圧縮したり、アルファチャンネルの
用途をマテリアルによって使い分けたり、場面によって精度を落としたり復号したり、
半透明やテクスチャアルファによる抜きの扱いでシェーダーを分けたり。
影のレンダリングのありなしや、ライトスペースの求め方の違い、フィルタリングの
種類による変更など。

VertexShader でもアニメーションによって、頂点ウエイトの個数、ビルボード化、
ジオメトリインスタンシングへの対応など種類が必要です。

これらをすべて自由に組み合わせられるような汎用的な描画の仕組みを作ると
シェーダーの数は爆発的に増えます。

もちろんシェーダープログラムも外部の情報へアクセスはできますし、分岐やループ
命令もありました。しかしながらシェーダーの組み合わせをによるバリエーション数を
減らすために用意された機能ではなかったことが問題でした。

マテリアル(描画命令)単位で大きな処理の切換えをしたいだけなのに、コード内で
分岐を行うのは少々大げさでです。
最適化の妨げになるし条件判断コードの実行によるパフォーマンスへの影響もあった
ので、どちらを取るか選択を迫られます。
(シェーダーを切換えるための負荷が減るメリットはあります)

Direct3D 11 (Shader Model 5.0) の Dynamic Shader Linkage の導入は
これらの問題を解決するために設けられたものです。
シェーダーのバリエーションを減らして管理しやすくすることが目的で、プログラム内
の命令ジャンプの飛び先を外部から切換えることが可能になりました。

分岐切り替えの粒度も不必要に細かくなく適切で、レンダリング命令単位(マテリアル単位)
で決められるし、その条件判断もシェーダーの外部で一度だけやればいいわけです。

期待が大きい反面、気になる点もあります。

・動的リンクで切換え可能なコードは予め 1つのシェーダーにコンパイルされている必要がある
・最適化への影響

オブジェクトのインスタンスも単なる Buffer へのマッピングに過ぎず、すべて静的に
決まってなければなりません。
非常に巨大な単一のシェーダープログラムができあがる可能性があります。

分岐先の決定は描画時まで遅延されるため、その分 HLSL コンパイラでできる最適化は
減少します。間接ジャンプはインライン化を阻害しますし、結局サブルーチン分の
消費レジスタも予めアロケートしておかなければならないのではないか、との疑問も
生じます。

シェーダーの単なる分岐と本当に違うのか、効果があるのかどうか、
動的リンクによるパフォーマンスの影響はどの程度なのか、
実ハードが出てからあらためて検証する必要がありそうです。

ちなみに D3D11 付属の HLSL コンパイラでは、class 定義だけなら ShaderModel 4.0
以前をターゲットにしても使えます。あくまで表記上の違いだけなので、できることは
今までと変わりません。
動的な飛び先の切り替え (interface 経由の呼び出し) は ShaderModel 5.0 以降のみ
となります。


関連エントリ
Direct3D11/DirectX11 (12) テセレータのレンダーステート他
Direct3D11/DirectX11 (11) 互換性とシェーダーの対応表など
Direct3D11/DirectX11 (10) テセレータの補間
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver



●レンダーステート

テセレータにはレンダーステートがありません。
ID3D11DeviceContext::TSSet~ 等の命令は一切無し。
その代りになるのが Hull Shader のたくさんある attribute です。

[domain("tri")]
[partitioning("fractional_even")]
[outputtopology("triangle_ccw")]
[outputcontrolpoints(4)]
[patchconstantfunc("func")]
float4 main(
	InputPatch<VS_OUTPUT,MAX_POINTS> ip,
	uint id : SV_OutputControlPointID
	) : TSPOS
{
   ...
}

HullShader の attribute は単なるヒントではなく、Tessellator の動作そのものを
決定しています。
テセレータの動作を変えるにはシェーダーまるごと置き換える必要があり、レンダー
ステートとシェーダーが一体化しているともいえます。

今までのシェーダーからは少々特殊ですが、管理するオブジェクトが減るので扱いは
楽です。他のシェーダーでも、シェーダーとペアで使うことがわかりきったステートなら
シェーダーに含めてしまって構わないのかもしれません。


●Effect

ShaderModel 5.0 にはまだ Effect ( .fx, fx_5_0 ) が無いようです。
現在のベータ SDK では、core API を使って各シェーダーを個別に生成したり管理する
必要があります。
Effect (ID3D10Effect) を core API 化してしまった Direct3D10 と比べると
何となくトーンダウンしているようにも見えます。

これが方針転換なのか、単に未完成で beta に含まれていないだけなのかまだわかりません。
シェーダー数が増えて大変そうだし後者のような気もします。
だけど Shader Compiler の分離もあるので、D3D10 の反省を踏まえての仕様変更の可能性もあります。
ツールやゲームエンジン等の設計にも関わってくるので、今後どうなるのか気になるところです。


●Tessellator と Triangle patch

Tessellator が扱える patch のタイプには isoline tri quad の 3種類あります。
(Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など)

quad / isoline はこれまでなんどか取り上げたとおり、テセレータは
float2 の DomainLocation (uv) を出力します。
tri の場合は 3次元の uvw を返します。

この u v w はそれぞれ、Triangle の 3頂点に対する割合(距離)を示して
います。(barycentric coordinates)

float3 v1, v2, v3;
pos= v1*uvw.x + v2*uvw.y + v3*uvw.z;

↓左から TessFactor = 222-2、333-3、444-4

tess tri

中 (InsdeTessFactor) を 2に固定して外側だけ割った場合。
↓左から TessFactor = 111-2、888-2、181-2

tess tri

右端(181-2)は、1 つのエッジだけ 8分割したものです。
以前アウトラインフォントの描画では quad あたり 1辺の分割に限定して isoline
を使いました。事前処理でこのようなプリミティブにきちんと分解できれば、
一度に複数のエッジを分割対象とすることも可能です。
まだまだ効率化はできるでしょう。


関連エントリ
Direct3D11/DirectX11 (11) 互換性とシェーダーの対応表など
Direct3D11/DirectX11 (10) テセレータの補間
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver



・D3D11 の方が対応ハードウエアのカバー範囲が広い。
・D3D10 専用ソフトは動かなくても、D3D11 対応ソフトなら動く可能性がある。
・逆に D3D11 対応ソフトは、D3D10 専用ソフトよりも絵がきれいとは限らない。

シェーダー対応をまとめるとこんな感じでしょうか。

              FIXED  SM1  SM2  SM3  SM4  SM5 
DirectX7/D3D7   ○   ×   ×   ×   ×   ×
DirectX8/D3D8   ○   ○   ×   ×   ×   ×
DirectX9/D3D9   ○   ○   ○   ○   ×   ×
DirectX/D3D10   ×   ×   ×   ×   ○   ×
DirectX/D3D11   ×   ×   △   ○   ○   ○

(SM1~5 は ShaderModel1.0~5.0)

Direct3D11 で ShaderModel2.0 が △ なのは、ShaderModel2.0 の固定機能が使えない
可能性があるからです。
例えば 2.0 世代のフォグはハードの固定機能で、RenderState で設定していました。
D3D11 にはこの手のステートがないため同等のブレンドを実現するためにシェーダーに
追加コードが必要です。
その分 Direct3D9 以前を使うよりも速度が落ちる可能性があるからです。


Direct3D10 以降、GeometryShader や HullShader + Tessellator + DomainShader
など、注目すべき新機能がジオメトリに移ってきました。
DirectX8 から始まったシェーダーの拡張は、しばらくはピクセルが中心だったといえます。
固定少数だったピクセルが世代を重ねて完全な 32bit になり、テクスチャルックアップも
制限が無くなり、定数レジスタが増えて実行可能命令数も増加しました。

PixelShader1  固定少数
PixelShader2  浮動小数 16~24bit
PixelShader3  浮動小数 32bit、動的分岐
PixelShader4  Unified化

必要な面積しか演算しなくて済むので効率がよいし、見た目の効果も大きいし、
大量の並列化による演算は CPU には真似ができない分野です。
GPU の力を一番実感しやすかったのかもしれません。

Shader の区別が無くなるところまで行き着いた今、流れはジオメトリに向いています。
単一の描画パイプラインの中で、頂点の生成やプリミティブの削除など自由自在です。
CPU の追い上げもありますが、GPU ならではの活用やさまざまな技法がこれから
生まれてくるものと期待できます。


関連エントリ
Direct3D11/DirectX11 (10) テセレータの補間
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分




isoline を使うと線分の分割だけでなく 2次元方向にも数を増やすことが出来ます。
おそらく髪の毛の表現などに使われる機能だと考えられます。
uv の u 方向のみライン描画されるので、u が毛の滑らかさ(丸み)、v が密度(本数)
を表します。

出力トポロジの line は domain("isoline") の場合のみ使用できるようです。
前回の記事で tri/quad の出力にも line を含ませていたのは間違いでした。
訂正致します。


テセレータを使うと、かなり簡単に頂点を増やすことができます。
特に isoline は uv 方向に任意に頂点を生成できるため使い勝手が良さそうです。
outputtopology を point にして GeometryShader でオブジェクトを配置すれば、
草むらなどの表現に使えるかもしれません。LOD も容易です。


テセレータは partitioning の指定によってテセレータの各段階を補間できるように
なります。一番シンプルな isoline の 1 line だけで調べてみました。

共通事項として、TessFactor のどれかに 0 が含まれているとプリミティブは表示
されないようです。無関係の固定図形を GeometryShader で描画している場合も
出力されないので、描画が完全に捨てられていると判断できます。

integier の場合、TessFactor が 1.0 の時分割数が 1 です。
isoline で頂点のみ出力すると両端の 2点が表示されます。(点を '*' で表します)

1.0=   *              *

TessFactor= 2.0 の時、線分は 2分割されて 3頂点が表示されます。

2.0=   *      *       *

同じように各 partitioning タイプ毎に TessFactor を変えて調べてみました。

fractional_odd
1.0= *              * 1分割
1.5= * *          * *
2.0= *  *        *  *
2.5= *   *      *   *
3.0= *    *    *    * 3分割

fractional_even
1.0= *       *       *
2.0= *       *       * 2分割
2.5= *      ***      *
3.0= *     * * *     *
3.5= *    *  *  *    *
4.0= *   *   *   *   * 4分割

integer == pow2
1.0= *               * 1分割
1.5= *       *       * 切り上げ
2.0= *       *       * 2分割
2.5= *    *     *    * 切り上げ
3.0= *    *     *    * 3分割
4.5= *   *   *   *   * 切り上げ
4.0= *   *   *   *   * 4分割

fractional_odd は奇数の時が基準です。
1.0 で 1分割、3.0 で空間が 3分割されており、その間は補間するように頂点が
移動しています。

fractional_even も同じように偶数時が基準となっています。
2分割と 4分割の中間の図形が生成されています。

integer では中間状態はなく、常に整数値で分割された値になります。
ここで興味深いのは、integer の TessFactor は切り捨てではなく切り上げになる
ことです。1.0 を少しでも超えると 2分割相当となります。

この挙動は他の fractional_even , fractional_odd と見比べると納得できます。
間を補間する場合は、1.0 を少しでも超えると補間のための頂点が生成されて移動を
始めるからです。

今回のテストでは pow2 の動作は integer と同じ結果で違いを発見できませんでした。

tess quad odd

上の図は、domain("quad") で partitioning("fractional_odd") の場合の様子を
表示したものです。
それぞれ左上から TessFactor = 1.0、1.5、2.0、3.0 を表しています。

isoline の場合と同じように、quad でも 1.0(1分割) と 3.0(3分割) の間が補間
されていることがわかります。
またその動きも 2次元になっているだけで、基本的には isoline と変わりません。
両端から生成された線分が次の分割状態に近づくよう中心に徐々に移動しています。


テセレータの挙動は想像よりもずっとわかりやすいものでした。テセレータ部だけ
固定機能と言われていたため、もっと決まった動作しかできないかと思ってましたが
HullShader DomainShader のおかげで十分な自由度もあります。
正直かなり使いやすいと思います。

CPU の core 数も増えて、CPU と GPU の違いも少なくなりつつあります。
複雑で特別な仕様の GPU や API をいちいち覚えるくらいなら、必要な機能だけ
CPU で直接書いた方が速いんじゃないかと思ってましたが見直しました。


関連エントリ
Direct3D11/DirectX11 (9) テセレータによるアウトラインフォントの描画など
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver



●Tessellator のタイプ

Tessellator の出力には 3種類のタイプがあります。

[domain(type)]:
 isoline  TessFactor[2]                           出力 point line
 tri      TessFactor[3]   InsideTessFactor[1]     出力 point line triangle
 quad     TessFactor[4]   InsideTessFactor[2]     出力 point line triangle

2008-11-17追記:間違いを修正しました。上記赤字と、tri/quad では line を出力できませんでした。

quad, tri はそれぞれ四角パッチ、三角パッチを意味しています。
isoline は面を均等に割った 2次元頂点を出力するようです。
TessFactor[1] が 1.0 の場合、線分の分割に相当します。

例えば isoline で、TessFactor=8,4 を与えると下記の点を出力します。

isoline point

Tessellator が出力するプリミティブの形状は次の4種類です。

[outputtopology(type)]:
 point
 line
 triangle_cw
 triangle_ccw

これらは Hull Shader のアトリビュートで指定します。
出力トポロジの影響を直接受けるのは Geometry Shader 以降ですが、その設定も
Domain Shader ではなく HullShader が行うので注意。

ちなみに DomainShader は、プリミティブの形状 (outputtopology) を判別する
手段がありません。
DomainShader は TessFactor を参照できるため、上の domain のみ指定する必要
があります。

isoline では triangle を出力できません。
line を指定すると、単なる横線が複数出力されました。


●TessFactor の種類

partitioning は TessFactor による分割方法を指定します。

[partitioning(type)]:
 integer
 fractional_even
 fractional_odd
 pow2

fractional だと、TessFactor が実数値の場合間を補間します。
LOD のつながりがよりスムーズになると考えられます。

outputtopology も、partitioning もマニュアルに書いてあることと違います。
まだまだ間違いが多いようです。


●アウトラインフォントの描画

テセレータパイプラインは、HullShader と DomainShader のおかげでかなり自由度が
高くなっています。固定機能の制限は小さく、さまざまな応用が考えられます。

以前「3D だけでなく、ベクタフォントを GPU で直接描画するなど 2D 面でも応用できる
かもしれません。」
などと書いてしまったので実際にやってみました。

tessellator font step 1
tessellator font step 8

拡大とワイヤー表示は次の通り

tessellator font step 8
tessellator font wire

実際の補間は DomainShader が行っているため、分割したポリゴンのエッジだけ
細分化することは思ったより簡単にできそうです。
quiad や triangle で特定のエッジだけ割っても良いのですが、内部に出来た頂点が
無駄になるし、割ったエッジに影響が出ないよう一点に集めないといけないので
少々複雑です。

isoline があるので、テセレータの段階では輪郭をそのまま割るだけにします。
ポリゴン化は GeometryShader で行います。

簡単にするため、データは前処理で専用のものを用意します。
3次ベジェ のアウトラインを使っています。

tess_font point

各区間、A-B や B-C はそれぞれ 2点の頂点座標(アンカー)+ハンドル2点 で表現
できます。各区間を 4点の制御座標でデータ化します。
このとき隣接するアンカーポイント(頂点)は共有可能です。

A-B :  頂点A, 制御点A0, 制御点A1, 頂点B
B-C :  頂点B, 制御点B0, 制御点B1, 頂点C


これをそのまま D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST
で渡し、domain("isoline")、outputtopology("line") で描画すれば容易にアウトラインの
線描画ができます。DomainShader でベジェカーブを求めるだけです。
フォントのアウトラインそのままで追加情報は不要です。


塗りつぶしは少々手間がかかります。上図の X, Y のように前処理で描画用の
追加点が必要です。

A-B は A-X-B の 3角形、B-C は B-X-Y-C の 4角形を描画します。
処理を簡単にするため、A-X-B は X が2頂点重なってると見なしてすべて四角形で行います。

これらの追加頂点を分割面 (A-X-B や B-X-Y-C) 毎に保持し、GeometryShader まで
情報が届くようにします。

必要な座標は 2次元 x,y のみです。
float4 にすると z,w にもう 1つ座標を入れられます。
アンカーポイントは共有される可能性があるため、面の情報はハンドル(制御点)側の
頂点 zw に入れます。

B-C の例、4頂点

 pointB.xy = 頂点B
 pointB.zw = 未使用

 ctrlB0.xy = 制御点B0
 ctrlB0.zw = 追加の頂点、追加点X

 ctrlB1.xy = 制御点B1
 ctrlB1.zw = 追加の頂点、追加点Y

 pointC.xy = 頂点C
 pointC.zw = 未使用

頂点フォーマットは単純な float4 のみ

D3D11_INPUT_ELEMENT_DESC	ldesc[]= {
{ "VPOS", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0,
	D3D11_INPUT_PER_VERTEX_DATA, 0	},
};
iDevice->CreateInputLayout( ldesc, 1, codeblob->GetBufferPointer(),
				codeblob->GetBufferSize(), &iLayout );

VertexShader はデータにスケーリング補正してるだけでそのまま流します。

// vs.hlsl
struct VS_INPUT {
	float4	vPos	: VPOS;
};

float4 main( VS_INPUT idata ) : VSPOS
{
	float4	pos= idata.vPos;

	const float	SCALE= 0.08;
	const float2	CENTER= float2(137,212);
	pos-= CENTER.xyxy;
	pos*= SCALE.xxxx;
	return	pos;
}

HullShader もテセレータの設定のみ。
分割は線分で行うので domain("isoline") を使い、GeometryShader で 2点のつながり
が必要なので出力は outputtopology("line")。
コントロールポイントはそのままスルーで DomainShader に任せます。

// hs.hlsl
struct VS_OUTPUT {
	float4	vPos	: VSPOS;
};

struct HS_DATA {
	float	Edge[2]	: SV_TessFactor;
};

HS_DATA func()
{
	HS_DATA	pdata;
	pdata.Edge[0]= 8.0;	// ここで分割数の決定、値が大きいとなめらか
	pdata.Edge[1]= 1.0;
	return	pdata;
}

[domain("isoline")]
[partitioning("integer")]
[outputtopology("line")]
[outputcontrolpoints(4)]
[patchconstantfunc("func")]
float4 main( InputPatch<VS_OUTPUT,4> ip,
	uint id : SV_OutputControlPointID ) : TSPOS
{
	return	ip[id].vPos;
}

DomainShader で分割点のベジェ補間を行い、GeometryShader に渡すための頂点を
構築します。
追加点は 2番目と 3番目の zw に入っているので、それらも取り出して頂点に入れます。
uv は Geometry Shader で線分の最後かどうか判断するために使います。

// ds.hlsl
struct HS_DATA {
	float	Edge[2]		: SV_TessFactor;
};

struct HS_OUTPUT {
	float4	vPos	: TSPOS;
};

cbuffer perFrame {
	float4x4	WVP;
};

float4 Bezier0( float t )
{
	float	t2= 1.0-t;
	return	float4( t2*t2*t2, t2*t2* t*3, t2* t* t*3, t* t* t );
}

float2 UVtoPosition( const OutputPatch<HS_OUTPUT,4> p, float2 uv )
{
	float4	u= Bezier0( uv.x );
	return	 u.x*p[0].vPos.xy
		+u.y*p[1].vPos.xy
		+u.z*p[2].vPos.xy
		+u.w*p[3].vPos.xy;
}

struct DS_OUTPUT {
	float4	nPos[2]	: NPOS;
	float	uv	: UV;
	float4	vPos	: SV_Position;
};

[domain("isoline")]
DS_OUTPUT main(
	HS_DATA ip,
	float2 uv : SV_DomainLocation,
	const OutputPatch<HS_OUTPUT,4> bpatch )
{
	DS_OUTPUT	dsout;

	float2	pos= UVtoPosition( bpatch, uv );
	dsout.vPos= mul( float4( pos.xy, 0, 1 ), WVP );

	// 追加点 2つを各頂点に埋め込む
	dsout.nPos[0]= mul( float4( bpatch[1].vPos.zw, 0, 1 ), WVP );
	dsout.nPos[1]= mul( float4( bpatch[2].vPos.zw, 0, 1 ), WVP );

	dsout.uv= uv.x; // Geometry Shader で使う
	return	dsout;
}

GeometryShader は頂点情報から Triangle を作ります。

// gs.hlsl
struct DS_OUTPUT {
	float4	nPos[2]	: NPOS;
	float	uv	: UV;
	float4	vPos	: SV_Position;
};

[maxvertexcount(6)]
void main(
	line DS_OUTPUT input[2],
	inout TriangleStream<DS_OUTPUT> stream )
{
	stream.Append( input[0] );
	stream.Append( input[1] );

	DS_OUTPUT	dout2= input[0];
	dout2.vPos= input[0].nPos[0];
	stream.Append( dout2 );

	stream.RestartStrip();

	if( input[1].uv >= 0.9998f ){
		DS_OUTPUT	dout3= input[1];
		dout3.vPos= input[0].nPos[1];

		stream.Append( input[1] );
		stream.Append( dout2 );
		stream.Append( dout3 );
		stream.RestartStrip();
	}
}

uv で判断しているのは最後だけ 1ポリゴン追加する必要があるためです。
下図でいう 4 番。

tess font

描画自体は簡単にできました。
元となるデータを準備する方が大変です。
中心に位置する追加点を求める何らかの手段が必要となりそうです。


関連エントリ
Direct3D11/DirectX11 (8) テセレータの動作
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver


●固定機能テセレータ

DomainShader で、Tessellator の出力 uv のみを WIRE 描画したものです。

TessFactor = 1, 1, 1, 1
InsideTessFactor = 1, 1
Tessellator1111_11

TessFactor = 3, 3, 3, 3
InsideTessFactor = 3, 3
Tessellator3333_33

TessFactor = 3, 3, 3, 3
InsideTessFactor = 8, 8
Tessellator3333_88

パッチ毎に分割数が異なる可能性があるため内部とエッジの係数を個別に指定します。
上の 3333-33 と 3333-88 は繋がります。
固定機能であるテセレータは、TessFactor に応じて上記の uv パターンを生成し、
必要分の頂点を出力しているだけだと考えられます。
実際の曲面を作るのは HullShader と DomainShader の仕事です。

struct DS_OUTPUT {
	float4	vPos	: SV_Position;
};

[domain("quad")]
DS_OUTPUT main(
		HS_POINT_DATA ip,
		float2 uv : SV_DomainLocation,
		const OutputPatch<HS_OUTPUT,4> bpatch )
{
	DS_OUTPUT	dout;
	uv+= float2( -0.5, -0.5 );
	uv*= float2( 0.5, 0.5 );
	dout.vPos= float4( uv.x, -uv.y, 0, 1 );
	return	dout;
}


●ベジェ

実際にシンプルなベジェ曲面を生成してみます。
単純化するため入力はすべてコントロールポイントとします。

Tessellator32

VertexShader は 16個のコントロールポイント定義のみ。
// vs.hlsl
float4 main( uint vid : SV_VertexID ) : VSPOS
{
	const float4	v[]= {
		{	-2,  2,		2,	1,	},
		{	-1,  2,		0,	1,	},
		{	 1,  2,		0,	1,	},
		{	 2,  2,		2,	1,	},
		{	-2,  1,		0,	1,	},
		{	-1,  1,		0,	1,	},
		{	 1,  1,		0,	1,	},
		{	 2,  1,		0,	1,	},
		{	-2, -1,		0,	1,	},
		{	-1, -1,		0,	1,	},
		{	 1, -1,		0,	1,	},
		{	 2, -1,		0,	1,	},
		{	-2, -2,		2,	1,	},
		{	-1, -2,		0,	1,	},
		{	 1, -2,		0,	1,	},
		{	 2, -2,		2,	1,	},
	};
	return	v[vid];
}


HullShader は TessFactor 設定以外に特にやることがありません。
VS からの出力をスルーし、もう 1つは TessFactor を定義します。

// hs.hlsl
struct VS_OUTPUT {
	float4	vPos	: VSPOS;
};

struct HS_OUTPUT {
	float3	vPos	: TSPOS;
};

struct HS_DATA {
	float	Edge[4]		: SV_TessFactor;
	float	Inside[2]	: SV_InsideTessFactor;
};

HS_DATA func(
	InputPatch<VS_OUTPUT,16> ip,
	uint patchID : SV_PrimitiveID )
{
	HS_DATA	pdata;
	pdata.Edge[0]= 32.0;
	pdata.Edge[1]= 32.0;
	pdata.Edge[2]= 32.0;
	pdata.Edge[3]= 32.0;
	pdata.Inside[0]= 32.0;
	pdata.Inside[1]= 32.0;
	return	pdata;
}

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(16)]
[patchconstantfunc("func")]
HS_OUTPUT main(
	InputPatch<VS_OUTPUT,16> ip,
	uint id : SV_OutputControlPointID,
	uint patchID : SV_PrimitiveID )
{
	HS_OUTPUT	odata;
	odata.vPos= ip[id].vPos.xyz; // そのまま渡すだけ
	return	odata;
}


DomainShader で、生成された頂点の実際の座標を求めます。
同時に View Projection の変換もします。

// ds.hlsl
struct HS_DATA {
	float	Edge[4]		: SV_TessFactor;
	float	Inside[2]	: SV_InsideTessFactor;
};

struct HS_OUTPUT {
	float3	vPos	: TSPOS;
};

struct DS_OUTPUT {
	float4	vPos	: SV_Position;
};

cbuffer perFrame {
	float4x4	WVP;	// View Projection
};

float4 Bezier0( float t )
{
	float	t2= 1.0-t;
	return	float4( t2*t2*t2, t2*t2* t*3, t2* t* t*3, t* t* t );
}

float3 UVtoPosition( const OutputPatch<HS_OUTPUT,16> p, float2 uv )
{
	float4	u= Bezier0( uv.x );
	float4	v= Bezier0( uv.y );
	float3	r;
	r = v.x*(u.x*p[ 0].vPos+u.y*p[ 1].vPos+u.z*p[ 2].vPos+u.w*p[ 3].vPos);
	r+= v.y*(u.x*p[ 4].vPos+u.y*p[ 5].vPos+u.z*p[ 6].vPos+u.w*p[ 7].vPos);
	r+= v.z*(u.x*p[ 8].vPos+u.y*p[ 9].vPos+u.z*p[10].vPos+u.w*p[11].vPos);
	r+= v.w*(u.x*p[12].vPos+u.y*p[13].vPos+u.z*p[14].vPos+u.w*p[15].vPos);
	return	r;
}

[domain("quad")]
DS_OUTPUT main(
	HS_DATA ip,
	float2 uv : SV_DomainLocation,
	const OutputPatch<HS_OUTPUT,16> bpatch )
{
	DS_OUTPUT	dout;
	float3	pos= UVtoPosition( bpatch, uv );
	dout.vPos= mul( float4( pos.xyz, 1 ), WVP );
	return	dout;
}


呼び出し側。GeometryShader は使用していません。

iContext->RSSetState( iRS_WIREFRAME );
iContext->VSSetShader( iVS, NULL, 0 );
iContext->HSSetShader( iHS, NULL, 0 );
iContext->DSSetConstantBuffers( 0, 1, &iPerFrame );
iContext->DSSetShader( iDS, NULL, 0 );
iContext->PSSetShader( iPS, NULL, 0 );
iContext->IASetPrimitiveTopology(
		D3D11_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST );
iContext->Draw( 16, 0 );



関連エントリ
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver


● ComputeShader 用の Append Buffer 作成

何度も DebugLayer に怒られつつテストしてみました。

Buffer
 D3D11_USAGE_DEFAULT
 D3D11_BIND_UNORDERED_ACCESS
 D3D11_RESOURCE_MISC_BUFFER_STRUCTURED
 StructureByteStride= 4
UAV
 DXGI_FORMAT_UNKNOWN
 D3D11_BUFFER_UAV_FLAG_APPEND

AppendStructuredBuffer<uint>	a;
[numthreads(1,1,1)]
void main( uint3 tid : SV_DispatchThreadID )
{
	a.Append( tid.x );
}

バッファ内容の変化が観測できず、まだ動作がつかめていません。
うまく動いていないところや使い方の疑問点もあります。

・ID3D11DeviceContext::CSSetUnorderedAccessViews() に 2つ以上の UAV を渡すと
 シェーダーが落ちる。サンプル HDRToneMappingCS11 では 1つしか渡していない。
・CSSetUnorderedAccessViews() の 3 番目の引数の意味がわからない。
 サンプルでは 2番目のリストのアドレスを UINT* で渡しているが、これが正しい
 とは思えない。


●テセレータ

これも D3D11 での大きな追加機能の一つです。

HullShader と DomainShader を設定することで、その間を繋ぐ Tessellator は
勝手に呼び出されるようになります。
VertexShader と PixelShader の間に、暗黙のうちに Rasterizer が呼ばれるのと
よく似ています。機能も似ています。

GS と HS → DS はオプションなので、パイプラインは下記の 4 通り (+2) です。

  VS → (Rasterizer) PS
  VS → GS → (Rasterizer) PS
  VS → HS → (Tessellator) DS → (Rasterizer) PS
  VS → HS → (Tessellator) DS → GS → (Rasterizer) PS

StreamOutput:
  VS → GS → SO
  VS → HS → (Tessellator) DS → GS → SO

Tessellator を Enable にした場合使用できるプリミティブは Patch のみとなります。
PATCH は新しく追加された Primitive Topology で、1~32 までの頂点を入力可能です。

・Point     List
・Line      List, Strip, ListAdj, StripAdj
・Triangle  List, Strip, ListAdj, StripAdj
・Patch     1~32

Patch の入力頂点は、主に分割時のカーブを求めるためのコントロールポイントとして
使われます。
従来 TriangleListAdj でも最大 6頂点までだったので、入力頂点数は大幅に増えました。
試していませんが DebugLayer のメッセージを見ると GeometryShader で
Patch を直接受け取れる可能性があります。もし使えるなら汎用の多入力頂点としても
応用できそうです。

HullShader は Tessellator に渡すパラメータの生成を行います。
入力された頂点 (コントロールポイント) 毎に走る関数と、パッチ単位で一回だけ走る
関数の 2つエントリポイントがあります。

VertexShader と GeometryShader がセットになったような感じですが、どちらも
1プリミティブ(patch) 分の全頂点に自由にアクセスすることが可能です。

(1) VertexShader が頂点単位に走る
(2) Patchを構成する全頂点(ControlPoint)がキャッシュされたら HullShader を
  呼び出す。
(3) HullShader は 2つの関数が存在する
  - Patch 単位で走る × 1
  - 頂点(ControlPoint) 単位で走る × 頂点数

HullShader が出力すべき必須パラメータは SV_TessFactor と SV_InsideTessFactor
です。これは Path 単位で走る "patchconstantfunc" が設定するようです。

Rasterizer の前に SV_Position が確定していなければならないのと同じように、
Tessellator の前に TessFactor も必須です。

(4) Tessellator が頂点を生成する。
(5) Tessellator の出力頂点毎に DomainShader が走る

DomainShader は、Tessellator の出力である SV_DomainLocation と、
HullShader の出力パラメータをそのまま受け取ります。
DomainShader の役割は、HullShader の出力と Tessellator の出力を元に、正しい
頂点を生成することのようです。

HullShader と違って、Tessellator の実行が完了してから走るわけではありません。
平行して生成される毎にストリームとして呼び出されるようです。
その点 VertexShader に近いといえます。

(6) 1プリミティブ分頂点が生成されたら GeometryShader を呼び出す。


まだ詳しく使い込んでない&調べてませんが、HullShader とテセレータは若干
並列動作しているのかもしれません。
ただし DomainShader が走る前には、依存している HullShader は完了している
必要があります。

─┬ per control point HS ────────→ DomainShader
 └ PatchConstantFunc HS → Tessellator → DomainShader


全コントロールポイントの情報が集まってから各シェーダーに渡されるなど、
シェーダー間で受け渡すデータ量が大幅に増えていることがわかります。
ComputeShader で追加された Append / Consume Buffer の仕組みは、このように
シェーダー間で複雑なデータを受け渡す必然性から設けられたものかもしれません。

単体で見たときはいまいち機能や用途が思い浮かばなかったのですが、HS, DS, GS の
流れを見ていると Append / Consume Buffer の存在意義が見えてきた気がします。

パイプラインが複雑で扱うデータも増えました。
それでも GPU / シェーダーで実行するメリットは、メインメモリを介さず、
CPU を介さずに頂点の増減をストリーム内で閉じたまま扱えることです。


関連エントリ
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver



ComputeShader の使い方はドキュメントも少なくまだよくわかっていません。
使用できる機能の共通性などから、PixelShader に近いか PixelShader の改良版
ではないかと予想していました。でも思ったより違いは多そうです。
Technical Preview (Beta) の段階なので、まだこれから仕様が変わる可能性もあります。


●作成と実行

シェーダーの生成はこれまでの PixelShader 等と同じです。
ShaderLinkage を指定できる点も同じ。

ID3D11Device*		iDevice;
ID3D11ComputeShader*	iCS;
iDevice->CreateComputeShader( blob, blobsize, NULL, &iCS );

ステート設定は CS~ 系の命令を使います。

ID3D11DeviceContext*	iContext;
iContext->CSSetShaderResources( 0, 1, srvlist );
iContext->CSSetShader( iCS, NULL, 0 );
UINT	uavcount= 256;
iContext->CSSetUnroderdAccessViews( 0, 1, &iUAV0, &uavcount );

他のシェーダーとの違いは、任意のアドレスに書き込み可能なリソース UAV を
設定する専用命令 CSSetUnroderdAccessViews() があることです。

UAV (UnorderdAccessView) は、RWBuffer や RWTexture2D 等の書き込み可能な
リソースへのアクセスを意味しています。

ComputeShader はストリーム出力を持たないので Shader は void 宣言されます。
値を返すには必ず出力先として UAV の設定が必要となるようです。

UAV は PixelShader でも使えるはずなのに SetUnorderdAccessView 相当の命令は
ありません。この辺の仕様は要調査です。
将来 10 世代 GPU 対応の ComputeShader 4.0/4.1 が使えるようになった場合も
出力先の扱いがどうなるか気になるところです。

ComputeShader の実行は Draw() ではなく専用命令 Dispatch() を使います。

iContext->Dispatch( 1, 1, 1 );

引数はマニュアルに載ってませんが、走らせるスレッドの数と考えられます。
おそらく CUDA や AMD Stream SDK の thread や block、domain 等に相当する
パラメータでしょう。


●ウィンドウレス実行

ComputeShader は描画とは関係なく実行できるはずです。
そこで Window も DXGI も使わずにシェーダーを走らせてみました。

D3D11CreateDevice(
		NULL,
		//D3D_DRIVER_TYPE_HARDWARE,
		//D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE,
		NULL,
		D3D11_CREATE_DEVICE_DEBUG,
		NULL,
		0,
		D3D11_SDK_VERSION,
		&iDevice,
		NULL,
		&iContext
	);
// hlsl 読み込みは省略 → memory
ID3DBlob*	codeblob;
D3DCompile(
		memory,
		(size_t)size,
		fname,
		NULL,	// macro
		NULL,	// include
		"main",
		"cs_5_0",
		D3D10_SHADER_ENABLE_STRICTNESS,
		0,
		&codeblob,
		&errblob
	);
iDevice->CreateComputeShader(
		codeblob->GetBufferPointer(),
		codeblob->GetBufferSize(),
		NULL, &iCS );
codeblob->Release();


// 出力先
ID3D11Buffer*	iBufferC;
D3D11_BUFFER_DESC	bdesc;
bdesc.Usage= D3D11_USAGE_DEFAULT;
bdesc.BindFlags= D3D11_BIND_SHADER_RESOURCE|D3D11_BIND_UNORDERED_ACCESS;
bdesc.CPUAccessFlags= 0;
bdesc.MiscFlags= 0;
bdesc.ByteWidth= sizeof(float)*4*256;
bdesc.StructureByteStride= sizeof(float);
iDevice->CreateBuffer( &bdesc, NULL, &iBufferC );

ID3D11UnorderedAccessView*	iUAView;
D3D11_UNORDERED_ACCESS_VIEW_DESC	uavdesc;
uavdesc.Format= DXGI_FORMAT_R32_UINT;
uavdesc.ViewDimension= D3D11_UAV_DIMENSION_BUFFER;
uavdesc.Buffer.FirstElement= 0;
uavdesc.Buffer.NumElements= 256;
uavdesc.Buffer.Flags= 0;
iDevice->CreateUnorderedAccessView( iBufferC, &uavdesc, &iUAView );

// CPUアクセス用バッファ
ID3D11Buffer*	iBufferC2;
D3D11_BUFFER_DESC	bdesc;
bdesc.Usage= D3D11_USAGE_STAGING;
bdesc.BindFlags= 0;
bdesc.CPUAccessFlags= D3D11_CPU_ACCESS_WRITE|D3D11_CPU_ACCESS_READ;
bdesc.MiscFlags= 0;
bdesc.ByteWidth= sizeof(float)*4*256;
bdesc.StructureByteStride= sizeof(float);
iDevice->CreateBuffer( &bdesc, NULL, &iBufferC2 );

// 初期データ書き込み
D3D11_MAPPED_SUBRESOURCE	mapres;
iContext->Map( iBufferC2, 0, D3D11_MAP_WRITE, 0, &mapres );
unsigned int*	ptr= reinterpret_cast<unsigned int*>( mapres.pData );
ptr[0]= 0;
iContext->Unmap( iBufferC2, 0 );
iContext->CopyResource( iBufferC, iBufferC2 );

// 実行
for( int i= 0 ; i< 4 ; i++ ){
	iContext->CSSetShader( iCS, NULL, 0 );
	UINT	uavcount= 256;
	iContext->CSSetUnorderedAccessViews( 0, 1, &iUAView, &uavcount );
	iContext->Dispatch( 10, 2, 1 );
}

// 結果読み込みと表示
iContext->CopyResource( iBufferC2, iBufferC );
D3D11_MAPPED_SUBRESOURCE	mapres;
iContext->Map( iBufferC2, 0, D3D11_MAP_READ, 0, &mapres );
unsigned int*	ptr= reinterpret_cast<unsigned int*>( mapres.pData );
printf( "%d\n", ptr[0] );
iContext->Unmap( iBufferC2, 0 );

下記のシェーダーを実行すると結果は「360」。(45 x 2 x 4)
Windows や Present() とか無しにシェーダーが走りました。

// cs.hlsl
RWBuffer<uint>	a;

[numthreads(1,1,1)]
void main( uint3 tid : SV_DispatchThreadID )
{
	InterlockedAdd( a[0], tid.x );
}

Reference では InterlockedAdd でなく直接 a[0]+= tid.x と書いても同じ値に
なってしまいます。要注意です。
実際は Query 等を使って GPU の実行を待つ必要があるかもしれません。


●リソースの受け渡し

GPU が書き込めるリソースは D3D11_USAGE_DEFAULT だけなので、UAV は全部
D3D11_USAGE_DEFAULT になります。D3D11_USAGE_DEFAULT だと CPU で直接
読み出せないため、システム側に D3D11_USAGE_STAGING のミラーバッファを用意し
アクセスの度に CopyResource() しています。
初期化手順も多く、あまり簡単とはいえません。

CPU 側との連携やデータの読み書きの容易さを考えると、CUDA や AMD Stream SDK
を直接使った方が便利だと思います。おそらく CopyResource 相当の転送も自動で
やってくれています。

ComputeShader を使うメリットは、GPU 毎の SDK の違いを吸収できることと
Direct3D との連携の容易さでしょう。

Structured Buffer や Append Buffer も試そうとしましたが、フラグ設定や
組み合わせが複雑でなかなかうまくいきませんでした。


関連エントリ
Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver


DirectX SDK November 2008 Technical Preview 5回目です。
WARP の試し方の補足と、D3D11 の大きな目玉のひとつである
「動的なシェーダーリンク」について。


● Direct3D 11 が Direct3D 10 より多くのハードで動く理由

  (1) Direct3D 9 世代の GPU に対応した
  (2) ソフトレンダラ WARP が追加された

(1) ShaderModel 2.0~3.0 でも API 自体は動きます。
ただし固定パイプライン用機能が存在しないため Shader 必須。
D3D11 になったからといって GPU の能力を超えて出来ることが増える訳じゃないので、
使えない機能多数。逆に使用できないステートなど制限が生じる可能性あり。

(2) GPU に必要な 3D 機能が無くても WARP を使えば CPU だけで動作します。
DirectX SDK November 2008 現在 10.1 相当で、Reference Driver よりずっと高速。


●手っ取り早く WARP を試す方法

install したサンプルの DXUT.cpp 2907行あたりに
pDeviceSettings->d3d11.DriverType= D3D_DRIVER_TYPE_WARP;
を挿入します。下記の赤い行。WARP Driver で起動するようになります。

// DXUT.cpp 2902行~
        if( GetDXUTState().GetOverrideForceREF() )
            pDeviceSettings->d3d11.DriverType = D3D_DRIVER_TYPE_REFERENCE;
        else if( GetDXUTState().GetOverrideForceHAL() )
            pDeviceSettings->d3d11.DriverType = D3D_DRIVER_TYPE_HARDWARE;

        pDeviceSettings->d3d11.DriverType= D3D_DRIVER_TYPE_WARP;


起動直後 = WARP
オプション画面から REFERENCE or HARDWARE に切り替え可能。(WARP には戻せない)

WARP device も FeatureLevel 10.1 なので、10.1 で動かないサンプルは動きません。
DirectX SDK November 2008 現在、動くのは MultithreadedRendering11 だけです。

実行速度は CPU 速度に依存します。


● Dynamic Shader Linkage

Direct3D 11 の大きな特徴の一つがこの Dynamic Shader Linkage です。

動的なリンクというと、複数のシェーダーバイナリをリンクし相互参照を解決する
イメージがありますが・・違います。

わかりやすく言えば「シェーダー内で関数ポインタが使えるようになった」ということ。

インスタンス自体は静的に生成されており、単一のシェーダーバイナリにすべての
インスタンス及びエントリポイントが含まれていなければなりません。

一つのシェーダープログラムの中に複数の関数を作成することが出来、その飛び先を
動的に切換えることができるわけです。

これらの仕組みは HLSL 上では class と interface の形で用いられます。

interface Base {
	float4	GetColor();
};

class Red : Base {
	float4	effect;
	float4	GetColor()
	{
		return	float4( 1, 0, 0, 1 );
	}
};

class Green : Base {
	float4	effect;
	float4	GetColor()
	{
		return	float4( 0, 1, 0, 1 );
	}
};

cbuffer ibuf {
	Red	cbRed;
	Green	cbGreen;
};

Base	color;	// この宣言はポインタ相当で実体を持たない

float4 main() : SV_Target
{
	return	color.GetColor();
}

上の例では Green と Red が interface Base を継承して作られています。
実行は Base で宣言された color を経由して行われており、どのメソッドが
呼び出されるのかコンパイル時にはわかりません。

Green 及び Red のインスタンスは cbRed, cbGreen として ConstantBuffer 上に
静的に作られています。外部の C++ 側からは、このインスタンス化された "cbRed"、
"cbGreen" を参照することが出来ます。

実際にどのインスタンスを color に割り当てるのか、レンダリング時に C++ 側で
いつでも切換えられるわけです。


シェーダー内のインスタンス参照には ID3D11ClassLinkage::GetClassInstance()
を使います。

ID3D11ClassLinkage* iClassLinkage;
iDevice->CreateClassLinkage( &iClassLinkage );
iDevice->CreatePixelShader( blob, size, iClassLinkage, &iPS );

ID3D11ClassInstance* iClassRed;
ID3D11ClassInstance* iClassGreen;
iClassLinkage->GetClassInstance( "cbRed", 0, &iClassRed );
iClassLinkage->GetClassInstance( "cbGreen", 0, &iClassGreen );

ID3D11DeviceContext::PSSetShader() で、シェーダーと同時に使用する
インスタンスのリストを渡します。

// Green を呼び出す場合
iContext->PSSetShader( iPS, &iClassGreen, 1 );


Shader 内には複数の interface が宣言されている可能性があるので、必ずしも
上のように単純になりません。Reflection を参照して、PSSetShader() に渡す
インスタンスリストの何番目がどの interface 呼び出しに対応するのか調べる
必要があります。

class に変数が含まれている場合、ConstantBuffer 内のメモリ配置との対応付けも
必要です。(この場合 Effect ではないのでバッファの作成も必要)

Dynamic Shader Linkage の仕組みは、このようにパフォーマンスに影響が出ないよう
慎重に作られているため扱いは必ずしも簡単ではないようです。
今後 Direct3D11 対応の Effect fx_5_0 が完成したらある程度自動で行ってくれる
ようになるかもしれません。


出力されたコードを見ると fcall 命令が使われています。label の値 (おそらく
オフセット) を用いてサブルーチン呼び出しを行う仕組みだと考えられます。

ps_5_0
dcl_globalFlags refactoringAllowed 
dcl_function_body fb0
dcl_function_body fb1
dcl_function_table ft0 = {fb0}
dcl_function_table ft1 = {fb1}
dcl_interface fp0[1][1] = {ft0, ft1}
dcl_output o0.xyzw
dcl_temps 1

fcall fp0[0][0]
mov o0.xy, r0.xyxx
mov o0.zw, l(0,0,0,1.000000)
ret 

label fb0
mov r0.xy, l(0,1.000000,0,0)
ret 

label fb1
mov r0.xy, l(1.000000,0,0,0)
ret

上記のように飛び先と種類はコンパイル時に決定しており、おそらく外部参照はできません。
このことは次の例を見てもわかります。

interface Base {
	float4	GetColor();
};

class Blue : Base {
	float4	GetColor()
	{
		return	float4( 0, 0, 1, 1 );
	}
};

Base	color;

float4 main() : SV_Target
{
	return	color.GetColor();
}

この場合 interface を利用しつつも、継承されたエントリが Blue しかありません。
驚くことに HLSL コンパイラは Blue 以外が決して呼ばれないことを認識し、
間接呼び出しを取り除きます。
つまり main の中には Blue::GetColor() がインライン展開されてしまいます。


class の宣言自体は ShaderModel 4.1 (FeatureLevel 10.1) 以前でも使えます。
ただし間接呼び出しは出来ないので、interface からの呼び出しはエラーとなります。
上の例だと「Base color;」を「Blue color;」に書き換えればコンパイル可能です。

ちなみに従来の 4.1 以前のシェーダーにもサブルーチン呼び出しの機能自体は存在
していました。しかしながら HLSL コンパイラはすべてをインライン展開し尽くすので
実際に使われることはほとんどありません。
自分が知る限り、実際に関数呼び出しのコードが出力されるのは switch 文の
attribute に [call] を指定した場合だけでした。

Direct3D11 になってようやくシェーダー内のサブルーチン呼び出しが意味を持つ
ようになりました。


関連エントリ
Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver


DirectX SDK November 2008 Technical Preview 4回目です。

●DirectX SDK November 2008 の隠れた変更 (D3D10)

以前作成した D3D10 用プログラムのコンパイルが通らなくなったため調べたところ
今回から D3DX10DisassembleEffect() が無くなってるようです。
Compiler 改良のため core API でなく D3DX になったのは良いのですが
仕様が変わるのは困ります。

D3D11 では HLSL コンパイラは D3DCompiler.h に統合され、バージョンに関係ない
共通 API に進化しているようです。blob 等の API は D3D10 のまま。
こちらの方に D3DX10DisassembleEffect() の後継らしき

D3DDisassemble10Effect()

がありました。でも D3DCompiler.h は d3d11shader.h を include しているので
D3D10 ではコンパイルが通りませんでした。


●FeatureLevel 一覧

Driver (device type) 毎の FeatureLevel を調べてみました。

・RADEON HD4850 (8.10)

HARDWARE  = 10.1 (a100)
REFERENCE = 11.0 (b000)
NULL      = 11.0 (b000)
WARP      = 10.1 (a100)


・GeForce GTX260 (178.24)

HARDWARE  = 10.0 (a000)
REFERENCE = 11.0 (b000)
NULL      = 11.0 (b000)
WARP      = 10.1 (a100)


・GeForce 7900GTX (178.24)

HARDWARE  =  9.3 (9300)
REFERENCE = 11.0 (b000)
NULL      = 11.0 (b000)
WARP      = 10.1 (a100)


・Intel 945/GMA950 (7.14.10.1461)

HARDWARE  =  9.3 (9300)
REFERENCE = 11.0 (b000)
NULL      = 11.0 (b000)
WARP      = 10.1 (a100)

予想通り、D3D_DRIVER_TYPE_HARDWARE の場合のみ異なっています。
Reference と Null Reference はどちらも 11.0 です。
WARP は現在 10.1 までの対応でした。

上記よりわかるのは GeForce でも WARP を使えば 10.1 のテストが出来ること。
また 11.0 の動作を確認するには現状 Reference を使うしかないようです。

それにしても D3D11 になって、テスト用に GeForce7900 を復活させることに
なるとは思いませんでした。

付属 Direct3D11 サンプルの MultithreadedRendering11 は GeForce7900GTX でも
動作しました。(Direct3D10 はいったい何だったのか)

ちなみにサンプルは WARP に対応していないので、FeatureLevel が合わない場合
強制的に Reference Driver になります。
これは DXUT の問題なので、WARP に対応させる場合は DXUT を書き換える必要が
あります。

EeePC 901 (Vista) の Intel 945 (ShaderModel2.0) もなぜか FeatureLevel 9.3 を
返します。MultithreadedRendering11 は動作しませんでした。
D3D11_FEATURE_THREADING が未サポートなためかもしれません。


● D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS

マニュアルに載ってない FEATURE オプションとして
D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS があるようです。
これは D3D10 デバイス (FeatureLevel 4.0 or 4.1) でも、FeatureLevel 11.0 相当の
機能を一部有している場合に用いられるようです。

前回 fxc のプロファイルで ComputeShader 4.0 , 4.1 が存在していることを指摘
しました。D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS はまさに、D3D10 デバイス
でも ComputeShader が使えるかどうか調べるために用いられます。

現在 D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS のメンバは
ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x
だけです。

10.1 デバイスである WARP も FALSE でした。
よって今現在 ComputeShader を試すには Reference しか無いようです。

その他調べた FueatureSupport は下記の通り。
Reference と Null Reference で CheckFeatureSupport() の結果が異なるのが
気になります。cpas とどこが違うんだろうという気にならなくもないです。

REFERENCE 11.0
 thread DriverConcurrentCreates= 0
 thread DriverCommandLists= 0
 double DoublePrecisionFloatShaderOps= 0
 ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x= 1

NULL REFERENCE 11.0
 thread DriverConcurrentCreates= 1
 thread DriverCommandLists= 1
 double DoublePrecisionFloatShaderOps= 1
 ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x= 1

WARP 10.1
 thread DriverConcurrentCreates= 0
 thread DriverCommandLists= 0
 double DoublePrecisionFloatShaderOps= 0
 ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x= 0

HARDWARE 9.3 (GeForce7900GTX)
 thread DriverConcurrentCreates= 0
 thread DriverCommandLists= 0
 double DoublePrecisionFloatShaderOps= 0
 ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x= 0

HARDWARE 10.1 (RADEON HD4850)
 thread DriverConcurrentCreates= 0
 thread DriverCommandLists= 0
 double DoublePrecisionFloatShaderOps= 0
 ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x= 0

10.0 / 10.1 ハードウエアも、将来ドライバの更新でこれらの対応状況が変化すると
思われます。


●新テクスチャ形式

マニュアルには記載されてませんが、D3D11 の新しいテクスチャ形式はすでに
DXGIFormat.h に追加されています。
増えたフォーマットは下記の通り。

DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM  = 89,
DXGI_FORMAT_B8G8R8A8_TYPELESS           = 90,
DXGI_FORMAT_B8G8R8A8_UNORM_SRGB         = 91,
DXGI_FORMAT_B8G8R8X8_TYPELESS           = 92,
DXGI_FORMAT_B8G8R8X8_UNORM_SRGB         = 93,
DXGI_FORMAT_BC6H_TYPELESS               = 94,
DXGI_FORMAT_BC6H_UF16                   = 95,
DXGI_FORMAT_BC6H_SF16                   = 96,
DXGI_FORMAT_BC7_TYPELESS                = 97,
DXGI_FORMAT_BC7_UNORM                   = 98,
DXGI_FORMAT_BC7_UNORM_SRGB              = 99,

lib に dxgi_beta.lib が存在しているので、おそらく dxgi.lib の代わりに
こちらをリンクすれば新フォーマットが使えそうです。

help 内の扱いが変わったことから予想してましたが、やはり DXGI は共通のままで
(DXGI2 のような) 新インターフェースに分岐しませんでした。
もしかしたら D3D10 でも使えるようになるのかもしれません。

追加された B8G8R8A8 (ARGB) 系は以前からある
DXGI_FORMAT_B8G8R8A8_UNORM = 87 の追加バリエーションとなります。

これ D3D10 の一般的なフォーマット R8G8B8A8 (ABGR) と逆順です。
Direct3D9 以前ではこっちの方が標準でした。
(D3D関連 DDSテクスチャの取り扱い)

仕様として隅に追いやられてたはずのフォーマットを正式な扱いに戻したのは、
やはり D3D9 ShaderModel3.0 以前の GPU に対応するために、必要だったからでは
ないでしょうか。

BC6/BC7 が新しい圧縮テクスチャ形式です。
詳細はまだわかりませんが、F16 が使われている BC6 の方が HDR 圧縮形式だと
考えられます。


Preview (beta) とはいえ、1つの SDK に 3世代のバージョンが同居しているのは
Direct3D で初めてです。
DXGI は D3D10 でも D3D11 でも共通に用いられますし、HLSL コンパイラは全世代で
共有されています。
アップデートの単位が徐々に細分化しているため、バージョンの区切りはこれから
ますます曖昧になっていくかもしれません。


関連エントリ
Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver

Direct3D11 シェーダーの動的リンクやテクスチャ
Direct3D11 マルチスレッドのための機能
Direct3D11 テセレータとシェーダー
Direct3D11 のパイプライン
Direct3D11 Compute Shader など
Gamefest2008 と Direct3D 11



引き続き DirectX SDK Novemver 2008 より、Direct3D 11 の Technical Preview です。
今まで取り上げたことがないもの中心。


●リソースサイズ

仕様として扱えるリソースの上限が拡張されています。
4GByte (32bit) を超えるリソースも扱えるらしく、内部的には 64bit で管理されて
いるようです。すでに VRAM 2GB の製品は存在しているので、4GB を超えるのも
時間の問題と思われます。

リソースサイズが 32bit を超えていても、データアクセス時の index は 32bit までに
制限されます。shader に double は追加されたものの 64bit int 型はまだ無いので
当然かもしれません。


●64bit 整数

HLSL では long/ulong 型が 64bit 整数となるようです。
double 対応前の HLSL コンパイラでは double 型を使うと float 相当の扱いでした。
(D3D10/DX10 シェーダーと64bit浮動少数)
新しい fxc で long/ulong を使うと未サポートエラーになります。

: error X3653: 'v': ps_5_0 does not support 64-bit integers
: error X3653: 'v': cs_5_0 does not support 64-bit integers


●fxc プロファイル

付属の fxc.exe は単一で、d3d9/d3d10/d3d11 共通になっています。
fxc の対応プロファイルを見ると、ComputeShader は ShaderModel 4.0/4.1 でも
使えそうです。

fx_2_0
fx_4_0
fx_4_1

cs_4_0  // ← ComputeShader 4.0?
cs_4_1  // ← ComputeShader 4.1?
cs_5_0

gs_4_0
gs_4_1 
gs_5_0

ds_5_0  // DomainShader

hs_5_0  // HullShader

ps_2_0
ps_2_a  // NVIDIA拡張
ps_2_b  // ATI拡張
ps_2_sw
ps_3_0
ps_3_sw
ps_4_0 
ps_4_0_level_9_1   // D3D11 の ShaderModel2.0
ps_4_0_level_9_3   // D3D11 の ShaderModel3.0
ps_4_1
ps_5_0

vs_1_1
vs_2_0 
vs_2_a  // NVIDIA拡張
vs_2_sw
vs_3_0
vs_3_sw
vs_4_0
vs_4_0_level_9_1   // D3D11 の ShaderModel2.0
vs_4_0_level_9_3   // D3D11 の ShaderModel3.0
vs_4_1
vs_5_0 

tx_1_0

前前回 FeatureLevel 9.1 と 9.2 の違いがわからないと書いたけど、これを見ると
本当にわかりません。


● RW Buffer

読み書き可能リソースが追加されています。
ついに、シェーダーが直接リソースに書き込みできるようになりました。
ストリーム以外の出力ができます。

RWBuffer
RWByteAddressBuffer
RWStructuredBuffer
RWTexture1D
RWTexture1DArray
RWTexture2D
RWTexture2DArray
RWTexture3D

書き込みできるのは PixelShader と ComputeShader のみ。
Load() メソッドではなく基本的に operator[] を使用します。
例えば Buffer では Load() と両方使えるものの RWBuffer は必ず operator[] を
使う必要があります。

Atomic 命令が使えるのは RWByteAddressBuffer だけで、こちらは逆に operator[] が
使えません。Load()/Store() や Interlocked 命令を使用します。

RWBuffer	a;
RWByteAddressBuffer	b;
Buffer		c;

float4
main() : SV_Target
{
	a[1]= 3;
	b.Store( 0, 4 );
	b.InterlockedAdd( 0, 1 );
	return	a[0]+ c.Load( 0 );
}

下記のようにコンパイルされました。(fxc /Tps_5_0)

ps_5_0
dcl_globalFlags refactoringAllowed 
dcl_resource_buffer (float,float,float,float) t0
dcl_uav_typed_buffer (float,float,float,float) u1
dcl_uav_raw u2
dcl_output o0.xyzw
dcl_temps 2
store_uav_typed u1.xyzw, l(1, 1, 1, 1), l(0x40400000, 0x40400000, 0x40400000, 0x40400000)
store_raw u2.x, l(0), l(4)
atomic_iadd u2, l(0), l(1)
ld_uav_typed r0.xyzw, l(0, 0, 0, 0), u1.xyzw
ld_indexable(buffer)(float,float,float,float) r1.xyzw, l(0, 0, 0, 0), t0.xyzw
add o0.xyzw, r0.xyzw, r1.xyzw
ret 

store_ 、atomic_ 命令が存在しておりやっと実感がわきます。
シェーダーが本当に書き込んでいます。

dcl_uav や ld_uav など、uav と付くのが読み書き可能な RW リソースを指している
ようです。t レジスタではなく u レジスタが使われています。
ちなみに 4.0 をターゲットにすると UAV をサポートしていないとのエラーが出ます。

// Resource Bindings:
//
// Name                   Type  Format         Dim Slot Elements
// ---------------- ---------- ------- ----------- ---- --------
// c                   texture  float4         buf    0        1
// a                       UAV  float4         buf    1        1
// b                       UAV    byte          rw    2        1

UAV は Unordered Access View (ID3D11UnorderedAccessView) の略だそうです。


● Append/Consume Buffer

RW Buffer や Atomic 命令だけでなく、もう少し便利な同期機能も追加されています。

AppendStructuredBuffer
AppendByteAddressBuffer
ConsumeStructuredBuffer
ConsumeByteAddressBuffer

Append がバッファの最後に追加、Consume がバッファの最後から取り出します。
LIFO として機能するようです。

RW Buffer と違い、読み込み専用、書き込み専用とそれぞれ個別に定義します。
送信側と受け側を明確にし、同期を単純化しているものと思われます。


使用可能なのはやはり PixelShader と ComputeShader のみ。
マニュアルでは一部全シェーダーで使えるかのような表記もありますがおそらく間違いでしょう。

使い方がよくわからなかったのでコンパイルのみ試したところ、PixelShader だとたまに
Internal Compiler Error が出るようです。
やはり主な用途は ComputeShader だと思われます。

ConsumeStructuredBuffer	c;
AppendStructuredBuffer	a;

[numthreads(1,1,1)]
void main()
{
	uint	t= c.Consume();
	a.Append( t );
}

コンパイルした結果は下記の通りです。(fxc /Tcs_5_0)

cs_5_0
dcl_globalFlags refactoringAllowed 
dcl_uav_structured u0, 4
dcl_uav_structured u1, 4
dcl_temps 1
dcl_thread_group 1, 1, 1
imm_atomic_consume r0.x, u0
imm_atomic_alloc r0.y, u1
ld_structured r0.x, r0.x, l(0), u0.x
store_structured u1.x, r0.y, l(0), r0.x
ret 

imm_atomic_consume / imm_atomic_alloc は、このペアを使って load や store を
行うと、atomic な操作でかつバッファサイズも増減することを意味しているようです。
例えば uint を float4 に変更すると次の通り。

ConsumeStructuredBuffer	c;
AppendStructuredBuffer	a;

[numthreads(1,1,1)]
void main()
{
	float4	t= c.Consume();
	a.Append( t );
}


cs_5_0
dcl_globalFlags refactoringAllowed 
dcl_uav_structured u0, 16
dcl_uav_structured u1, 16
dcl_temps 2
dcl_thread_group 1, 1, 1
imm_atomic_consume r0.x, u0
ld_structured r0.y, r0.x, l(0), u0.x
ld_structured r0.z, r0.x, l(4), u0.x
ld_structured r0.w, r0.x, l(8), u0.x
ld_structured r0.x, r0.x, l(12), u0.x
imm_atomic_alloc r1.x, u1
store_structured u1.x, r1.x, l(0), r0.y
store_structured u1.x, r1.x, l(4), r0.z
store_structured u1.x, r1.x, l(8), r0.w
store_structured u1.x, r1.x, l(12), r0.x
ret 

Append / Consume 操作は必ずスカラー単位で行われているようです。
これだけだと、float4 の間に他の Append/Consume が割り込んでしまいそうな
気がしますが、offset も同時に指定しているし、まだよくわかりません。

// Name                   Type  Format         Dim Slot Elements
// ---------------- ---------- ------- ----------- ---- --------
// c                       UAV  struct     consume    0        1
// a                       UAV  struct      append    1        1

バッファとしては UAV の一種で、Dim が consume 及び append となっています。


関連エントリ
Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
Direct3D11 Technical Preview D3D11の互換性、WARP Driver

Direct3D11 シェーダーの動的リンクやテクスチャ
Direct3D11 マルチスレッドのための機能
Direct3D11 テセレータとシェーダー
Direct3D11 のパイプライン
Direct3D11 Compute Shader など
Gamefest2008 と Direct3D 11


Direct3D11 (DirectX11) はマルチスレッドを意識した設計が行われています。
この場合のスレッドとは CPU 側の話で、複数 core CPU が効率よく D3D API を
使用できるようになっています。

D3D10 以前のスレッド対応機能は、複数のスレッドから呼び出してもとりあえず
問題が起こらない、というレベルのものでした。
ハードウエアスレッドでの速度効率はあまり考えられていなかったといえます。

D3D11 では次の点が改良されています。

・描画やステート変更などの命令発行を複数持てる。CommandList と呼ばれる。
 Push バッファをコンテキスト毎に複数持てる。(deferred context)

・描画スレッド以外でも GPU リソースを作成できる。

従来は単一のコマンドバッファに API 単位で同期しつつアクセスが行われていた
ためスレッド間でブロックします。D3D11 はバッファリングで不要なブロッキングと
コンテキストの矛盾を回避します。

個別の CommandList (Display List) の実行には新しい GPU やドライバの機能を
必要としますが、命令の記録を API でエミュレートできるため、低い FeatureLevel
でも動作するようです。エミュレートは実行時にメモリコピーが発生する可能性が
あるため、single core CPU の場合はおそらくあまりメリットがないと思われます。
実際 D3D11_CREATE_DEVICE_SINGLETHREADED を指定すると
CreateDeferredContext() は失敗するようです。


これらの機能に対応するため D3D の Device Interface は Context が分離されました。

(1) ID3D11Device
(2) ID3D11DeviceContext

(1) の ID3D11Device 自体は Create 命令だらけになっています。
描画やステート変更は (2) の ID3D11DeviceContext に移動しました。

言ってしまえば、スレッドセーフでシェア可能な API とスレッドローカルな API に
分かれたわけです。
ID3D11Device を使ったリソースの作成はスレッドセーフであり特別な管理は不要です。
ID3D11DeviceContext は API 呼び出しを行うスレッド毎に所有する必要があります。

ID3D11DeviceContext の生成には次の命令を使います。

ID3D11Device::GetImmediateContext()
ID3D11Device::CreateDeferredContext()

ImmediateContext は従来の API 呼び出しと同等で、プッシュバッファに記録された
命令は必要に応じて kick されます。
DeferredContext の呼び出しは CommandList (ID3D11CommandList) に記録され、
ExecuteCommandList() を実行するまで保留します。
ExecuteCommandList() の呼び出しは、ImmediateContext() が行います。

●描画メインスレッド
 1. GetImmediateContext() で作成した ID3D11DeviceContext を使う。
 2. 他のスレッドが作成した CommandList を ExecuteCommandList() する。

●サブスレッド
 1. CreateDeferredContext() で作成した ID3D11DeviceContext を使用する。
 2. FinishCommandList() で ID3D11CommandList を受け取る。


シェーダーの種類も増えたので、ID3D11DeviceContext の命令も増えています。

CS~ CoputeShader
DS~ DomainShader
GS~ GeometryShader
HS~ HullShader
IA~ InputAssembler
OM~ OutputManager
PS~ PixelShader
RS~ Rasterizer
VS~ VertexShader
SO~ StreamOutput
OM~ OutputManager

HS DS GS VS PS の 6種類のシェーダーそれぞれに次の 8命令が用意されています。

~GetConstantBuffers()
~SetConstantBuffers()
~GetSamplers()
~SetSamplers()
~GetShader()
~SetShader()
~GetShaderResource()
~SetShaderResource()

CS のみ CSGetUnorderdAccessViews()/CSSetUnorderdAccessViews() の特殊な
2命令があります。
これだけで 6 x 8 + 2 = 50命令


関連エントリ
Direct3D11 Technical Preview D3D11の互換性、WARP Driver



DirectX SDK Novemver 2008 には DirectX11 (D3D11) の beta SDK が含まれています。
Direct3D11 API の機能や詳細が明らかになってきました。

Direct3D10 で大幅に変更された API の路線は継承されていますが
さらに現実的な改良がいくつも施されています。
中でも驚いたのが、D3D10 の時よりも間口を広げたこと。

手持ちの RADEON HD4850 でも D3D11 サンプルが動作しました。
(Windows Vista x64 + HD4850 CATALYST 8.10)
サンプルによっては、Reference でなくアクセラレートが利いているものもあります。
D3D9 GPU では一切動かなかった D3D10 とはえらい違いです。
信じられないくらい。


■D3D9 → D3D10 の場合

・Preview SDK が出ても当時は Vista 発売前だったため Reference Driver ですら
 動かなかった。
・D3D10 専用 GPU (ShaderModel 4.0 以上) が無いと一切ハードウエアアクセラレータ
 が利かなかった。(Reference のみ)


■D3D10 → D3D11 の場合

・Vista 上なら旧ビデオカードでも Reference Device で動く。
・D3D10 GPU でも D3D11 API が使えた。Feature Level が制限されるだけで API
 自体は置き換えられる。未対応の機能さえ使わなければアクセラレートされるらしい。
・さらに WARP Driver (WARP Device) が使える。(詳細は後述)


D3D9 以前は CAPS 情報があったため、旧 GPU でも使えない機能が判別できました。
D3D10 はデバイスの性能を特定する仕組みが無く、D3D10 の全機能が必須となり
 旧 GPU は切り捨てられました。
D3D10.1 では FeatureLevel が導入され、10.0 と同居可能となりました。
D3D11 では同じように FeatureLevel で各 GPU のレベルを判別します。

Feature Level
  11.0 = ShaderModel 5.0
  10.1 = ShaderModel 4.1
  10.0 = ShaderModel 4.0
   9.3 = ShaderModel 3.0
   9.2 = ShaderModel 2.0
   9.1 = ShaderModel 2.0

上記 FeatureLevel の定義を見ると、D3D10 GPU どころか D3D9 世代、
ShaderModel 2.0~3.0 にも対応しているように見えます。
もしこれが本当なら、Direct3D10 よりも Direct3D11 の方が対応 GPU の範囲が広い
ことになります。
Direct3D10 は動かないけど Direct3D11 なら動く可能性があるということ。

Direct3D9 の固定パイプライン(HW T&L) が使えないだけで、従来の D3D9Ex も
D3D11 API に統合可能なのかもしれません。

互換性を保った API の置き換えは D3D9 以前の DirectX では当たり前だったため、
元に戻っただけともいえます。
それでも D3D10 の仕様を考えると非常に大きな路線変更です。

Direct3D10 へ移行するのはものすごい骨が折れるし敷居も高かったので良い傾向です。

FeatureLevel 9.2 と 9.1 の違いは不明です。
ShaderModel は vs3.0/vs2.0 ではなく、vs_4_0_level_9_1 等 4.0 の制限版と
なるようです。


●Device Type (Driver Type)

Device Type が増えました。
目新しいのは Null Reference Device と WARP Device です。

・Hardware Device (HAL)
・Null Reference Device
・Reference Device
・Software Device
・WARP Device


Null Reference Device は Reference Device 同様ソフトウエア動作しますが
レンダリング機能が無いようです。API 呼び出しだけソフトウエアでシミュレート
することで、現実的な速度でデバッグを行うのが目的のようです。

WARP Device (Windows Advanced Rasterizer) は高速なソフトウエアラスタライザ
のようです。Multi Core CPU が当たり前になり GPU との差が埋まりつつあるため、
ソフトウエアラスタライザが復活といったところでしょうか。
WARP Device は D3D11/D3D10.1 の置き換えが出来るそうです。
詳しい説明がないため中身はわかりませんが、RAMP とか MMX RGB Driver が存在
していた D3D5~D3D6 あたりを思い出します。

実際に D3D_DRIVER_TYPE_WARP を使ってみたところ、D3D10WARP_beta.dll が
読み込まれているのを確認しました。
Dual Core CPU で試したためあまり参考にならないかもしれませんが、確かに動作は
REFERENCE Driver より高速でした。
Reference で 0.3fps 程度のサンプルが、数fps 程度には向上しています。
Quad 以上ならもっと効果が顕著だと思われます。

これから CPU の core 数は増加していきます。例えば Core i7 は Quad + HT で
ハードウエア 8 スレッドらしいので、今後はさらに期待できそうです。


DirectX11 に関しては下記関連エントリも参考にしてください。


関連エントリ
Direct3D11 シェーダーの動的リンクやテクスチャ
Direct3D11 マルチスレッドのための機能
Direct3D11 テセレータとシェーダー
Direct3D11 のパイプライン
Direct3D11 Compute Shader など
Gamefest2008 と Direct3D 11



リリースされたようです。

DirectX SDK November 2008
DirectX Software Development Kit

予告通り Direct3D11 の Technical Preview が含まれています。