DREDもどき

こんばんわ。
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, &param, &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, &param, &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使える環境ならそっちを使った方が良いです。