本サイトでは、アフィリエイト広告およびGoogleアドセンスを利用しています。

Presentだけを実行するとメモリが増えていく

DirectX12 のアプリケーション開発において、ほとんど何もしていないのに使用メモリがモリモリ増えていく事案に出遭いました。何もせずに、 Present メソッドを呼び出しているだけですが、使用メモリが増えていきます。

自分の環境は以下に示す状態となります。

  • Windows10 x64 1909 バージョン
  • Visual Studio 2019
  • Windows SDK 10.0.18362.0
  • NVIDIA Geforce RTX 2060S
  • ドライババージョン: 442.92

Windows10 バージョン 2004 に更新してから、この問題は解決しているように見えます。手元のいくつかのサンプルではメモリ増加が観測できませんでした(2020/05/17 記載)

サンプルコード

抜粋しながらコードの例を示します。DirectX12のデバイスとスワップチェインを準備している箇所が以下となります。特に変な箇所はないと思っています。

HRESULT hr;
UINT dxgiFlags = 0;
ComPtr<IDXGIFactory3> factory;

#if defined(_DEBUG)
ComPtr<ID3D12Debug> debug;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug))))
{
  debug->EnableDebugLayer();
  dxgiFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
#endif
CreateDXGIFactory2(dxgiFlags, IID_PPV_ARGS(&factory));

hr = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
const D3D12_COMMAND_QUEUE_DESC dxQDesc = {
  D3D12_COMMAND_LIST_TYPE_DIRECT,
  D3D12_COMMAND_QUEUE_PRIORITY_NORMAL,
  D3D12_COMMAND_QUEUE_FLAG_NONE,
  0
};
m_device->CreateCommandQueue(&dxQDesc, IID_PPV_ARGS(&m_commandQueue));

const DXGI_SWAP_CHAIN_DESC1 swapchainDesc{
  640,
  480,
  DXGI_FORMAT_B8G8R8A8_UNORM,
  FALSE,
  { 1, 0 },
  DXGI_USAGE_RENDER_TARGET_OUTPUT,
  2,
  DXGI_SCALING_STRETCH,
  DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
  DXGI_ALPHA_MODE_UNSPECIFIED,
  0
};

ComPtr<IDXGISwapChain1> swapchain1;
factory->CreateSwapChainForHwnd(m_commandQueue.Get(), hwnd, &swapchainDesc, nullptr, nullptr, &swapchain1);
swapchain1.As(&m_swapchain);

問題の Present の部分です。

m_swapchain->Present(1, 0);

症状

これをゲームループなどで Present だけを実行するようにすると、使用メモリがモリモリと増えていく症状に出遭います。あるいはウィンドウが応答なし状態に陥ったりと妙な状態に陥ります。

デバッグ時の診断ツールを同時に動かしていると、応答なしではなくメモリがモリモリと増えていくような動きをしました。診断ツールなしだと、アプリが応答不能になるのを観測しました。

対策

画面をクリアするだけのアプリケーションでは今回の症状が起こらなかったことを確認しています。これを元にして調査した結果、以下のことがわかりました。

  • ID3D12Debug::EnableDebugLayer を呼び出して、デバッグレイヤーを有効化している
  • コマンドキューに何も積まれていない

この条件を満たして、 Present を実行するとメモリ消費が発生することがわかりました。デバッグレイヤーを無効化してしまうことは避けたかったため、コマンドキューにダミー処理を追加することで今回は対処ができました。

適当にフェンスの Signal 発行、それのウェイト待機くらいの処理を追加して、今回の対処としました。同期を待つ部分で多少の時間ロスが発生しますが、この点は仕方ないコストとして払う感じとしました。

m_commandQueue->Signal(m_fence.Get(), m_value);
if (m_fence->GetCompletedValue() < m_value)
{
  m_fence->SetEventOnCompletion(m_value, m_handle);
  WaitForSingleObject(m_handle, INFINITE);
}

このコードはいわゆる、フレーム間の同期待ちコードと酷似しています。

まとめ

状況によっては今回紹介したような事例にはならない可能性も十分にあります。自分の環境では高確率で起こりますが、たまに起こらないこともありました。

そもそもこのようなコードを書くこと自体が稀な状況かと思います。といってもデバッグ調査中でこのような状況に陥ってしまうことも十分考えられます。このときにメモリがモリモリと増えたら今回紹介した事案が役に立ったら嬉しく思います。

DirectX
すらりんをフォローする
すらりん日記
タイトルとURLをコピーしました