こんばんわ。
Pocolです。
WriteBufferImmediate()を使って,DREDもどきを実装するサンプルプログラムを書きました。
https://github.com/ProjectAsura/D3D12Samples/tree/master/D3D12_CustomDRED
実装としては,以前に説明したようにVirtualAlloc()でパンくず用のメモリを作り,これをID3D12Device3::OpenExistingHeapFromAddress()に渡して,ID3D12Heapを作ります。
作った,ヒープからCreatePlacedResource()でID3D12Resourceを作り,GetGPUVirtualAddress()して,WriteBufferImmediate()で書き込むためのベースアドレスを取得します。
bool Init(ID3D12Device3* pDevice)
{
if (pDevice == nullptr)
{
ELOG("Error : Invalid Argument");
return false;
}
m_BufferSize = sizeof(D3D12_AUTO_BREADCRUMB_OP) * UINT16_MAX;
m_pAddress = VirtualAlloc(nullptr, m_BufferSize, MEM_COMMIT, PAGE_READWRITE);
if (m_pAddress == nullptr)
{
ELOG("Error : Out of Memory");
return false;
}
auto hr = pDevice->OpenExistingHeapFromAddress(m_pAddress, IID_PPV_ARGS(&m_pHeap));
if (FAILED(hr))
{
ELOG("Error : ID3D12Device3::OpenExistingHeapFromAddress() Failed. errcode = 0x%x", hr);
return false;
}
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Alignment = 0;
desc.Width = m_BufferSize;
desc.Height = 1;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = DXGI_FORMAT_UNKNOWN;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER;
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST;
hr = pDevice->CreatePlacedResource(m_pHeap, 0, &desc, state, nullptr, IID_PPV_ARGS(&m_pBuffer));
if (FAILED(hr))
{
ELOG("Error : ID3D12Device::CreatePlacedResource() Failed. errcode = 0x%x", hr);
return false;
}
m_GpuAddress = m_pBuffer->GetGPUVirtualAddress();
m_CurrIndex = 0;
return true;
}
あとは,コマンドの前後にWriteBufferImmediate()を呼び出して,コールされたかどうかをフラグを書き込みます。
void Push(ID3D12GraphicsCommandList2* pCmdList, D3D12_AUTO_BREADCRUMB_OP op)
{
Marker marker = {};
marker.Op = op;
marker.In = 1;
marker.Out = 0;
D3D12_WRITEBUFFERIMMEDIATE_PARAMETER param = {};
param.Dest = m_GpuAddress + sizeof(D3D12_AUTO_BREADCRUMB_OP) * m_CurrIndex;
param.Value = bit_cast<UINT>(marker);
auto mode = D3D12_WRITEBUFFERIMMEDIATE_MODE_MARKER_IN;
pCmdList->WriteBufferImmediate(1, ¶m, &mode);
m_CurrentOp = op;
}
void Pop(ID3D12GraphicsCommandList2* pCmdList)
{
Marker marker = {};
marker.Op = m_CurrentOp;
marker.In = 0;
marker.Out = 1;
D3D12_WRITEBUFFERIMMEDIATE_PARAMETER param = {};
param.Dest = m_GpuAddress + sizeof(D3D12_AUTO_BREADCRUMB_OP) * m_CurrIndex;
param.Value = bit_cast<UINT>(marker);
auto mode = D3D12_WRITEBUFFERIMMEDIATE_MODE_MARKER_OUT;
pCmdList->WriteBufferImmediate(1, ¶m, &mode);
m_CurrIndex++;
}
Push()とPop()挟み方は次のような感じ。
void DrawInstanced(
_In_ UINT VertexCountPerInstance,
_In_ UINT InstanceCount,
_In_ UINT StartVertexLocation,
_In_ UINT StartInstanceLocation) override
{
Push(m_pCmdList, D3D12_AUTO_BREADCRUMB_OP_DRAWINSTANCED);
m_pCmdList->DrawInstanced(VertexCountPerInstance, InstanceCount, StartVertexLocation, StartInstanceLocation);
Pop(m_pCmdList);
}
GPUクラッシュが発生したら,VirtualAllocで確保したメモリを見に行って,どこまで書き込みが終わっているかをチェックすれば,DREDもどきが出来ると思います。
void Print()
{
auto marker = reinterpret_cast<Marker*>(m_pAddress);
if (marker == nullptr)
return;
ILOG("BreadcrumbCount : %u", m_LastIndex);
for(auto i=0; i<m_LastIndex; ++i)
{
char state[3] = " ";
if (marker[i].Out)
{
state[0] = 'O';
state[1] = 'K';
}
else if (marker[i].In)
{
state[0] = 'N';
state[1] = 'G';
}
uint32_t opIndex = marker[i].Op;
ILOG("%04u : [%s] Op = %s", i, state, g_BreadcrumbTags[opIndex]);
}
}
というわけで,DREDもどきの実装でした。
PageFaultとかも頑張ろうかなと思ったのですが,MP切れなのと,BindlessResource(DynamicResource)でのアクセスが検知できないので,諦めました。
このあたりはNVIDIA AftermathとかAMD Radeon GPU Dectective使える環境ならそっちを使った方が良いです。