Share via


HDRToneMappingCS11 サンプル

このサンプルでは、2008 年 11 月のリリースからいくつかの重要な項目が更新されています。詳細については、こちらを参照してください

この Direct3D 11 サンプルでは、Direct3D 11 の優れた新機能であるコンピュート シェーダー (Compute Shader、以下 CS と呼びます) をセットアップして実行する方法を示します。サンプルでは、HDR トーン マッピングの実装にのみ CS を利用していますが、その概念は他のポストプロセッシング アルゴリズムや、より一般的な演算にも簡単に適用できます。

Ee416569.d3d11_sample_HDRToneMappingCS11(ja-jp,VS.85).jpg

パス

ソース SDK ルート\Samples\C++\Direct3D11\HDRToneMappingCS11
実行可能ファイル SDK ルート\Samples\C++\Direct3D11\Bin\x86 または x64\HDRToneMappingCS11.exe

サンプルの概要

このサンプルでは、CS を使用して高速並列リダクションを画像上で実行して平均照度を計算し、その照度を使用してトーン マッピングを実行する方法を示します。Direct3D 11 ハードウェアが実際に使用可能になるまでは、サンプルはソフトウェア リファレンス デバイスでのみ動作します。しかしながら、今すぐにでも CS の試用を開始することを開発者にお勧めします。CS を使用してポストプロセッシングやより一般的な演算を実行すると、従来の "画面クワッドをレンダリングしてから、ピクセル シェーダーを使用して演算する" 方法と比べて少なくとも次のような利点が得られます。

  1. CS は、出力リソースの任意の位置への書き込みが可能です (これは、スキャッタリング機能としても知られています)。この機能により、より一般的で複雑なアルゴリズムをグラフィック ハードウェアに実装できるようになります。

  2. CS は、データを共有したり、スレッド間の実行を同期するためのメカニズムを提供します。これらの機能により、特にカーネルやリダクション演算を必要とするアルゴリズムで、余分なバッファーやテクスチャー フェッチ操作を大幅に減らすことができます。

  3. Direct3D 11 には、GPU で既知の数の CS スレッドを明示的に起動するための専用 API があります。これにより、アルゴリズムで最適な数のスレッドを実行できると共に、メモリー アクセスやレジスタの使用が予測可能になります。その結果、パフォーマンスが向上し、最適化の機会が高まります。

  4. CS は、グラフィックス パイプラインの特定のステージにはバインドされておらず、一連のステートを独自に持ちます。つまり、レンダリング パイプラインとポストプロセッシング ユニットは切り離されており、コードの保守がより簡単になります。

次のセクションでは、CS をセットアップして実行する方法を詳しく説明します。また、その後のセクションでは、並列リダクション アルゴリズムについて簡単に説明します。

コンピュート シェーダーのセットアップ

CS を使用するには、まず入出力のためのリソースとリソース ビューをセットアップする必要があります。入力については、CS はすべてのリソース タイプ (Texture1D{Array}、Texcture2D{Array}、Texture3D、Buffer など) を受け入れます。これらの形式は、他のシェーダー ステージでも既に使用されています。これらのリソースを入力として CS にバインドする場合も特に違いはありません。サンプルから抜粋した次のコードでは、Texcture2D およびそのシェーダー リソース ビューを作成し、それを入力として CS にバインドしています。

// Create the render target texture// Our skybox will be rendered to this texture,// and then this texture will be the input to the Compute Shader to calculate the luminanceD3D11_TEXTURE2D_DESC Desc;ZeroMemory( &Desc, sizeof( D3D11_TEXTURE2D_DESC ) );Desc.ArraySize = 1;Desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;Desc.Usage = D3D11_USAGE_DEFAULT;Desc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;Desc.Width = pBackBufferSurfaceDesc->Width;Desc.Height = pBackBufferSurfaceDesc->Height;Desc.MipLevels = 1;Desc.SampleDesc.Count = 1;V_RETURN( pd3dDevice->CreateTexture2D( &Desc, NULL, &g_pTexRender11 ) );// Create the resource view, this is used to bind the texture above as input to the Compute ShaderD3D11_SHADER_RESOURCE_VIEW_DESC DescRV;DescRV.Format = Desc.Format;DescRV.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;DescRV.Texture2D.MipLevels = 1;DescRV.Texture2D.MostDetailedMip = 0;V_RETURN( pd3dDevice->CreateShaderResourceView( g_pTexRender11, &DescRV, &g_pTexRenderRV11 ) );// ...// For CS inputID3D11ShaderResourceView* aRViews[ 1 ] = { g_pTexRenderRV11 };pd3dImmediateContext->CSSetShaderResources( 0, 1, aRViews );        

