DRED を使った D3D12 の GPU デバッグ手法

DRED を使った D3D12 の GPU デバッグ手法

0.はじめに
 はじめまして。研究開発室に所属する川口です。普段はリアルタイムレンダリング技術の研究開発を行っています。
 今回は DirectX 12 のクラッシュ追跡に利用できる DRED という機能について紹介したいと思います。なおこの記事は CEDEC 2020 の NVIDIA 竹重様の講演およびその補完資料を大いに参考にさせていたいただいております。
https://cedec.cesa.or.jp/2020/session/detail/s5e6f80234bee4.html
https://shikihuiku.github.io/post/cedec2020_prescriptions_for_deviceremoval/

 DirectX の開発中に Device Removed というエラーに遭遇して原因究明に非常に手間取った経験のある人は多いと思います。この Device Removed とは GPU やドライバーで発生したエラーが原因で、アプリケーションの実行を継続できなくなったときに現れるメッセージです。
 GPU の応答が一定時間無かった時に発生する TDR (Timeout Detection and Recovery) と、GPU がリソースにアクセスできないといった致命的な問題が要因のエラーの二種類に大別されます。
 TDR については TdrLimit というレジストリキーの設定で制限時間を変更することもできます。根本的な問題解決には繋がりませんが、単に非常に重い処理なのか、あるいは何かしらのエラーで GPU が応答していないのかといった問題の切り分けには役立ちます。
https://devblogs.microsoft.com/pix/tdr-debugging/

 Device Removed は GPU で発生するものなので、エラーが検出された時点での CPU の処理とは時間差があり、メッセージ発生時の処理を追っても原因の究明は困難です。

1. D3DConfig について
 Device Removed を追う前に、デバッグに必須のツールである D3DConfig について紹介します。D3DConfig は DirectX のデバッグ設定を行う機能です。デバッグ設定とは DirectX のデバッグレイヤーや GPU バリデーション、そして今回紹介する DRED の設定を指します。
 D3DConfig ではなく DirectX Control Panel (DXCPL) で DirectX のデバッグ設定を行っている方も多いかもしれません。この DXCPL でも十分便利なのですが、DirectX 9 SDK で提供されているツールということもあり機能が古く DirectX 12 の開発には不十分です。
 既に DXCPL の機能拡張は停止しており、新規のデバッグ機能の追加は D3DConfig のみになってます。今回紹介する DRED も D3DConfig でのみサポートされています。
 D3DConfig はコマンドプロンプトや Powershell、Visual Studio 上のターミナル等で実行できます。
https://devblogs.microsoft.com/directx/d3dconfig-a-new-tool-to-manage-directx-control-panel-settings/

1.1 D3DConfig のコマンド
 ターミナル上で引数無しで `d3dconfig` を実行すると現在のすべての設定の状態が得られます。

> d3dconfig
apps
----------------
HOGEHOGE.exe

debug-layer
----------------
conservative-state-tracking=false
debug-layer-mode=force-on
gbv-allow-state-tracking=true
gbv-mode=app-controlled
gbv-pso-create-frontload=false
gbv-shader-patch-mode=unguarded-validation
sync-command-queues=app-controlled

device
----------------
feature-level-limit=none
force-warp=false
no-feature-level-upgrade=false

dred
----------------
auto-breadcrumbs=forced-on
breadcrumb-contexts=forced-on
page-faults=forced-on
watson-dumps=forced-on

message-break
----------------
allow-debug-breaks=false
cat-app-defined=false
cat-cleanup=false
cat-compilation=false
cat-creation=false
cat-execution=false
cat-init=false
cat-misc=false
cat-resource-manip=false
cat-shader=false
cat-state-getting=false
cat-state-setting=false
sev-corruption=false
sev-error=true
sev-info=false
sev-message=false
sev-warning=true
Break-On D3D11 Message Ids:
  <none>
Break-On D3D12 Message Ids:
  <none>

message-mute
----------------
cat-app-defined=false
cat-cleanup=false
cat-compilation=false
cat-creation=false
cat-execution=false
cat-init=false
cat-misc=false
cat-resource-manip=false
cat-shader=false
cat-state-getting=false
cat-state-setting=false
mute-all=false
sev-corruption=false
sev-error=false
sev-info=false
sev-message=false
sev-warning=false
Muted D3D11 Message Ids:
  <none>
