Direct3D12でimguiを使ってみる

Direct3Dのアプリを書くには、デバッグ描画が必要です。デバッグ描画とは、例えば、画面上にフレームレートや様々な設定を描画するものです。 最終的な商品では使われないものなので、切り離してコンパイルできるように作りたいです。

この辺のサイトを参考に組み込んでみました。

https://zenn.dev/norainu/articles/10856bfe120aa2

分離できるように別クラスにまとめます。

class ImGuiUtil {
public:
    ImGuiUtil() {

    }
    virtual ~ImGuiUtil() {
    }

    // 初期化
    void Initialize(HWND hWnd, ID3D12Device *pDevice)
    {
        IMGUI_CHECKVERSION();
        ImGui::CreateContext();
        ImGuiIO& io = ImGui::GetIO();
        io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
        io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

        ImGui::StyleColorsLight();

        D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
        srvHeapDesc.NumDescriptors = 1;
        srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
        srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
        pDevice->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&m_srvHeap));

        ImGui_ImplWin32_Init(hWnd);
        ImGui_ImplDX12_Init(pDevice, FRAME_BUFFER_COUNT,
            DXGI_FORMAT_R8G8B8A8_UNORM, m_srvHeap.Get(),
            m_srvHeap->GetCPUDescriptorHandleForHeapStart(),
            m_srvHeap->GetGPUDescriptorHandleForHeapStart());

    }

    // 終了処理
    void Finalize()
    {
        ImGui_ImplDX12_Shutdown();
        ImGui_ImplWin32_Shutdown();
        ImGui::DestroyContext();
    }

    // フレームの先頭で呼ぶ
    void NewFrame()
    {
        ImGui_ImplDX12_NewFrame();
        ImGui_ImplWin32_NewFrame();
        ImGui::NewFrame();
    }

    // 描画の最後に呼ぶ
    void Render(ID3D12GraphicsCommandList *pCommandList)
    {
        ImGui::Render();

        ID3D12DescriptorHeap* descriptorHeaps[] = { m_srvHeap.Get() };
        pCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
        ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), pCommandList);
    }

private:
    ComPtr<ID3D12DescriptorHeap> m_srvHeap;
};

これと、Windowのイベント処理関数の先頭に、Imguiのウィンドウ関数を追加します。

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    //  ImGuiのウィンドウ処理
    if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam)) {
        return true;
    }

これで、プロジェクトに以下のファイルを追加して、コンパイルしたら動きます。

試しに、フレームレートを表示しました。

// DebugWindow
ImGui::Begin("DebugWindow");
ImGui::Text("fps : %.2f", framerate);
ImGui::End();

便利ですね。

組み込み時にはまった点をメモしておきます。

(1) 描画時にimgui用のヒープを設定せずにエラーになった

ID3D12DescriptorHeap* descriptorHeaps[] = { m_srvHeap.Get() };
pCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), pCommandList);

(2) ウィンドウサイズを間違って設定して、ドットバイドットで描画されていなかった。

ウィンドウを作成するときのサイズが、枠とかも入れたサイズで、描画領域のサイズとずれていたために、imguiの計算とずれていました。

こんな感じでバッファのサイズを取得して修正しました。

    // バッファサイズからウィンドウのサイズを求める
    DWORD windowStyle = WS_OVERLAPPEDWINDOW;
    RECT rc = { 0, 0, INITIAL_WIDTH, INITIAL_HEIGHT };
    AdjustWindowRect(&rc, windowStyle, FALSE);
    HWND hWnd = CreateWindow(wcex.lpszClassName, "Direct3D 12 Sample", windowStyle, 100, 100, rc.right - rc.left, rc.bottom - rc.top, nullptr, nullptr, wcex.hInstance, nullptr);
// ウィンドウの描画領域のサイズをバッファのサイズに設定します
RECT rect;
GetClientRect(hWnd, &rect);
int sw = rect.right - rect.left;
int sh = rect.bottom - rect.top;