出力については、CS が出力データを書き込むリソースに UAV (Unordered Access Viewの略語、Direct3D 11 で導入された新しいビュー タイプ) を作成します。UAV に使用可能なリソースを作成するには、リソースの作成時に D3D11_BIND_UNORDERED_ACCESS フラグを追加します。次に、サンプルから抜粋したコード例を示します。

// Create two Texture1D for ping-ponging in the reduction operation used for calculating luminanceD3D11_TEXTURE1D_DESC Desc1D;ZeroMemory( &Desc1D, sizeof( D3D11_TEXTURE1D_DESC ) );Desc1D.ArraySize = 1;Desc1D.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;Desc1D.Usage = D3D11_USAGE_DEFAULT;Desc1D.Format = DXGI_FORMAT_R32_FLOAT;Desc1D.Width = int(ceil(pBackBufferSurfaceDesc->Width / 16.0f) * ceil(pBackBufferSurfaceDesc->Height / 16.0f));Desc1D.MipLevels = 1;V_RETURN( pd3dDevice->CreateTexture1D( &Desc1D, NULL, &g_pTex1DReduction0 ) );V_RETURN( pd3dDevice->CreateTexture1D( &Desc1D, NULL, &g_pTex1DReduction1 ) );      

上記で作成した 2 つの Texture1D リソースは、(並列リダクション時のピンポンで) CS の入出力としても使用されます。そのため、リソースの .BindFlags として D3D11_BIND_SHADER_RESOURCE および D3D11_BIND_UNORDERED_ACCESS の両方を指定します。その後、次のようにリソースに UAV を作成します。

// Create UAV on the above two Texture1D objectD3D11_UNORDERED_ACCESS_VIEW_DESC DescUAV;DescUAV.Format = Desc1D.Format;DescUAV.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE1D;DescUAV.Texture1D.MipSlice = 0;V_RETURN( pd3dDevice->CreateUnorderedAccessView( g_pTex1DReduction0, &DescUAV, &g_pReductionUAView0 ) );V_RETURN( pd3dDevice->CreateUnorderedAccessView( g_pTex1DReduction1, &DescUAV, &g_pReductionUAView1 ) );      

これで、作成した UAV を使用して Texture1D を出力リソースとしてバインドできます。

// For CS outputID3D11UnorderedAccessView* aUAViews[ 1 ] = { g_pReductionUAView0 };pd3dImmediateContext->CSSetUnorderedAccessViews( 0, 1, aUAViews, (UINT*)(&aUAViews) );      

CS コード内で、新しい読み書き可能なオブジェクト タイプを使用して出力を宣言し、それを角かっこでインデックス付けします。

RWTexture1D<float> Result : register( u0 );// ...Result[Gid.y*g_param.x+Gid.x] = accum[0] / groupthreads;      

このように、CS ではスキャッタリングがサポートされています。スキャッタリングを使用すると、CS のどのスレッドでも読み書き可能オブジェクトの任意の位置に書き込むことが可能になります。

入出力の設定が完了したら、CS を実行します。このためには、次のように ID3D11DeviceContext::Dispatch を呼び出します。

// Run the CSpd3dImmediateContext->Dispatch( dimx, dimy, 1 );      

上記の呼出しによって、副次的に dimx*dimy*1 個の CS スレッド グループが GPU でディスパッチされます。各スレッド グループには、[numthreads(x, y, z)] 個 (CS コード内で定義) のスレッドが含まれます。つまり、dimx*dimy*1*x*y*z 個のスレッドが並列して実行されます。各スレッドは、CS シェーダー コードで確認できる 3 つのシステム値 (SV_GroupThreadIDSV_GroupID、および SV_DispatchThreadID) のうちの 1 つまたは複数個を使用することによって自己同定が可能です。これらのシステム値は、一般に入力または出力リソースにインデックス付けするために使用されます。