Muted D3D12 Message Ids:
  <none>

 よく利用するコマンドを紹介します。

> d3dconfig apps
# D3DConfig の設定を利用するアプリケーションの一覧を確認します

> d3dconfig apps --add TestApp.exe
# アプリケーションを登録します

> d3dconfig debug-layer debug-layer-mode=force-on
> d3dconfig debug-layer debug-layer-mode=force-off
> d3dconfig debug-layer debug-layer-mode=app-controlled
# デバッグレイヤーを強制オン・オフ・アプリケーション設定にします

> d3dconfig debug-layer gbv-mode=force-on
> d3dconfig debug-layer gbv-mode=force-off
> d3dconfig debug-layer gbv-mode=app-controlled
# GPU-based Validation を強制オン・オフ・アプリケーション設定にします

> d3dconfig message-break allow-debug-breaks=true
# メッセージブレークを有効にします
> d3dconfig message-break --add-id-12 722
# DX12 の ID=722(D3D12_MESSAGE_ID_CREATERESOURCE_INVALIDMIPLEVELS) でのブレークを有効にします

 app-controlled はアプリケーション側の “デバッグレイヤーを有効” といった設定に依存します。force-on/off はアプリケーション側の設定関係なく登録されているアプリケーションに全てに機能の有効・無効化を強制します。
 正直なところ GUI ツールである DXCPL と比べると設定に対応したコマンドの把握が必要なのは面倒です。しかしコマンドラインツールになったことでスクリプトによる自動化が可能になり、必要に応じて .bat ファイルを用意しておくなど開発のワークフローに組み込むことは容易になっています。

2. DRED について
 今回は詳しく紹介しませんが、GPU の問題の調査には Debug Layer と GPU Based Validation が大いに役立ちます。これらの機能は、適切でない D3D の API の利用など、アプリケーション実行時に問題の起きた処理をデバッグ出力に表示してくれます。非常に便利な機能ですが、あくまでエラー検出に過ぎず Device Removed が実際に発生した状況を追跡するには少し手が届かない部分があります。
 DRED は Device Removed Extended Data の略称で、名前の通り Device Removed に対して追加の情報を付与する機能です。Debug Layer とは独立した機能で実行のオーバーヘッドも少ないので少なくとも開発中は常に有効にしても問題ありません。(ゼロコストではなく AAA の D3D12 ゲームエンジンで 2~5% のパフォーマンス低下があったそうです)
DRED の詳しい仕様はここで確認できます。
https://microsoft.github.io/DirectX-Specs/d3d/DeviceRemovedExtendedData.html

2.1 Auto-Breadcrumbs
 DRED の主要な機能の一つが Auto-Breadcrumb です。なお DRED の利用はバージョン1.2 から導入された Breadcrumb Context という便利な機能を使うため Windows 10 の 20H1 以降を推奨します。
 Auto-Breadcrumbs は、童話のヘンゼルとグレーテルのように Breadcrumb つまりパンくずを道しるべとして残す機能です(童話内ではパンくずは鳥が食べてしまい結局役には立たないのですが・・・)。ここでいう道しるべとは GPU のイベント実行の流れを指し、Auto-Breadcrumb を有効にするとイベントのたびに自動で記録されていきます。
 GPU イベントが実行されるたびに Breadcrumb は記録されていきます。その過程で Device Removed が発生してある時点以降のイベントが実行できなくなると Breadcrumb か途切れるので、どのタイミングで Device Removed が発生したかの特定が容易になる、という仕組みです。
 Auto-Breadcrumb で記録されるイベント(GPU operations)は以下の enum で定義されています。
https://microsoft.github.io/DirectX-Specs/d3d/DeviceRemovedExtendedData.html#d3d12_auto_breadcrumb_op
 イベントを記録するだけでは、Draw のようにメッシュごとに連続で呼ばれることの多いイベントではどのコールで問題が発生したかの特定が困難です。そこで Breadcrumb Context という機能で PIX のマーカーの文字列を Breadcrumb に付与することができるようになっています。

