前回作成した三角形を表示するプログラムを修正してテクスチャマッピングを行ってみます。
※2016/01/24 一部のPCで実行できない不具合を修正致しました。
テクスチャマッピングと言っても,d3d12になってもやることは変わりません。テクスチャを読み込んで,リソースのオブジェクトを作って読み込んだピクセルデータを書き込んで,シェーダに渡すという感じです。今回のサンプルでは,UpdateSubresource()という関数を作って,サブリソースを更新するようにしました。
さて,まずはテクスチャのローダーがいります。何を使ってもよいと思いますが,d3d11の時に自前で作ったものがあるので,今回のサンプルでは自前のプログラムを流用します。
どれを使ってもらってもよいと思うのですが,今回のサンプルでは上記のD3D11_TgaLoaderのプログラムを使用して,tgaファイルを読み込みすることにしました。
まずは,ローダーを利用してテクスチャを読み込みます。
// TGAファイルを検索.
std::wstring path;
if ( !SearchFilePath( L"res/sample32bitRLE.tga", path ) )
{
ELOG( "Error : File Not Found." );
return false;
}
// TGAファイルをロード.
asdx::ResTGA tga;
if ( !tga.Load( path.c_str() ) )
{
ELOG( "Error : Targa File Load Failed." );
return false;
}
続いては,リソースオブジェクトを生成します。
// ヒーププロパティの設定.
D3D12_HEAP_PROPERTIES prop;
prop.Type = D3D12_HEAP_TYPE_DEFAULT;
prop.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
prop.CreationNodeMask = 1;
prop.VisibleNodeMask = 1;
// リソースの設定.
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Width = tga.GetWidth();
desc.Height = tga.GetHeight();
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
// リソースを生成.
hr = m_pDevice->CreateCommittedResource(
&prop,
D3D12_HEAP_FLAG_NONE,
&desc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(m_pTexture.GetAddress()) );
if ( FAILED( hr ) )
{
ELOG( "Error : ID3D12Device::CreateCommittedResource() Failed." );
return false;
}
次は,サブリソースの更新を行います。
// サブリソースデータ設定.
D3D12_SUBRESOURCE_DATA subRes;
subRes.pData = tga.GetPixels();
subRes.RowPitch = tga.GetWidth() * tga.GetBitPerPixel() / 8;
subRes.SlicePitch = subRes.RowPitch * tga.GetHeight();
// サブリソースの更新.
auto ret = UpdateSubresources(
m_pCmdList.GetPtr(),
m_pCmdQueue.GetPtr(),
m_pFence.GetPtr(),
m_FenceEvent,
m_pTexture.GetPtr(),
0,
1,
&subRes);
if ( !ret )
{
ELOG( "Error : UdpateSubresources() Failed." );
return false;
}
あとは,シェーダリソースビュー生成用にディスクリプタヒープが必要になるのですが,SetDescritorHeap()で,定数バッファビューとシェーダリソースビューのヒープを別々に設定することができないので,定数バッファビューとシェーダリソースビューのディスクリプタヒープは共用にします。…といっても単に NumDescriptors の数を増やすだけです
// CBV・SRV・UAV用ディスクリプターヒープを生成.
{
D3D12_DESCRIPTOR_HEAP_DESC desc = {};
desc.NumDescriptors = 2; // 定数バッファとシェーダリソースビューを作るので2つ。
desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
hr = m_pDevice->CreateDescriptorHeap( &desc, IID_PPV_ARGS(m_pCbvSrvHeap.GetAddress()) );
if ( FAILED( hr ) )
{
ELOG( "Error : ID3D12Device::CreateDescriptorHeap() Failed." );
return false;
}
}
続いて,シェーダリソースビューを生成しておきます。
// シェーダリソースビューの設定.
D3D12_SHADER_RESOURCE_VIEW_DESC viewDesc = {};
viewDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
viewDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
viewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
viewDesc.Texture2D.MipLevels = 1;
viewDesc.Texture2D.MostDetailedMip = 0;
// シェーダリソースビューを生成.
D3D12_CPU_DESCRIPTOR_HANDLE handle = m_pCbvSrvHeap->GetCPUDescriptorHandleForHeapStart();
handle.ptr += m_CbvSrvDescriptorSize;
m_pDevice->CreateShaderResourceView( m_pTexture.GetPtr(), &viewDesc, handle );
今回のサンプルでは,RGBAの4チャンネルあるテクスチャを使用するので,Shader4ComponentMapping に D3D12_DEFAULT_4_COMPONENT_MAPPING を設定しています。 もし,R成分のみの1チャンネルのテクスチャが使いたいとか,RG2チャンネルのテクスチャが使いたいとか出てくる場合は,Shader4ComponentMappingを MSDNのリファレンス を参考に設定すると良いと思います。
テクスチャマッピングを行うためにシェーダファイルも修正しておきます。ピクセルシェーダは下記のような感じにしました。
//-------------------------------------------------------------------------------------------------
// ピクセルシェーダのメインエントリーポイントです.
//-------------------------------------------------------------------------------------------------
PSOutput PSFunc(const VSOutput input)
{
PSOutput output = (PSOutput)0;
output.Color = input.Color * ColorTexture.Sample( ColorSmp, input.TexCoord );
return output;
}
単純にテクスチャフェッチするだけの面白くないシェーダです。
さて,上記のシェーダに合うようにテクスチャレジスタの0番とサンプラーステートレジスタの0番目にシェーダリソースビューと,サンプラステートを設定する処理を行います。これは次のようにルートシグニチャを生成する際に設定しておきます。
// ルートシグニチャを生成.
{
// ディスクリプタレンジの設定.
D3D12_DESCRIPTOR_RANGE range[2];
range[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
range[0].NumDescriptors = 1;
range[0].BaseShaderRegister = 0;
range[0].RegisterSpace = 0;
range[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
range[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
range[1].NumDescriptors = 1;
range[1].BaseShaderRegister = 0;
range[1].RegisterSpace = 0;
range[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
// ルートパラメータの設定.
D3D12_ROOT_PARAMETER param[2];
param[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
param[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;
param[0].DescriptorTable.NumDescriptorRanges = 1;
param[0].DescriptorTable.pDescriptorRanges = &range[0];
param[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
param[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
param[1].DescriptorTable.NumDescriptorRanges = 1;
param[1].DescriptorTable.pDescriptorRanges = &range[1];
// 静的サンプラーの設定.
D3D12_STATIC_SAMPLER_DESC sampler = {};
sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
sampler.MipLODBias = 0;
sampler.MaxAnisotropy = 0;
sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
sampler.MinLOD = 0.0f;
sampler.MaxLOD = D3D12_FLOAT32_MAX;
sampler.ShaderRegister = 0;
sampler.RegisterSpace = 0;
sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
// ルートシグニチャの設定.
D3D12_ROOT_SIGNATURE_DESC desc;
desc.NumParameters = _countof(param);
desc.pParameters = param;
desc.NumStaticSamplers = 1;
desc.pStaticSamplers = &sampler;
desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
asdx::RefPtr<ID3DBlob> pSignature;
asdx::RefPtr<ID3DBlob> pError;
// シリアライズする.
hr = D3D12SerializeRootSignature(
&desc,
D3D_ROOT_SIGNATURE_VERSION_1,
pSignature.GetAddress(),
pError.GetAddress() );
if ( FAILED( hr ) )
{
ELOG( "Error : D3D12SerializeRootSignataure() Failed." );
return false;
}
// ルートシグニチャを生成.
hr = m_pDevice->CreateRootSignature(
0,
pSignature->GetBufferPointer(),
pSignature->GetBufferSize(),
IID_PPV_ARGS(m_pRootSignature.GetAddress()) );
if ( FAILED( hr ) )
{
ELOG( "Error : ID3D12Device::CreateRootSignature() Failed." );
return false;
}
}
前回と異なるのは,D3D12_DESRIPTOR_RANGE と D3D12_ROOT_PARAMTER の数が増えて,シェーダリソースビュー用の設定が追加されているのと,スタティックサンプラーの設定が追加されている点です。あとは,ルートシグニチャのFlagsにアクセス制限用のフラグを立てていたのですが,めんどっちいので外しました。初期化部分の処理はこれで終わりです。
あとは,描画処理ですね。
前回は定数バッファを設定するだけだったので,ディスクリプタテーブルは1つ設定するだけよかったのですが,今回はシェーダリソースビュー分が追加されているので2つ設定するかたちになります。
// ディスクリプタヒープを設定.
m_pCmdList->SetDescriptorHeaps( 1, m_pCbvSrvHeap.GetAddress() );
// ルートシグニチャを設定.
m_pCmdList->SetGraphicsRootSignature( m_pRootSignature.GetPtr() );
// ディスクリプタヒープテーブルを設定.
auto handleCBV = m_pCbvSrvHeap->GetGPUDescriptorHandleForHeapStart();
auto handleSRV = m_pCbvSrvHeap->GetGPUDescriptorHandleForHeapStart();
handleSRV.ptr += m_CbvSrvDescriptorSize;
m_pCmdList->SetGraphicsRootDescriptorTable( 0, handleCBV );
m_pCmdList->SetGraphicsRootDescriptorTable( 1, handleSRV );
あとはこの設定で,ポリゴンを描画すればテクスチャが表示されると思います。
このままサクッと終われれば良かったのですが,一か所サラっと流した箇所がありましたね。そうです,サブリソースの更新処理です。
このサブリソースの更新処理がまた面倒なので,まずは処理の流れをざっくり説明して,そのあとに実際の処理を見ていくことにしましょう。
上記の流れを見るだけでも面倒そうなのが伝わりますかね。コードを見るともっと面倒くさいです。
アプリ側で呼び出しているの下記のメソッドです。
bool UpdateSubresources
(
ID3D12GraphicsCommandList* pList,
ID3D12CommandQueue* pQueue,
ID3D12Fence* pFence,
HANDLE handle,
ID3D12Resource* pResource,
UINT firstSubResource,
UINT subResourceCount,
D3D12_SUBRESOURCE_DATA* pSrcData
)
{
if ( pList == nullptr ||
pQueue == nullptr ||
pFence == nullptr ||
pResource == nullptr ||
pSrcData == nullptr )
{
return false;
}
asdx::RefPtr<ID3D12Device> device;
pResource->GetDevice( IID_PPV_ARGS( device.GetAddress() ));
asdx::RefPtr<ID3D12Resource> intermediate;
{
// アップロード用リソースを生成.
D3D12_RESOURCE_DESC uploadDesc = {
D3D12_RESOURCE_DIMENSION_BUFFER,
0,
GetRequiredIntermediateSize( pResource, firstSubResource, subResourceCount ),
1,
1,
1,
DXGI_FORMAT_UNKNOWN,
{ 1, 0 },
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
D3D12_RESOURCE_FLAG_NONE
};
D3D12_HEAP_PROPERTIES props = {
D3D12_HEAP_TYPE_UPLOAD,
D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
D3D12_MEMORY_POOL_UNKNOWN,
1,
1
};
auto hr = device->CreateCommittedResource(
&props,
D3D12_HEAP_FLAG_NONE,
&uploadDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(intermediate.GetAddress()));
if ( FAILED( hr ) )
{
return false;
}
}
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = pResource;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
pList->ResourceBarrier( 1, &barrier );
UpdateSubresources(
pList,
pResource,
intermediate.GetPtr(),
0,
firstSubResource,
subResourceCount,
pSrcData );
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ;
pList->ResourceBarrier( 1, &barrier );
pList->Close();
auto list = reinterpret_cast<ID3D12CommandList*>(pList);
pQueue->ExecuteCommandLists(1, &list);
auto fence = 1;
auto hr = pQueue->Signal( pFence, fence );
if ( FAILED(hr) )
{ return false; }
if ( pFence->GetCompletedValue() < fence )
{
hr = pFence->SetEventOnCompletion( fence, handle );
if ( FAILED(hr) )
{ return false; }
WaitForSingleObject( handle, INFINITE );
}
return true;
}
まずは,アップロード用の中間リソースを生成します。この中間リソースは線形なバッファとして生成することに注意してください。
そのため,D3D12_RESOURCE_DIMENSION_BUFFER, D3D12_TEXTURE_LAYOUT_RAW_MAJOR, D3D12_HEAP_TYPE_UPLOAD等が設定されています。あとこのバッファを作成するのに必要なバッファサイズはGetRequiredIntermediateSize()で取得しています。このメソッドの中身は下記のようになっています。
//-------------------------------------------------------------------------------------------------
// データのアップロードに必要なサイズを取得します.
//-------------------------------------------------------------------------------------------------
UINT64 GetRequiredIntermediateSize
(
ID3D12Resource* pDstResource,
UINT firstSubresource,
UINT subResourceCount
)
{
auto desc = pDstResource->GetDesc();
UINT64 result = 0;
ID3D12Device* pDevice;
pDstResource->GetDevice( IID_PPV_ARGS( &pDevice ) );
assert( pDevice != nullptr );
pDevice->GetCopyableFootprints(
&desc,
firstSubresource,
subResourceCount,
0,
nullptr,
nullptr,
nullptr,
&result );
pDevice->Release();
return result;
}
こうして中間リソースを生成した後は,サブリソースの更新のためにリソースバリアを張っておきます。Transition.SataAfterはD3D12_RESOURCE_STATE_COPY_DESTにしておきます。
続いて287行目で呼び出しているUpdateSubresources()メソッドの処理ですが,下記のようになります。
//-------------------------------------------------------------------------------------------------
// サブリソースを更新します.
//-------------------------------------------------------------------------------------------------
UINT64 UpdateSubresources
(
ID3D12GraphicsCommandList* pCmdList,
ID3D12Resource* pDstResource,
ID3D12Resource* pIntermediate,
UINT64 intermediateOffset,
UINT firstSubresource,
UINT subResourceCount,
D3D12_SUBRESOURCE_DATA* pSrcData
)
{
UINT64 requiredSize = 0;
auto bufferSize = static_cast<UINT64>(
sizeof(D3D12_PLACED_SUBRESOURCE_FOOTPRINT) +
sizeof(UINT) +
sizeof(UINT64) ) * subResourceCount;
if ( bufferSize > SIZE_MAX )
{ return 0; }
auto buffer = HeapAlloc(GetProcessHeap(), 0, static_cast<SIZE_T>(bufferSize));
if (buffer == nullptr)
{ return 0; }
auto pLayouts = reinterpret_cast<D3D12_PLACED_SUBRESOURCE_FOOTPRINT*>(buffer);
auto pRowSizesInBytes = reinterpret_cast<UINT64*>(pLayouts + subResourceCount);
auto pRowCounts = reinterpret_cast<UINT*>(pRowSizesInBytes + subResourceCount);
auto desc = pDstResource->GetDesc();
ID3D12Device* pDevice;
pDstResource->GetDevice( IID_PPV_ARGS( &pDevice ) );
pDevice->GetCopyableFootprints(
&desc,
firstSubresource,
subResourceCount,
intermediateOffset,
pLayouts,
pRowCounts,
pRowSizesInBytes,
&requiredSize);
pDevice->Release();
auto result = UpdateSubresources(
pCmdList,
pDstResource,
pIntermediate,
firstSubresource,
subResourceCount,
requiredSize,
pLayouts,
pRowCounts,
pRowSizesInBytes,
pSrcData);
HeapFree(GetProcessHeap(), 0, buffer);
return result;
}
このメソッドではD3D12_PLACED_SUBRESOURCE_FOOTPRINTを生成して,201行目で呼び出すUpdateSubresources()に流しています。この201行目で呼び出しているメソッドの処理は下記のようになります。
//-------------------------------------------------------------------------------------------------
// サブリソースを更新します.
//-------------------------------------------------------------------------------------------------
inline UINT64 UpdateSubresources
(
ID3D12GraphicsCommandList* pCmdList,
ID3D12Resource* pDstResource,
ID3D12Resource* pIntermediate,
UINT firstSubresource,
UINT subResourceCount,
UINT64 requiredSize,
const D3D12_PLACED_SUBRESOURCE_FOOTPRINT* pLayouts,
const UINT* pRowCounts,
const UINT64* pRowSizesInBytes,
const D3D12_SUBRESOURCE_DATA* pSrcData
)
{
auto imdDesc = pIntermediate->GetDesc();
auto dstDesc = pDstResource->GetDesc();
if ( imdDesc.Dimension != D3D12_RESOURCE_DIMENSION_BUFFER ||
imdDesc.Width < requiredSize + pLayouts[0].Offset ||
requiredSize > SIZE_T(-1) ||
(dstDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER && (firstSubresource != 0 || subResourceCount != 1)) )
{ return 0; }
BYTE* pData;
auto hr = pIntermediate->Map(0, nullptr, reinterpret_cast<void**>(&pData));
if ( FAILED( hr ) )
{ return 0; }
for ( UINT i=0; i <subResourceCount; ++i )
{
if ( pRowSizesInBytes[i] > SIZE_T(-1) )
{ return 0; }
D3D12_MEMCPY_DEST dstData = {
pData + pLayouts[i].Offset,
pLayouts[i].Footprint.RowPitch,
pLayouts[i].Footprint.RowPitch * pRowCounts[i]
};
CopySubresource(
&dstData,
&pSrcData[i],
SIZE_T(pRowSizesInBytes[i]),
pRowCounts[i],
pLayouts[i].Footprint.Depth );
}
pIntermediate->Unmap(0, nullptr);
if ( dstDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER )
{
D3D12_BOX srcBox;
srcBox.left = UINT( pLayouts[0].Offset );
srcBox.right = UINT( pLayouts[0].Offset + pLayouts[0].Footprint.Width );
srcBox.top = 0;
srcBox.front = 0;
srcBox.bottom = 1;
srcBox.back = 1;
pCmdList->CopyBufferRegion(
pDstResource,
0,
pIntermediate,
pLayouts[0].Offset,
pLayouts[0].Footprint.Width );
}
else
{
for ( UINT i=0; i<subResourceCount; ++i )
{
D3D12_TEXTURE_COPY_LOCATION dst;
D3D12_TEXTURE_COPY_LOCATION src;
dst.pResource = pDstResource;
dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dst.SubresourceIndex = i + firstSubresource;
src.pResource = pIntermediate;
src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
src.PlacedFootprint = pLayouts[i];
pCmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
}
}
return requiredSize;
}
上記のメソッド内で呼び出しているCopySubresource()は下記のように単なるmemcpy処理です。
//-------------------------------------------------------------------------------------------------
// サブリソースをコピーします.
//-------------------------------------------------------------------------------------------------
void CopySubresource
(
const D3D12_MEMCPY_DEST* pDst,
const D3D12_SUBRESOURCE_DATA* pSrc,
SIZE_T rowSizeInBytes,
UINT rowCounts,
UINT sliceCount
)
{
for ( UINT z=0; z <sliceCount; ++z )
{
auto pDstSlice = reinterpret_cast<BYTE*>(pDst->pData) + pDst->SlicePitch * z;
const auto pSrcSlice = reinterpret_cast<const BYTE*>(pSrc->pData) + pSrc->SlicePitch * z;
for ( UINT y=0; y<rowCounts; ++y )
{
memcpy(pDstSlice + pDst->RowPitch * y,
pSrcSlice + pSrc->RowPitch * y,
rowSizeInBytes );
}
}
}
見てもらうと分かるようにだいぶ面倒ですね。それと同時にこのような面倒な処理を至る所でD3D11は隠蔽してくれていたので,「そら重いわ!」と納得もできますね。
上記のような処理を毎回アプリを作るたびに書くのはやってられないと思うので,この部分はライブラリ化して使いまわせるようにしておくと良いと思います。
そんなわけで,一応サブリソースの更新について説明しました。
今回は簡単なテクスチャマッピングを取り扱ってみました。
次回は,バンドルを取り扱ってみようかなと思っています。
本ソースコードおよびプログラムはMIT Licenseに準じます。
プログラムの作成にはMicrosoft Visual Studio Community 2015, 及び Windows SDK 10.0.10240.0を用いています。
D3D12_Texture