LoginSignup
2
2

More than 3 years have passed since last update.

DirectXTKでデバイスロスト処理を自動化する

Posted at

DirectXTK の小ネタです。
DirectXTKはMicrosoftが提供しているDirectX11を便利に使うためのライブラリです。

デバイスロスト時の処理の記述が面倒くさかったので自動化してみました。

OnDeviceLost が面倒くさい

例えとして、 DirectXTKを用いてテクスチャを表示する解説ページ(英語) に則って画像を表示するプログラムを書いてみると下のようになる。

Game.h
class Game {
public:
    // ...
    void CreateDeviceDependentResources();
    void OnDeviceLost();
private:
    // ...
    std::unique_ptr<DirectX::SpriteBatch> m_spriteBatch;
    std::unique_ptr<DirectX::CommonStates> m_commonStates;
    Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_texture;
};
Game.cpp
void Game::CreateDeviceDependentResources()
{
    // m_spriteBatch, m_commonStates, m_texture の初期化処理
}

void Game::OnDeviceLost()
{
    m_spriteBatch.reset();
    m_commonStates.reset();
    m_texture.Reset();
}

この中の OnDeviceLost に注目してほしい。
これはデバイスロスト時に呼ばれる関数ですべてのリソースを解放する必要があるのだが、リソースを追加するたびに OnDeviceLost にそのリソースの解放処理を追記しないといけない。これは面倒くさく、バグにつながる。

できればリソースを追加しただけで解放も自動的に行ってくれるようにしたい。

std::unique_ptr を使って自動で解放する

デストラクタによって自動的にリソースが解放されることを利用して、下のように改善してみた。

Game.h
class Game {
public:
    // ...
    void CreateDeviceDependentResources();
    void OnDeviceLost();
private:
    // ...
    struct DeviceDependentResources {
        std::unique_ptr<DirectX::SpriteBatch> spriteBatch;
        std::unique_ptr<DirectX::CommonStates> commonStates;
        Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> texture;
    };
    std::unique_ptr<DeviceDependentResources> m_resources;
};
Game.cpp
void Game::CreateDeviceDependentResources()
{
    m_resources = std::make_unique<DeviceDependentResources>();
    // spriteBatch, commonStates, texture の初期化処理
}

void Game::OnDeviceLost()
{
    m_resources.reset();
}

リソースをすべて DeviceDependentResources 構造体に入れてそれを std::make_unique している。
解放時は DeviceDependentResources 構造体ごと解放する。
これによってリソースを増やしたときに OnDeviceLost に解放処理を追記する必要が無くなった。やったー。

とても便利になったが、一つ問題があるとすれば、ヒープ領域を確保する必要が無かったところヒープを使用してしまっていることだ。
デストラクタによる自動解放を利用しつつ、新たにヒープ領域を確保しない方法があればいいのだが……。と思っていたが、あった。

std::optional を使おう

std::optional はC++17で追加された、無効かもしれない値を保持するためのものである。
これを使えば先ほどの std::unique_ptr を用いたような任意のタイミングでのコンストラクトとデストラクトが可能になる。
そして、 std::unique_ptr との一番の違いはヒープからメモリをとってこないということである。(メモリはスタック領域からとってくる)(つまり実行時型情報を用いたポリモーフィズムは使えない)

使った例 ↓

Game.h
class Game {
public:
    // ...
    void CreateDeviceDependentResources();
    void OnDeviceLost();
private:
    // ...
    struct DeviceDependentResources {
        std::unique_ptr<DirectX::SpriteBatch> spriteBatch;
        std::unique_ptr<DirectX::CommonStates> commonStates;
        Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> texture;
    };
    std::optional<DeviceDependentResources> m_resources;
};
Game.cpp
void Game::CreateDeviceDependentResources()
{
    m_resources.emplace();
    // spriteBatch, commonStates, texture の初期化処理
}

void Game::OnDeviceLost()
{
    m_resources.reset();
}

2つ目の例と見た目はほぼ変わらないですが、余分なヒープ領域をとってくることはなくなった。

補足

上の例の SpriteBatch とかについても std::unique_ptr より std::optional のほうがよさげではある。
でも公式リファレンスでは謎の std::unique_ptr 推しをしている。これは多分 std::optional の説明が面倒くさいからだと思う。実際の実行性能もほぼ変わらないし。

まとめ

メンバ変数を任意のタイミングでコンストラクト/デストラクトしたいときは std::optional!!!

参考

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2