2.2 GPU Page Fault Data
 DRED のもう一つの機能が GPU Page Fault Data です。GPU Page Falut は GPU 上のメモリ不正アクセスで Device Removed の原因の一つです。削除済みのオブジェクトや範囲外の SRV などにアクセスしようとした際に発生します。
 DRED は Page Fault が起きた仮想アドレスに一致する既存あるいは直近に解放された API オブジェクトの名前と種類を報告してくれます。DRED は直近に解放された最大65000個のオブジェクトを記録してるので、オブジェクトの早期解放の追跡に有用です。
 GPU Page Fault Data の種類(メモリアロケーションの種類)は以下の enum で定義されています。
https://microsoft.github.io/DirectX-Specs/d3d/DeviceRemovedExtendedData.html#d3d12_dred_allocation_type

3. DRED の有効化
 DRED を利用する方法は二つあります。一つはアプリケーションから有効化する方法、もう一つは D3DConfig を使う方法です。アプリケーション内部で有効にする際は、そのアプリケーションが D3DConfig に登録されている場合は sytem-controlled になっている必要があります。

3.1 アプリケーションから設定
 D3D Decice を作成する前に以下の処理を行うことで DRED を有効化できます。

ID3D12DeviceRemovedExtendedDataSettings1* pDredSettings1 = nullptr;
HRESULT hr = g_D3D12FnTable.GetDebugInterface( __uuidof( ID3D12DeviceRemovedExtendedDataSettings1 ), (void**)&pDredSettings1 );
if ( SUCCEEDED( hr ) )
{
    pDredSettings1->SetAutoBreadcrumbsEnablement( D3D12_DRED_ENABLEMENT_FORCED_ON );
    pDredSettings1->SetBreadcrumbContextEnablement( D3D12_DRED_ENABLEMENT_FORCED_ON );
    pDredSettings1->SetPageFaultEnablement( D3D12_DRED_ENABLEMENT_FORCED_ON );
    // LOG("[DRED 1.2] Device Removed Extended Data (Auto Breadcrumbs, Breadcrumb Context, Page Fault) are enabled");
}
else
{
    // LOG("[DRED 1.2] Failed to enable DRED 1.2.");
}

 なお ID3D12DeviceRemovedExtendedDataSettings1 は DRED1.2 を示しています。わざわざ使う必要はないですが DRED1.1 は ID3D12DeviceRemovedExtendedDataSettings です。DRED1.1 では SetBreadcrumbContextEnablement は使えません。
 DRED が利用可能かどうかはプリプロセッサで確認できます。

#if defined(__ID3D12DeviceRemovedExtendedDataSettings1_INTERFACE_DEFINED__)
    // DRED 1.2 が有効
#elif defined(__ID3D12DeviceRemovedExtendedDataSettings_INTERFACE_DEFINED__)
    // DRED 1.1 が有効
#endif

#余談ですが DRED 1.0 は Windows 10 1809 から導入された機能ですが、その時点ではユーザには API が公開されていなかったので、API 上は DRED 1.1 からとなりバージョンと API の数字がずれています。

3.2 D3DConfig から設定

 D3DConfig からの設定は d3dconfig dred で行えます。以下に設定の例を示しました。

> d3dconfig dred
# 現在の DRED の設定を表示します
auto-breadcrumbs=system-controlled
breadcrumb-contexts=system-controlled
page-faults=system-controlled
watson-dumps=system-controlled

> d3dconfig dred auto-breadcrumbs=forced-on 
> d3dconfig dred breadcrumb-contexts=forced-on 
# Auto-Breadcrumbs、Breadcrumb Context を有効化します

> d3dconfig dred page-faults=forced-on 
# GPU Page Fault Data を有効化します

4. DRED の情報を読む
 DRED の情報を読むためには、自前で DRED のデータを処理してログに書き出すか、もしくは、クラッシュ時に作成されるダンプファイルを利用する方法があります。
 ダンプファイルの読み取りは、対象のアプリケーションがクラッシュした際にフルダンプを作成するように設定しておく必要があります。
https://docs.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
 作成されたダンプファイルは windbg.exe や D3DDred.js で読み込むことができます。
https://github.com/Microsoft/DirectX-Debugging-Tools

 今回はダンプファイルは使用せずにアプリケーション内に DRED のログを表示する方法を実装してみました。

