はじめに
DirectXのデバッグ機能には、生成したリソースの解放漏れをチェックするというものが含まれています。有効にするとデバッグ実行後に、Visual Studio の出力ウィンドウによくわからないテキストが出てくるのを見たことがある人もいるのではないでしょうか。
最近、以下の記事を見かけたので、私も知っている情報を文章化して残しておこうと思いました。本記事ではDirectX12を対象として説明します。DirectX11版が気になる人は、基本的な事柄は同様のため、本記事を読んだ後で、参考記事を読むことをお勧めします。
本記事では、ComPtr を用いて各リソースを扱うことを前提としています。直接DirectXのインターフェースクラス(ポインタ)を取り扱っている場合には、少し異なる部分が発生する点にご注意ください。
また、エラーハンドリングについても記載を省略しているので、コードなどの利用の際にはご注意ください。
調査のための準備
デバッグ機能を有効にする
DirectX12ではデバッグ機能(デバッグレイヤー)を有効にするには、ID3D12Debugインターフェース経由で行います。この実装のコード例を次に示します。デバッグレイヤーを有効化した後は、 ID3D12Debugインターフェースは解放して問題ありません。
ComPtr<ID3D12Debug> debug;
D3D12GetDebugInterface(IID_PPV_ARGS(&debug));
debug->EnableDebugLayer();
リソースに名前をつける
DirectX12の各リソースには、名前をつけることが出来ます。 SetName というメソッドが行います。SetNameはID3D12Objectインターフェースで実装されています。ほぼ全てのDirectX12のオブジェクトがこのインターフェースを継承するため、名前をつけておくとリソースリーク以外の調査にも役立つことが多いです。
m_rootSignature->SetName(L"MyRootSignature");
インターフェースの継承関係を理解しておく
DirectXの各リソースを示すインターフェースは、他のインターフェースを継承しています。この継承関係を理解しておくことも大切です。私が手元で作成した図を次に記載します。この図は全ての表示をしておらず、最近の末尾ナンバリングが増えたものや、機能が増えたものなどが欠けています。
リソースと表現される多くのオブジェクトは ID3D12DeviceChild インターフェースを継承しています。
DirectXはCOMのお作法で実装されるため、この継承関係をC++のクラスのようにして辿ることは正しくありません。辿る際には、 QueryInterface メソッドを用いて対象となるインターフェースを取得する、という方法で行います。
継承関係の図については、Microsoft のページにも記載があったので、こちらを見てみるのもよいでしょう。
レポートの出力について
デバッグレイヤーを有効化して、リソースの解放漏れ(リソースリーク)がある状態でプログラムを終了すると、Visual Studio の出力ウィンドウ内に以下のような情報が出力されます。Refcount が1以上のものを調査するのが基本となります。
D3D12 WARNING: Process is terminating. Using simple reporting. Please call ReportLiveObjects() at runtime for standard reporting. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Producer at 0x0000000003545770, Refcount: 2. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x0000000003654990, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x00000000035CE3B0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x000000000366EC00, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x00000000036845E0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x00000000036F9450, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x00000000036F9190, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x00000000037E2850, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x00000000037E2AB0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x0000000003870700, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x000000000A18BC90, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x000000000A19D860, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object at 0x000000000A19D5A0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Object : 12 [ STATE_CREATION WARNING #0: UNKNOWN]
Live Object at に続く数値は、各リソースのアドレスとなっています。ComPtr<T>のGet()で得られるポインタ値と一致します。Live Producer は ID3D12Device のポインタとなっていることがほとんどでしょう。そのため、列挙されているLive Object の各項目を調べていくことになります。
プログラム終了時に出力されるこのレポートは、Direct12のデバイスが解放される瞬間ではなく、プログラムがアンロードされるときに呼ばれるという点に注意です。そのため、次に紹介する方法でリークをチェックするのが簡単です。
ID3D12DebugDeviceからのレポート出力
詳細情報を出力すると調査が捗ります。そのために、ID3D12DebugDeviceを使用します。ID3D12DebugDeviceは、ID3D12Deviceから得ることができます。
ComPtr<ID3D12DebugDevice> debugDevice;
m_device.As(&debugDevice); // m_device が ComPtr<ID3D12Device> な場合
ComPtr を使っていないデバイスの場合では、以下の通りです。
pd3ddev12->QueryInterface(__uuidof(ID3D12DebugDevice), &debugDevice);
ID3D12DebugDeviceインターフェースには、ReportLiveDeviceObjects メソッドがあり、これを呼び出すことで現在の生存リソースの情報を出力できます。このメソッドに指定するフラグは D3D12_RLDO_FLAGS 型で、主にD3D12_RLDO_DETAILを使うことが多いと思われます。
D3D12_RLDO_DETAIL
以下のコードで詳細レポートを出力します。内部用のオブジェクト情報はアプリケーション側のリーク調査の妨げになるので、D3D12_RLDO_IGNORE_INTERNALフラグを設定して無視としています。
debugDevice->ReportLiveDeviceObjects(D3D12_RLDO_DETAIL | D3D12_RLDO_IGNORE_INTERNAL);
出力例としては、以下の通りです。SetNameで名前を指定していると、Name: として表示されるので、特定しやすくなります。
D3D12 WARNING: Live ID3D12Device at 0x00000000035CE0E0, Name: d3d12Dev, Refcount: 3 [ STATE_CREATION WARNING #274: LIVE_DEVICE]
D3D12 WARNING: Live ID3D12RootSignature at 0x000000000A22B1A0, Name: MyRootSignature, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #577: LIVE_ROOTSIGNATURE]
D3D12 WARNING: Live ID3D12Resource at 0x000000000A23ED80, Name: modelVB, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #575: LIVE_RESOURCE]
D3D12_RLDO_SUMMARY
サマリー出力することもできます。D3D12_RLDO_DETAILのときと同様に呼び出します。
debugDevice->ReportLiveDeviceObjects(D3D12_RLDO_SUMMARY | D3D12_RLDO_IGNORE_INTERNAL);
このレポート出力は以下のようになります。各リソースごとに生存しているオブジェクトの個数が出力されるものとなっています。
D3D12 WARNING: Using ID3D12DebugDevice2::ReportLiveDeviceObjects with D3D12_RLDO_DETAIL will help drill into object lifetimes.
[ STATE_CREATION WARNING #255: LIVE_OBJECT_SUMMARY]
D3D12 WARNING: Live ID3D12Device at 0x0000000003503FF0, Name: d3d12Dev, Refcount: 3 [ STATE_CREATION WARNING #274: LIVE_DEVICE]
D3D12 WARNING: Live ID3D12RootSignature : 1 [ STATE_CREATION WARNING #255: LIVE_OBJECT_SUMMARY]
D3D12 WARNING: Live ID3D12Resource : 1 [ STATE_CREATION WARNING #255: LIVE_OBJECT_SUMMARY]
注意点など
先のレポートらは、DirectX12のデバイスが解放されたあとで、出力を行ったものです。これが大切で、ComPtr でラップしたDirectX12のデバイスを保持しているクラスのデストラクタが完了した後で呼び出す、ということです。そうしないと、余計な情報が出力されてリークしているものを見つけるのに手間取ることになります。D3D12_RLDO_IGNORE_INTERNALフラグをつけて、余計なノイズを減らすのもその1つです。参照カウンタ0となっているのに出力レポートに表示されて困るという場合、ぜひこのフラグをセットしましょう。
ID3D12DebugDeviceは、ID3D12DeviceをReleaseした後でも呼び出すことができます。このインターフェース事前に取得しておき、デバイスの解放後でレポート関数を呼び出す、という実装にすると見やすい結果を得られるのではないかと思います。
各リソースは ID3D12DeviceChildを継承していると先に説明しました。これらのオブジェクトはデバイスへの内部参照を保持します。そのため、通常の実行においてID3D12Deviceの参照カウンタはかなり大きな値となっています。そして、このインターフェースを継承したオブジェクト群がリソースチェックの対象となり、デバイスよりも長い生存期間を持たないという暗黙的な仕様があるようです。
D3D12_RLDO_SUMMARY などの RLDO が気になりますが、おそらく、 Report Live Device Objects の略といったところなのでしょう。
コメント