OGRE1.9でリソースリーク

OGRE1.9のD3D11レンダラを使って色々検証していたんですが,どうもプログラム内で テクスチャを生成すると,終了時にD3D11がリソースリークを報告するようです.

terminating_rldo

デバッグアウトプットにしか出ていなかったので最初気づきませんでした.アボートするような 深刻なバグではないようですが気になったので調べてみました.

結論だけ知りたい方はogre_v1-9-0_OgreD3D11Texture.patchをご覧下さい.

D3D11でのデバッグ

D3D9だとデバッグランタイムに切り替えてメッセージを見て,とかあったのですがD3D11では 特にそう言うものはないようです.

その代わり(なのかどうか分かりませんが),ID3D11Debug::ReportLiveDeviceObjectというメソッドを使って, 生存しているオブジェクトを表示することが出来るようです.初期化は以下のような感じですね.普通のCOMインターフェースです.

  ID3D11Device *device = /* ... */;
  ID3D11Debug *debug;
  device->QueryInterface(__uuidof(ID3D11Debug), (void**)&debug);

  debug->ReportLiveDeviceObjects(D3D11_RLDO_SUMMARY); /*サマリモード*/
  debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL); /*詳細モード*/

結果はOutputDebugStringを使ったときと 同じようにデバッグアウトプットに出力されます.

リソースリークの再現コード

たとえば以下のようにするとテクスチャを生成して,その直後に完全に解放されるはずなのですが,これがうまくいかない.

Ogre::RenderWindow *rw = /* ... */;
{
  auto tex = Ogre::TextureManager::getSingleton().createManual("tex", 
               Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
               Ogre::TEX_TYPE_2D, rw->getWidth(), rw->getHeight(), 0, 
               Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);

  Ogre::TextureManager::getSingleton().remove("tex");
  Ogre::TextureManager::getSingleton().unloadUnreferencedResources(false);
}

試しにこの前後にReportLiveDeviceObjectsを挟んで,出力結果の差分をとってみました.

Ogre::RenderWindow *rw = /* ... */;

OutputDebugStringA("1.##################################################\n");
mDebug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
{
      auto tex = Ogre::TextureManager::getSingleton().createManual("tex", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
      Ogre::TEX_TYPE_2D, RenderWindow()->getWidth(), RenderWindow()->getHeight(), 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET);

      Ogre::TextureManager::getSingleton().remove("tex");
      Ogre::TextureManager::getSingleton().unloadUnreferencedResources(false);
}
OutputDebugStringA("2.##################################################\n");
mDebug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);

diff_rldo

前後で増えたのは以下の二行です.Refcountは参照カウント,IntRefはD3Dの内部参照です.ID3D11Texture2Dの参照カウントは消えてますけど,ID3D11RenderTargetViewが残ってしまっていますね.

  D3D11 WARNING: Live ID3D11Texture2D at 0x0000000007912690,        Refcount: 0, IntRef: 1 1
  D3D11 WARNING: Live ID3D11RenderTargetView at 0x0000000007913E70, Refcount: 1, IntRef: 0 1

原因と解決

原因はOgre::D3D11RenderTextureのデストラクタでした. このクラスはOgre::D3D11Textureを生成する過程で作られ,ID3D11RenderTargetViewID3D11DepthStencilViewの二つをメンバーに含むのですが,これらを解放するコードが見当たりません.

RenderSystems/Direct3D11/src/OgreD3D11Texture.cpp:1187行目付近

    //---------------------------------------------------------------------
    D3D11RenderTexture::D3D11RenderTexture( const String &name, D3D11HardwarePixelBuffer *buffer,  D3D11Device & device ) : mDevice(device),
    RenderTexture(buffer, 0)
    {
        mName = name;

        rebind(buffer);
    }

    //---------------------------------------------------------------------

    D3D11RenderTexture::~D3D11RenderTexture()
    {

    }

これを以下ように書き換えることで解決しました.

    //---------------------------------------------------------------------
    D3D11RenderTexture::D3D11RenderTexture( const String &name, D3D11HardwarePixelBuffer *buffer,  D3D11Device & device )
        : mDevice(device)
        , mRenderTargetView(nullptr)
        , mDepthStencilView(nullptr)
        , RenderTexture(buffer, 0)
    {
        mName = name;

        rebind(buffer);
    }

    //---------------------------------------------------------------------

    D3D11RenderTexture::~D3D11RenderTexture()
    {
        SAFE_RELEASE(mRenderTargetView);
        SAFE_RELEASE(mDepthStencilView);
    }

おまけ:OGREでのD3D11Deviceの取得方法

OGREではネイティブなリソースはRenderSystemなどのプラグインの中に隠されているのですが,一応アクセスすることは可能です. 以下のようにします.

  auto rs = (Ogre::D3D11RenderSystem*)Root()->getRenderSystem();
  ID3D11Device *device;
  rs->getCustomAttribute("D3DDEVICE", (void**)&device);
  device->QueryInterface(__uuidof(ID3D11Debug), (void**)&mDebug);

ちなみに,間違えて最初はこうやってました.Ogre::D3D11Device::get()というDLLエクスポートされていない関数の実装を無理矢理与えて 内部リソースをぶんどるやり方です.

  ID3D11Device* Ogre::D3D11Device::get() { return mD3D11Device;  }  
  /* ... */
  void func()
  {
    auto rs = (Ogre::D3D11RenderSystem*)Root()->getRenderSystem();
    rs->_getDevice().get()->QueryInterface(__uuidof(ID3D11Debug), (void**)&mDebug);
  }

追記:2014-09-28
どうもD3D11レンダーシステム側での,デバイスエラーが起きたかどうかのチェックの一部がpoorなようで, OGREで上記のID3D11Debug::ReportLiveDeviceObjects()を使うと,タイミングによってはエラーが発生する場合があるようです.

ID3D11Debug::ReportLiveDeviceObjects()は,デバッグ出力へのメッセージの出力と,同時にID3D11InfoQueueにもメッセージを 出力しているようです.

D3D11レンダーシステムのエラー処理ではデバイスエラーが起きたかどうかを,ID3D11InfoQueueに貯まっている メッセージの数で判定している部分(D3D11Device::isError())があるため,普通にReportLiveDeviceObjects()を 使ってしまうとエラーとして誤認されます.

これを回避するには以下のようにします.

ID3D11Debug *debug; // 初期化済み

debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL); // RLDOを使ったら…

ID3D11InfoQueue *infoQueue;
debug->QueryInterface(__uuidof(ID3D11InfoQueue), (void**)&infoQueue);

infoQueue->ClearStoredMessages(); // クリアしておく.

おまけ:OGREのログ制御

OGREではログが標準ではファイルとコンソールに出力されるのですが,コンソールに出力されるのを抑止する方法があります. 方法は単純で以下のようにしてOGREの初期化に先んじてOgre::LogMangaerをインスタンス化してしまえば良いのです.

createLogの第3引数をfalseにするとコンソールにログが出力されなくなります.

  auto lm = new Ogre::LogManager;
  nlm->createLog("Ogre.log", true, false);

  mRoot = new Ogre::Root;

OGREの初期化処理ではLogManagerが既にインスタンス化されているかどうかをチェックしていますのでこの方法は合法なはずです. OGREの初期化時のログの出し方を制御できるように,LogManagerの生成だけはOgre::Rootの初期化如何と無関係に出来るようになっているものと思われます.

Leave a Reply