4.1 DRED ログ表示の実装
 今回実装した DRED ログ表示の疑似実装を示します。(LOG など一部社内 SDK の関数を仮の名前に置き換えています)。ログの実装は Unreal Engine の実装が非常に参考になったので興味があればご参照ください。この関数を D3D API の各種エラーチェックでエラーが返ってきたタイミングなどに呼び出すことでログを表示することができます。

void D3D12DREDLog(ID3D12Device *pDevice)
{
    // D3D12_AUTO_BREADCRUMB_OP に対応するイベント名を用意しておきます
    // バージョンによってイベントが増えるので注意する
    static const TCHAR* OpNames[] =
    {
        _T("SETMARKER"),                                           // 0
        _T("BEGINEVENT"),                                          // 1
        _T("ENDEVENT"),                                            // 2
        _T("DRAWINSTANCED"),                                       // 3
        _T("DRAWINDEXEDINSTANCED"),                                // 4
        _T("EXECUTEINDIRECT"),                                     // 5
        _T("DISPATCH"),                                            // 6
        _T("COPYBUFFERREGION"),                                    // 7
        _T("COPYTEXTUREREGION"),                                   // 8
        _T("COPYRESOURCE"),                                        // 9
        _T("COPYTILES"),                                           // 10
        _T("RESOLVESUBRESOURCE"),                                  // 11
        _T("CLEARRENDERTARGETVIEW"),                               // 12
        _T("CLEARUNORDEREDACCESSVIEW"),                            // 13
        _T("CLEARDEPTHSTENCILVIEW"),                               // 14
        _T("RESOURCEBARRIER"),                                     // 15
        _T("EXECUTEBUNDLE"),                                       // 16
        _T("PRESENT"),                                             // 17
        _T("RESOLVEQUERYDATA"),                                    // 18
        _T("BEGINSUBMISSION"),                                     // 19
        _T("ENDSUBMISSION"),                                       // 20
        _T("DECODEFRAME"),                                         // 21
        _T("PROCESSFRAMES"),                                       // 22
        _T("ATOMICCOPYBUFFERUINT"),                                // 23
        _T("ATOMICCOPYBUFFERUINT64"),                              // 24
        _T("RESOLVESUBRESOURCEREGION"),                            // 25
        _T("WRITEBUFFERIMMEDIATE"),                                // 26
        _T("DECODEFRAME1"),                                        // 27
        _T("SETPROTECTEDRESOURCESESSION"),                         // 28
        _T("DECODEFRAME2"),                                        // 29
        _T("PROCESSFRAMES1"),                                      // 30
        _T("BUILDRAYTRACINGACCELERATIONSTRUCTURE"),                // 31
        _T("EMITRAYTRACINGACCELERATIONSTRUCTUREPOSTBUILDINFO"),    // 32
        _T("COPYRAYTRACINGACCELERATIONSTRUCTURE"),                 // 33
        _T("DISPATCHRAYS"),                                        // 34
        _T("INITIALIZEMETACOMMAND"),                               // 35
        _T("EXECUTEMETACOMMAND"),                                  // 36
        _T("ESTIMATEMOTION"),                                      // 37
        _T("RESOLVEMOTIONVECTORHEAP"),                             // 38
        _T("SETPIPELINESTATE1"),                                   // 39
        _T("INITIALIZEEXTENSIONCOMMAND"),                          // 40
        _T("EXECUTEEXTENSIONCOMMAND"),                             // 41
        _T("DISPATCHMESH"),                                        // 42
    };

    // D3D12_DRED_ALLOCATION_TYPE に対応する名前を用意しておきます
    static const TCHAR* AllocTypesNames[] =
    {
        _T("COMMAND_QUEUE"),             // 19 start is not zero!
        _T("COMMAND_ALLOCATOR"),         // 20
        _T("PIPELINE_STATE"),            // 21
        _T("COMMAND_LIST"),              // 22
        _T("FENCE"),                     // 23
        _T("DESCRIPTOR_HEAP"),           // 24
        _T("HEAP"),                      // 25
        _T("UNKNOWN"),                   // 26 not exist
        _T("QUERY_HEAP"),                // 27
        _T("COMMAND_SIGNATURE"),         // 28
        _T("PIPELINE_LIBRARY"),          // 29
        _T("VIDEO_DECODER"),             // 30
        _T("UNKNOWN"),                   // 31 not exist
        _T("VIDEO_PROCESSOR"),           // 32
        _T("UNKNOWN"),                   // 33 not exist
        _T("RESOURCE"),                  // 34
        _T("PASS"),                      // 35
        _T("CRYPTOSESSION"),             // 36
        _T("CRYPTOSESSIONPOLICY"),       // 37
        _T("PROTECTEDRESOURCESESSION"),  // 38
        _T("VIDEO_DECODER_HEAP"),        // 39
        _T("COMMAND_POOL"),              // 40
        _T("COMMAND_RECORDER"),          // 41
        _T("STATE_OBJECT"),              // 42
        _T("METACOMMAND"),               // 43
        _T("SCHEDULINGGROUP"),           // 44
        _T("VIDEO_MOTION_ESTIMATOR"),    // 45
        _T("VIDEO_MOTION_VECTOR_HEAP"),  // 46
        _T("VIDEO_EXTENSION_COMMAND"),   // 47
        _T("INVALID"),                   // 0xffffffff
    };

    // DRED Auto-Breadcrumb の先頭ノード
    const D3D12_AUTO_BREADCRUMB_NODE1* pBreadcrumbHead = nullptr;

    // DRED のインタフェースを取得
    ID3D12DeviceRemovedExtendedData1* pDred = nullptr;
    if ( SUCCEEDED( pDevice->QueryInterface( __uuidof( ID3D12DeviceRemovedExtendedData1 ), (void**)&pDred ) ) )
    {
        // Auto-Breadcrunmb の先頭ノードを取得
        D3D12_DRED_AUTO_BREADCRUMBS_OUTPUT1 DredAutoBreadcrumbsOutput;
        HRESULT hr = pDred->GetAutoBreadcrumbsOutput1( &DredAutoBreadcrumbsOutput );
        if ( SUCCEEDED( hr ) )
        {
            pBreadcrumbHead = DredAutoBreadcrumbsOutput.pHeadAutoBreadcrumbNode;
        } // End if
        else if ( hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE )
        {
            LOG( _T( "[DRED 1.2] The device is not in a removed state." ) );
            return;
        }
        else if ( hr == DXGI_ERROR_UNSUPPORTED )
        {
            LOG( _T( "[DRED 1.2] auto-breadcrumbs have not been enabled." ) );
            return;
        }
    } // End if (QueryInterface)

    if ( pDred )
    {
        // Auto-Breadcrunmb を先頭からたどって出力していく
        // ノードはグラフィクスコマンドリストごとに一つ作成されている
        // 詳細:https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_auto_breadcrumb_node
        TCHAR str[64];
        if ( pBreadcrumbHead )
        {
            LOG( _T( "[DRED 1.2] Last tracked GPU operations:" ) );

            UINT TracedCommandLists = 0;
            auto pNode = pBreadcrumbHead;
            while ( pNode )
            {
                // 最後に実行された Breadcrumb の値
                UINT LastCompletedOp = *pNode->pLastBreadcrumbValue;

                // 最後に実行された Op の値と 全体の Breadcrumb の数が違うなら途中で止まっている
                if ( LastCompletedOp != pNode->BreadcrumbCount && LastCompletedOp != 0 )
                {
                    LOG( _T( "[DRED 1.2] Auto Breadcrunmb Info") );
                    StrWtoT( str, 64, pNode->pCommandListDebugNameW );
                    LOG( _T( "\tCommandlistName:         %s" ), str );
                    StrWtoT( str, 64, pNode->pCommandQueueDebugNameW );
                    LOG( _T( "\tCommandQueue:            %s" ), str );
                    LOG( _T( "\tNum Breadcrumbs:         %d" ), pNode->BreadcrumbCount );
                    LOG( _T( "\tLast Breadcrumb:         %d" ), LastCompletedOp );
                    LOG( _T( "\tNum Breadcrumb Contexts: %d" ), pNode->BreadcrumbContextsCount);
                    LOG( _T( "------------------------------------" ) );
                    TracedCommandLists++;

                    // Breadcrumb の数だけ Context (PIX のマーカー) 用の文字列を用意しておく
                    // Map を使った方が効率がいい
                    Array<StringT> Contexts;
                    Contexts.resize( pNode->BreadcrumbCount );

                    // Breadcrumb Context を走査して対応する Breadcrumb の番号に Context の文字列を格納する
                    for ( UINT i = 0; i< pNode->BreadcrumbContextsCount; ++i )
                    {
                        D3D12_DRED_BREADCRUMB_CONTEXT Context = pNode->pBreadcrumbContexts[i];

                        StrWtoT( str, 64, Context.pContextString );
                        Contexts[Context.BreadcrumbIndex].Format( _T( "[%s]" ), str );
                    }

                    // すべての Breadcrumb を出力する
                    for ( UINT Op = 0; Op < pNode->BreadcrumbCount; ++Op )
                    {
                        // 最後に実行された Op より前の Breadcrumb は実行されている([reached]とする)
                        const TCHAR* OpRearched = ( Op <= LastCompletedOp ) ? _T( "[reached]" ) : _T( "" );

                        // イベント名を解決する
                        D3D12_AUTO_BREADCRUMB_OP BreadcrumbOp = pNode->pCommandHistory[Op];
                        const TCHAR* OpName     = ( BreadcrumbOp < ARRAYSIZE( OpNames ) ) ? OpNames[BreadcrumbOp] : _T( "Unknown Op" );

                        // コンテキストのテキストを解決する
                        const TCHAR* OpContext  = ( !Contexts[Op].empty() ) ? Contexts[Op].GetBuffer() : _T( "" );

                        // 出力
                        LOG( _T( "\t%d %s| %s%s" ), Op, OpRearched, OpName, OpContext );
                    } // End for (BreadcrumbCount)
                } // End if (LastCompletedOp)

                pNode = pNode->pNext;
            } // End while (pNode)

            if ( TracedCommandLists == 0 )
            {
                LOG( _T( "[DRED 1.2] No command list found." ) );
            } // End if (TracedCommandLists)
        } // End if (pBreadcrumbHead)
        else
        {
            LOG( _T( "[DRED 1.2] No Breadcrumb data." ) );
        } // End else

        // Page Fault Data を取得する
        D3D12_DRED_PAGE_FAULT_OUTPUT1 DredPageFaultOutput;
        if ( SUCCEEDED( pDred->GetPageFaultAllocationOutput1( &DredPageFaultOutput ) ) && DredPageFaultOutput.PageFaultVA != 0 )
        {
            // 取得した Page Fault の仮想アドレス
            LOG( _T( "[DRED 1.2] PageFault VA: 0x%llX" ), (long long)DredPageFaultOutput.PageFaultVA );

            // アロケート済みのノードを走査する
            const D3D12_DRED_ALLOCATION_NODE1* pNode = DredPageFaultOutput.pHeadExistingAllocationNode;
            if ( pNode )
            {
                LOG( _T( "[DRED  1.2] Allocation Nodes:" ) );
                while ( pNode )
                {
                    // アロケーションの名前を解決する
                    // enum D3D12_DRED_ALLOCATION_TYPE is start with D3D12_DRED_ALLOCATION_TYPE_COMMAND_QUEUE(19)
                    UINT index = pNode->AllocationType - D3D12_DRED_ALLOCATION_TYPE_COMMAND_QUEUE;
                    const TCHAR* AllocTypeName = ( index < ARRAYSIZE( AllocTypesNames ) ) ? AllocTypesNames[index] : _T( "Unknown Alloc" );
                    StrWtoT( str, 64, pNode->ObjectNameW );
                    LOG( _T( "\tName: %s (Type: %s)" ), str, AllocTypeName );

                    pNode = pNode->pNext;
                } // End while (pNode)
            } // End if (pNode)

            // 直近に解放されたノードを走査する
            pNode = DredPageFaultOutput.pHeadRecentFreedAllocationNode;
            if ( pNode )
            {
                LOG( _T( "[DRED 1.2] Freed Nodes:" ) );
                while ( pNode )
                {
                    // アロケーションの名前を解決する
                    UINT index = pNode->AllocationType - D3D12_DRED_ALLOCATION_TYPE_COMMAND_QUEUE;
                    const TCHAR* AllocTypeName = ( index < ARRAYSIZE( AllocTypesNames ) ) ? AllocTypesNames[index] : _T( "Unknown Alloc" );
                    StrWtoT( str, 64, pNode->ObjectNameW );
                    LOG( _T( "\tName: %s (Type: %s)" ), str, AllocTypeName );

                    pNode = pNode->pNext;
                } // End while (pNode)
            } // End if (pNode)
        } // End if (DredPageFaultOutput)
        else
        {
            LOG( _T( "[DRED 1.2] No PageFault data." ) );
        } // End else
    } // End if (pDred)
} // End function

 社内レンダリングエンジン上で実際に得られたログも示します。一部省略しつつ、イベント名などは社内エンジンのものから適当に置き換えています