同じスレッド グループ内のスレッド (つまり、同じ SV_GroupID を持つスレッド) は、相互にデータを共有できます。この新しい機能をうまく利用することで、I/O 負荷を低減することが必要です。一般的なアプローチの 1 つでは、入力リソースからデータを読み取り、そのデータを共有メモリーに保存した後で利用します。共有メモリーは実際のレジスタを使用して実装されるため、非常に高速になります。

多くのポストプロセッシング アルゴリズムには類似点があります。それは、単一ピクセルを出力するために、入力内のいくつかの隣接するピクセルへのアクセスを必要とする点です。従来のアプローチでは、各出力ピクセルに隣接するこれらのピクセルのテクスチャー フェッチやバッファー フェッチを利用するため、余分な I/O 読み取りが多く発生します。各出力ピクセルに対して、入力内の隣接するピクセルの領域がオーバーラップすることになります。オーバーラップする領域内のピクセルは、複数回読み取られます。このサンプル (ReduceTo1DCS.hlsl ファイルの CSMain) で使用しているリダクションによるアプローチと、HDRFormats10 サンプル (HDRFormats10.fx ファイルの DownScale2x2_Lum) で使用しているアプローチを比較すると、共有メモリーの使用によって I/O がいかに低減されるかが確認できます。

CS を使用した並列リダクション アルゴリズム

数学的には、画像の照度の計算は非常に単純です。単に画像のすべてのピクセルの照度を加算し、合計値をピクセルの数で割るだけです。ただし、並列処理して高いパフォーマンスを得るには、いくつかの工夫が必要です。それには、2 つの手順を実行します。まず、16x16 のタイルを使用して画像を 1D テクスチャーに変換し、次に最終的な結果が得られるまで 1D テクスチャーを繰り返し縮小します。最初の手順を下図に示します。これは、ReduceTo1DCS.hlsl で CS が実行している処理です。詳細については、次の段落で説明します。

Ee416569.d3d11_sample_HDRToneMappingCS11_1(ja-jp,VS.85).png

サイズが W*H のレンダリングされた画像があると仮定します。そこで、各スレッド グループに 16*16 個のスレッドが含まれた ceil(W/16.0f)*ceil(H/16.0f) 個のスレッド グループを実行します。上の図では、1 つの小さな青いタイルが、1 個のスレッド グループによって処理されるピクセルを示しています。各スレッド グループは、まず対象のタイル全体に含まれるピクセルの照度をグループ共有メモリーに読み込みます。次に、そのスレッド グループ内のスレッドは、並列リダクションの ADD 演算をグループ共有メモリーで実行します。リダクションの ADD 演算が完了すると、タイル内のすべてのピクセル照度の合計値が、グループ共有メモリーの最初の要素に格納されます。続いて、スレッド グループの最初のスレッドが、その合計値をタイル内のピクセルの正確な数で除算してタイルの平均照度を求め、それを結果の tex1D に書き込みます。画像の境界上にないタイルには、ちょうど 16*16=256 ピクセルが含まれています。一方、境界上の (灰色で示した) タイルに含まれているピクセルの数は、それより少ない可能性があります (W または H が 16 で割り切れない場合)。これに対処する方法については、コードを参照してください。

最初の手順が完了すると、すべてのタイルの平均照度を含む 1D テクスチャーが得られます。次に、これを縮小して 1D テクスチャーに格納されているすべての値の平均を計算し、レンダリングされた画像の照度を求めます。このプロセスは、上記で行ったプロセスと似ています。これは、ReduceToSingleCS.hlsl で CS が実行している処理です。

Ee416569.d3d11_sample_HDRToneMappingCS11_2(ja-jp,VS.85).png

N 個の要素が含まれた入力 1D テクスチャーがあると仮定します。そこで、各スレッド グループに 128 個のスレッドが含まれた ceil(N/128.0f) 個のスレッド グループを実行します。上の図では、各スレッド グループが、入力内の中かっこでグループ化された要素をすべて処理し、各要素グループの単一の平均値を出力します。その方法は、前述の手順と同じです。グループ化された要素をグループ共有メモリーに読み込んで並列 ADD リダクションを実行し、その合計値をグループに含まれている要素の正確な数で除算します。

上記のすべての手順を実行したら、結果の出力 tex1D を入力として使用します。そして、結果の tex1D の要素が 1 つになるまでこの演算を繰り返します。これが、最終的なレンダリングされた画像照度となります。

