Skip to content

ImGui

在Win32API+DX12项目中引入ImGui

参考代码:在Win32API+DX12项目中引入ImGui

1. 添加ImGui依赖

我们使用vcpkg进行包管理,在项目根目录下的vcpkg.json文件的"dependencies"字段中添加以下内容:

1
2
3
4
{
  "name": "imgui",
  "features": ["win32-binding","dx12-binding"]
}

随后,在项目的cmakeList.txt文件中找到target_link_libraries命令,进行以下修改:

#在相邻的上方添加该行
find_package(imgui CONFIG REQUIRED)

target_link_libraries(UmbraForward PRIVATE
    Microsoft::DirectX-Headers
    d3d12.lib
    dxgi.lib
    d3dcompiler.lib
    dxguid.lib
    imgui::imgui  #在命令内添加该行
)

2. 添加SRV描述符堆并绑定到渲染管线

包含文件

在代码文件中添加imgui相关内容时需要包含以下头文件:

1
2
3
#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx12.h"

由于ImGui需要加载字体纹理,在dx12中需要使用到SRV描述符堆。如果不存在,需要创建对应的描述符堆。

找到创建RTV描述符堆的代码并在下方添加:

//create description heap for rtv.
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
//...创建rtv描述符堆的描述和根据描述创建描述符堆的逻辑...

D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
//目前资源数量为1,包含且仅包含ImGui使用的字体
srvHeapDesc.NumDescriptors = 1;
//声明这是一个CBV、SRV或UAV的描述符堆
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;   
//这个描述符堆对Shader是可见的
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&m_srvHeap)));

3. 拦截Win32窗口消息

以处理鼠标点击、拖动等事件。

Win32Application.cpp文件中找到Win32Application::WindowProc方法的实现,进行以下修改:

//在WindowProc方法的实现上方添加本行,对该方法进行前向声明
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); 

LRESULT CALLBACK Win32Application::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    //添加拦截逻辑
    // 如果 ImGui 需要处理当前输入,则直接返回 true 拦截消息
    if (ImGui_ImplWin32_WndProcHandler(hWnd, message, wParam, lParam))
        return true; 

    RenderAppBase* pSample = reinterpret_cast<RenderAppBase*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
    //……其它原本逻辑……
}

4. ImGui组件生命周期管理

  1. 初始化

    OnInit()LoadAssets()方法执行完后,执行以下内容初始化imgui上下文:

    void SampleTriangle::LoadImgui(){
       // 检查版本并创建上下文
       IMGUI_CHECKVERSION(); 
       ImGui::CreateContext(); 
    
       // 配置键鼠支持
       ImGuiIO& io = ImGui::GetIO(); (void)io; 
       io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 启用键盘控制[cite: 1]
       io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;  // 启用手柄控制[cite: 1]
       ImGui::StyleColorsDark(); // 设置暗黑主题风格[cite: 1]
    
       // 初始化 Win32 后端
       ImGui_ImplWin32_Init(Win32Application::GetHwnd()); 
    
       // 配置 DX12 初始化参数结构体
       ImGui_ImplDX12_InitInfo init_info = {}; 
       init_info.Device = m_device.Get(); 
       init_info.CommandQueue = m_commandQueue.Get(); 
       init_info.NumFramesInFlight = FrameCount; 
       init_info.RTVFormat = DXGI_FORMAT_R8G8B8A8_UNORM; 
       init_info.DSVFormat = DXGI_FORMAT_UNKNOWN; 
       init_info.SrvDescriptorHeap = m_srvHeap.Get(); 
    
       // 提供 SRV 描述符分配/释放的回调函数 (ImGui 1.91.6 之后的规范)
       init_info.SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo* info, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_handle) { 
           * out_cpu_handle = info->SrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(); 
           * out_gpu_handle = info->SrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart(); 
       };
       init_info.SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE) {}; 
    
       // 初始化 DX12 后端
       ImGui_ImplDX12_Init(&init_info); 
    }
    
  2. 帧更新(Update)

    这些逻辑在OnUpdate()中调用,用于通知ImGui开启新的一帧,创建所需的ui组件。

    void SampleTriangle::UpdateImgui() {
       //about imgui component update.
       // 开启 Dear ImGui 的新帧
       ImGui_ImplDX12_NewFrame();
       ImGui_ImplWin32_NewFrame();
       ImGui::NewFrame();
    
       // 创建一个基础窗口
       ImGui::Begin("Umbra Forward Control Panel");
       ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
       ImGui::End();
    
       // 将上面声明的 UI 逻辑烘焙为底层的绘制数据
       ImGui::Render();
    }
    
  3. 帧更新(Render)

    Update中仅仅只是更新了绘制的数据,现在还需要将其添加到命令队列进行渲染。

    OnRender完成全部主要绘制后,切换rtv状态为呈现、关闭命令列表前,向命令列表中添加绘制ImGui组件的命令。

    1
    2
    3
    4
    5
    6
    //draw imgui component.
    // 绑定供 ImGui 字体使用的 SRV 描述符堆
    ID3D12DescriptorHeap* descriptorHeaps[] = { m_srvHeap.Get() };
    m_commandList->SetDescriptorHeaps(1, descriptorHeaps); 
    // 渲染 ImGui 收集到的绘制数据
    ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), m_commandList.Get()); 
    
  4. 销毁(destroy)

    OnDestroy() 中,确保在退出程序时销毁 ImGui 上下文以防止内存泄漏。

    1
    2
    3
    4
    5
    6
    7
    8
    //destroy imgui
    // 确保 GPU 已经完成了所有的工作 (等待 Fence)
    WaitForPreviousFrame();
    
    // 卸载后端并销毁 ImGui 上下文
    ImGui_ImplDX12_Shutdown(); 
    ImGui_ImplWin32_Shutdown(); 
    ImGui::DestroyContext();