まずは右も左もわからない状況なので,DirectXのサンプルにあるBasicCompute11を,少しいじってみるところから始めてみます。
今回は,地味なコンソールアプリです。



【コンピュートシェーダの利点】 ・出力リソースの任意の位置に書き込み可能。 ・「データ共有」や「スレッド間同期」のメカニズム。 ・指定した数の「スレッド」を明示的に起動でき,パフォーマンスを最適化できる。 ・描画パイプラインと無関係なので,コードの保守が簡単。
StructuredBuffer<MyStruct> InputBuffer : register( t0 ); RWStructuredBuffer<MyStruct> OutputBuffer : register( u0 );ちなみに他の書き方も色々とできますが,とりあえず構造化バッファを使うコードにしてみました。
00125: // バッファにデータを格納.
00126: for( int i=0; i<NUM_ELEMENTS; ++i )
00127: {
00128: g_Buf0[ i ].s32 = i;
00129: g_Buf0[ i ].f32 = static_cast<float>( i ) * 0.25f;
00130:
00131: g_Buf1[ i ].s32 = i;
00132: g_Buf1[ i ].f32 = static_cast<float>( i ) * 0.75f;
00133: }
00134:
00135:
00136: // 構造化バッファを生成.
00137: hr = CreateStructuredBuffer( g_pDevice, sizeof(BufType), NUM_ELEMENTS, &g_Buf0[ 0 ], &g_pBuf0 );
00138: assert( SUCCEEDED( hr ) );
00139: hr = CreateStructuredBuffer( g_pDevice, sizeof(BufType), NUM_ELEMENTS, &g_Buf1[ 0 ], &g_pBuf1 );
00140: assert( SUCCEEDED( hr ) );
00141: hr = CreateStructuredBuffer( g_pDevice, sizeof(BufType), NUM_ELEMENTS, nullptr, &g_pBufResult );
00142: assert( SUCCEEDED( hr ) );
00143:
00144:
00145: // 入出力用のビューを生成.
00146: hr = CreateBufferSRV( g_pDevice, g_pBuf0, &g_pBufSRV0 );
00147: assert( SUCCEEDED( hr ) );
00148: hr = CreateBufferSRV( g_pDevice, g_pBuf1, &g_pBufSRV1 );
00149: assert( SUCCEEDED( hr ) );
00150: hr = CreateBufferUAV( g_pDevice, g_pBufResult, &g_pBufUAV );
00151: assert( SUCCEEDED( hr ) );
テクスチャ2Dを作る → シェーダリソースビューを作る…という流れでしたが,それと同じように
構造化バッファを作る → シェーダリソースビューを作る…という流れになっています。
00358: //---------------------------------------------------------------------------------------------
00359: // 構造化バッファを生成します.
00360: // ※ 頂点バッファやインデックスバッファとしては使用不可。
00361: //---------------------------------------------------------------------------------------------
00362: HRESULT CreateStructuredBuffer
00363: (
00364: ID3D11Device* pDevice,
00365: UINT elementSize,
00366: UINT count,
00367: void* pInitData,
00368: ID3D11Buffer** ppBufferOut
00369: )
00370: {
00371: (*ppBufferOut) = nullptr;
00372:
00373: D3D11_BUFFER_DESC desc;
00374: memset( &desc, 0, sizeof(desc) );
00375:
00376: desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
00377: desc.ByteWidth = elementSize * count;
00378: desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
00379: desc.StructureByteStride = elementSize;
00380:
00381: if ( pInitData )
00382: {
00383: D3D11_SUBRESOURCE_DATA initData;
00384: initData.pSysMem = pInitData;
00385:
00386: return pDevice->CreateBuffer( &desc, &initData, ppBufferOut );
00387: }
00388:
00389: return pDevice->CreateBuffer( &desc, nullptr, ppBufferOut );
00390: }
00391:
続いて,シェーダリソースビューの生成処理です。
00419: //---------------------------------------------------------------------------------------------
00420: // シェーダリソースビューを生成します.
00421: //---------------------------------------------------------------------------------------------
00422: HRESULT CreateBufferSRV( ID3D11Device* pDevice, ID3D11Buffer* pBuffer, ID3D11ShaderResourceView** ppSRVOut )
00423: {
00424: D3D11_BUFFER_DESC desc;
00425: memset( &desc, 0, sizeof(desc) );
00426: pBuffer->GetDesc( &desc );
00427:
00428: D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
00429: memset( &srvDesc, 0, sizeof(srvDesc) );
00430:
00431: srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
00432: srvDesc.BufferEx.FirstElement = 0;
00433:
00434: if ( desc.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS )
00435: {
00436: srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
00437: srvDesc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW;
00438: srvDesc.BufferEx.NumElements = desc.ByteWidth / 4;
00439: }
00440: else if ( desc.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_STRUCTURED )
00441: {
00442: srvDesc.Format = DXGI_FORMAT_UNKNOWN;
00443: srvDesc.BufferEx.NumElements = desc.ByteWidth / desc.StructureByteStride;
00444: }
00445: else
00446: {
00447: return E_INVALIDARG;
00448: }
00449:
00450: return pDevice->CreateShaderResourceView( pBuffer, &srvDesc, ppSRVOut );
00451: }
構造化バッファの場合は,構造体の中身は当然ながらユーザーが決めるので,DXGI_FORMATにはないフォーマットになったりすると思います。構造化バッファの場合はDXGI_FORMAT_UNKNOWNをしてしておきましょう。
00453: //---------------------------------------------------------------------------------------------
00454: // アンオーダードアクセスビューを生成します.
00455: //---------------------------------------------------------------------------------------------
00456: HRESULT CreateBufferUAV( ID3D11Device* pDevice, ID3D11Buffer* pBuffer, ID3D11UnorderedAccessView** ppUAVOut )
00457: {
00458: D3D11_BUFFER_DESC desc;
00459: memset( &desc, 0, sizeof(desc) );
00460: pBuffer->GetDesc( &desc );
00461:
00462: D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
00463: memset( &uavDesc, 0, sizeof(uavDesc ) );
00464:
00465: uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
00466: uavDesc.Buffer.FirstElement = 0;
00467:
00468: if ( desc.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS )
00469: {
00470: uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
00471: uavDesc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
00472: uavDesc.Buffer.NumElements = desc.ByteWidth / 4;
00473: }
00474: else if ( desc.MiscFlags & D3D11_RESOURCE_MISC_BUFFER_STRUCTURED )
00475: {
00476: uavDesc.Format = DXGI_FORMAT_UNKNOWN;
00477: uavDesc.Buffer.NumElements = desc.ByteWidth / desc.StructureByteStride;
00478: }
00479: else
00480: {
00481: return E_INVALIDARG;
00482: }
00483:
00484: return pDevice->CreateUnorderedAccessView( pBuffer, &uavDesc, ppUAVOut );
00485: }
見て分かるように,ほとんどシェーダリソースビューの生成と変わらない感じです。
00154: // コンピュートシェーダを走らせる.
00155: ID3D11ShaderResourceView* pSRVs[ 2 ] = { g_pBufSRV0, g_pBufSRV1 };
00156: RunComputeShader( g_pContext, g_pCS, 2, pSRVs, nullptr, nullptr, 0, g_pBufUAV, 32, 1, 1 );
RunComputerShader()メソッドの実装ですが,下記のようになっています。
00513: //----------------------------------------------------------------------------------------------
00514: // コンピュートシェーダを実行します.
00515: //----------------------------------------------------------------------------------------------
00516: void RunComputeShader
00517: (
00518: ID3D11DeviceContext* pContext,
00519: ID3D11ComputeShader* pComputeShader,
00520: UINT numViews,
00521: ID3D11ShaderResourceView** pSRVs,
00522: ID3D11Buffer* pCBCS,
00523: void* pCSData,
00524: DWORD numDataBytes,
00525: ID3D11UnorderedAccessView* pUAV,
00526: UINT x,
00527: UINT y,
00528: UINT z
00529: )
00530: {
00531: pContext->CSSetShader( pComputeShader, nullptr, 0 );
00532: pContext->CSSetShaderResources( 0, numViews, pSRVs );
00533: pContext->CSSetUnorderedAccessViews( 0, 1, &pUAV, nullptr );
00534:
00535: if ( pCBCS )
00536: {
00537: D3D11_MAPPED_SUBRESOURCE res;
00538:
00539: pContext->Map( pCBCS, 0, D3D11_MAP_WRITE_DISCARD, 0, &res );
00540: memcpy( res.pData, pCSData, numDataBytes );
00541: pContext->Unmap( pCBCS, 0 );
00542:
00543: ID3D11Buffer* ppCB[ 1 ] = { pCBCS };
00544: pContext->CSSetConstantBuffers( 0, 1, ppCB );
00545: }
00546:
00547: pContext->Dispatch( x, y, z );
00548:
00549: ID3D11UnorderedAccessView* pNullUAVs[ 1 ] = { nullptr };
00550: ID3D11ShaderResourceView* pNullSRVs[ 2 ] = { nullptr, nullptr };
00551: ID3D11Buffer* pNullCBs [ 1 ] = { nullptr };
00552:
00553: pContext->CSSetShader( nullptr, nullptr, 0 );
00554: pContext->CSSetUnorderedAccessViews( 0, 1, pNullUAVs, nullptr );
00555: pContext->CSSetShaderResources( 0, 2, pNullSRVs );
00556: pContext->CSSetConstantBuffers( 0, 1, pNullCBs );
00557: }
いつもはコマンドの発行ににID3DDeviceContext::Draw()メソッドとかID3DDeviceContext::DrawIndexed()メソッドなどを使っているのですが,コンピュートシェーダではDraw()メソッドなどの代わりにDispatch()メソッドあるいはDispatchIndirect()メソッドを使用します。
00037: #define size_x 32
00038: #define size_y 1
00039: #define size_z 1
00040:
00041:
00042: //------------------------------------------------------------------------------------
00043: // コンピュートシェーダのメインエントリーポイントです.
00044: //------------------------------------------------------------------------------------
00045: [numthreads(size_x, size_y, size_z)]
00046: void CSFunc( const CSInput input )
00047: {
00048: int index = input.dispatch.x;
00049:
00050: // 適当に演算させてみる.
00051: BufOut[ index ].i = BufIn0[ index ].i + BufIn1[ index ].i;
00052: BufOut[ index ].f = BufIn0[ index ].f * BufIn1[ index ].f;
00053: }
このスレッド数ですが,まだコンピュートシェーダを使いはじめた状態なので,正直どういう場合にはどういう数値がいいかなどのノウハウがまだないです(だから誰か教えて!w)。今回のサンプルは超いい加減なので,サイズ1次元しか使っていないです。
00488: //---------------------------------------------------------------------------------------------
00489: // バッファを生成し,内容をコピーします.
00490: //---------------------------------------------------------------------------------------------
00491: ID3D11Buffer* CreateAndCopyToBuffer( ID3D11Device* pDevice, ID3D11DeviceContext* pContext, ID3D11Buffer* pBuffer )
00492: {
00493: ID3D11Buffer* pCloneBuf = nullptr;
00494:
00495: D3D11_BUFFER_DESC desc;
00496: memset( &desc, 0, sizeof(desc) );
00497:
00498: pBuffer->GetDesc( &desc );
00499: desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
00500: desc.Usage = D3D11_USAGE_STAGING;
00501: desc.BindFlags = 0;
00502: desc.MiscFlags = 0;
00503:
00504: if ( SUCCEEDED( pDevice->CreateBuffer( &desc, nullptr, &pCloneBuf ) ) )
00505: {
00506: pContext->CopyResource( pCloneBuf, pBuffer );
00507: }
00508:
00509: return pCloneBuf;
00510: }
次に,CPUアクセスの仕方ですが,Map(), Unmap()を使って地味にアクセスしていきます。今回のサンプルではCPUでGPUで計算しているのと同じ処理を行って,CPUの演算結果とGPUの演算結果を比較します。当然ながら,浮動小数点計算は「==」とか「!=」で比較せず,誤差範囲内に入っているかどうかでチェックすべきなのですが,面倒なので「==」とか「!=」使っちゃっています。ちゃんとした実装をするときには,この実装のままだと絶対に一致しないので気を付けましょう。
00159: // GPU からの計算結果を読み戻して,CPUで計算した結果と同じであるか検証する.
00160: {
00161: // バッファを生成とコピー.
00162: ID3D11Buffer* pBufDbg = CreateAndCopyToBuffer( g_pDevice, g_pContext, g_pBufResult );
00163:
00164: D3D11_MAPPED_SUBRESOURCE subRes;
00165: BufType* pBufType;
00166:
00167: // マップ.
00168: hr = g_pContext->Map( pBufDbg, 0, D3D11_MAP_READ, 0, &subRes );
00169: assert( SUCCEEDED( hr ) );
00170:
00171: pBufType = (BufType*)subRes.pData;
00172:
00173: ILOG( "Verifying against CPU result..." );
00174: bool isSuccess = true;
00175:
00176: for( int i=0; i<NUM_ELEMENTS; ++i )
00177: {
00178: // CPUで演算.
00179: int value0 = g_Buf0[i].s32 + g_Buf1[i].s32;
00180: float value1 = g_Buf0[i].f32 * g_Buf1[i].f32;
00181:
00182: // GPUの演算結果とCPUの演算結果を比較.
00183: if ( ( pBufType[i].s32 != value0 )
00184: || ( pBufType[i].f32 != value1 ) )
00185: {
00186: ILOG( "Failure." );
00187: ILOG( " index = %d", i );
00188: ILOG( " cpu value0 = %d, value1 = %f", value0, value1 );
00189: ILOG( " gpu value0 = %d, value1 = %f", pBufType[i].s32, pBufType[i].f32 );
00190: isSuccess = false;
00191: break;
00192: }
00193: }
00194:
00195: // CPUとGPUの結果がすべて一致したら成功のログを出力.
00196: if ( isSuccess )
00197: { ILOG( "Succeded!!" ); }
00198:
00199: // アンマップ.
00200: g_pContext->Unmap( pBufDbg, 0 );
00201:
00202: // 解放処理.
00203: ASDX_RELEASE( pBufDbg );
00204: }
今回は,コンピュートシェーダを軽く触ってみました。今回の内容では,実用できるレベルに至らないと思うのであと数回かけて基本を押さえていくことにします。