また、上記のリダクション アルゴリズムは、CS コードをほんの少し変更するだけで、各ピクセルの最大値、最小値、積を計算する場合にも使用できます。

最後のパスでは、HDR トーン マッピングを実際に行います。それについては、特に説明しません。ここでは単に、CS でバック バッファーのコンテンツを直接変更する方法を紹介するために CS を使用しています。

更新項目 (2009 年 3 月)

  • このサンプルは CS4.0 を使用して再実装されました。それにより、既存の D3D10 クラス ハードウェア上で実行することが可能になりました (以前は D3D11 クラス ハードウェアを必要とする CS5.0 を使用していたため、D3D11 ハードウェアが提供されるまではリファレンス デバイスでのみ実行可能でした)。

  • HDR トーン マッピングに加えて、ブラーリングとブルーミングも提供されます。

  • これら 3 つのポストプロセッシング エフェクトはすべて、2 つのパスでそれぞれ実装されています。比較しやすくするために、一方では CS4.0 を使用し、もう一方では従来のピクセル シェーダー メソッドを使用しています。

CS4.x およびポストプロセッシング

CS4.x にはテクスチャーに対して直接書き込みを行う機能がないため、次のようにする必要があります。

  • 上記のリダクション演算で、1D テクスチャーの代わりに構造化バッファーを中間結果として使用します。

  • 2D テクスチャーに直接出力する代わりに、(ブラーリングとブルーミングで使用する) 分離可能なコンボリューションの 2 つのパスから構造化バッファーに画像出力を並べて表示します。

  • もう 1 つのレンダリング パスを追加して、最後のコンポジション パスでサンプリングできるように、構造化バッファーに格納した画像データを 2D テクスチャーに戻す変換を行います。

  • ピクセル シェーダーを使用して、最後のコンポジション パスでバック バッファーを更新します。

とはいえ、次の理由から CS4.x はポストプロセッシングの優れた機能であると言えます。

  • 使用するポストプロセッシング アルゴリズムで、HDR トーン マッピングやヒストグラムなどのパス間の中間結果としてテクスチャー リソースを必要としない限り、ここで紹介したパフォーマンスが CS4.x によって得られることもあります。

  • 複数のパスや中間テクスチャー データが必要な場合、これらのアルゴリズムでの CS4.x の実装は (上記の余分なデータ変換のために) 低速になる可能性がありますが、将来の CS5.0 アルゴリズムのプロトタイピングとテストを実行するための優れたプラットフォームとなります。CS4.x は CS5.0 の中核的なサブセットであり、コンピュート シェーダー アーキテクチャによって得られる主要な利点の多くを理解するのに役立ちます。また、CS4.x では、現在利用できるハードウェアでその機能を使用することができます。その一例として、画像コンボリューションがあります。

コンボリューションでの共有メモリーの利用

ピクセル シェーダーでコンボリューションを実行する場合、出力ピクセルごとに入力テクスチャー内の隣接するピクセルが常に読み取られます。その後、加重値で乗算および合計されて結果値が求められます。この演算では、特にコンボリューション カーネルのサイズが大きくなるほど、余分な読み取りが多く発生してしまいます。

ただし、コンボリューションをコンピュート シェーダーで実行する場合は、共有メモリーを使用してスレッド グループ内のスレッドの数と同数のピクセルを読み込んで (また、複数のスレッド グループを同時に実行して、入力画像の複数のチャンクを処理することもできる)、共有メモリーで効率的にカーネル コンボリューション演算を実行します。共有メモリーは通常、ハードウェア ベンダーによってオンチップ キャッシュの一部またはオンチップ レジスタとして実装されるため、非常に高速にアクセスできます。この利点は、共有メモリー アーキテクチャによって提供される、大きなキャッシュの動作を明示的にプログラムするための機能だと考えることができます。これにより、使用するアルゴリズムの明確なアクセス パターンがわかるので、とても理想的なヒット率が得られます。

Ee416569.d3d11_sample_HDRToneMappingCS11_3(ja-jp,VS.85).png

このスキームでは、共有メモリー内の各ピクセルがテクスチャー読み取りによって入力画像から読み込まれるのは 1 回だけであり、結果のピクセルの計算時には常に隣接する入力ピクセルが共有メモリー内にあります。この方法の詳細については、FilterCS.hlsl を参照してください。