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( 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 ( mapres.pData ); printf( "%d\n", ptr[0] ); iContext->Unmap( iBufferC2, 0 );
下記のシェーダーを実行すると結果は「360」。(45 x 2 x 4)
Windows や Present() とか無しにシェーダーが走りました。
// cs.hlsl RWBuffera; [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