[DRED 1.2] Last tracked GPU operations:
[DRED 1.2] Auto Breadcrunmb Info
	CommandlistName:         CommandList_10
	CommandQueue:            D3D12CommandList
	Num Breadcrumbs:         268
	Last Breadcrumb:         61
	Num Breadcrumb Contexts: 114
------------------------------------

	42 [reached]| BeginEvent[GBuffer]
	43 [reached]| DrawIndexedInstanced
	44 [reached]| DrawIndexedInstanced
	45 [reached]| DrawIndexedInstanced
	46 [reached]| DrawIndexedInstanced
	47 [reached]| DrawIndexedInstanced
	48 [reached]| DrawIndexedInstanced
	49 [reached]| DrawIndexedInstanced
	50 [reached]| DrawIndexedInstanced
	51 [reached]| EndEvent
	52 [reached]| BeginEvent[...]
	53 [reached]| ResourceBarrier
	54 [reached]| DrawIndexedInstanced
	55 [reached]| EndEvent
	56 [reached]| BeginEvent[...]
	57 [reached]| EndEvent
	58 [reached]| BeginEvent[RayTracing]
	59 [reached]| BeginEvent[RayTracing_0]
	60 [reached]| ResourceBarrier
	61 [reached]| DispatchRays
	62 | EndEvent
	63 | EndEvent
	64 | BeginEvent[...]
	65 | ResourceBarrier
	66 | Dispatch
	67 | EndEvent
	68 | BeginEvent[...]
	69 | ResourceBarrier
	70 | Dispatch
	71 | EndEvent
	72 | BeginEvent[...]
	73 | ResourceBarrier
	74 | Dispatch

	232 | BeginEvent[LightCulling]
	233 | ResourceBarrier
	234 | CopyBufferRegion
	235 | ResourceBarrier
	236 | Dispatch
	237 | EndEvent
	238 | BeginEvent[Forward]
	239 | ResourceBarrier
	240 | DrawIndexedInstanced
	241 | DrawIndexedInstanced
	242 | DrawIndexedInstanced
	243 | DrawIndexedInstanced
	244 | DrawIndexedInstanced
	245 | DrawIndexedInstanced
	246 | DrawIndexedInstanced
	247 | DrawIndexedInstanced
	248 | EndEvent
	249 | BeginEvent[SkyMap]
	250 | DrawIndexedInstanced
	251 | EndEvent

 61 番目の DispatchRays まで reached になっていることが確認できます。つまりレイトレーシングを行った際に何かしらの問題があり Device Removed が生じたと推測できます。
 実際に、このログはレイトレーシングの Shader Table のアドレスをわざとおかしく設定して Device Removed を発生させました。
 Page Fault はちょうどよいログが用意できなっかったので省略していますが、意図しないタイミングでリソースの開放を行うなどすればと確認できると思います。
 ログの中の […] で示されているテキストが Breadcrumb Context の機能で、ここでは PIXBeginEvent で指定したレンダリングパスの名前が表示されています。PIXSetMarker で任意の位置にマーカーを仕込むこともできます。
 DrawIndexedInstanced が並んでいる部分などは実際に何のオブジェクトの描画が実行されているかわかりませんが、マーカーでオブジェクトの名前を設定してあげれば問題が発生した際に原因箇所の特定に役立ちます。
 PIX の機能は以下に詳しく載っています。
https://devblogs.microsoft.com/pix/winpixeventruntime/

5. まとめ
 今回は DirectX 12 のデバッグ機能の一つである DRED を紹介しました。すべての機能を紹介しきれたわけではありませんが、困難な GPU デバッグの一助になれば幸いです。
 DirectX の新しいデバッグ機能としては、Debug Object Auto-Naming というものがすでにがアナウンスされていますし、DRED 1.3 へのアップデートもありそうです。デバッグ作業の効率化について新しい情報があればまたブログ記事で紹介していきたいと思います。

この記事をシェアする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です