弃用警告
本文对于当前版本的 vkguide 已过时,仅适用于旧版 Vkguide。本额外章节的内容现已包含在主教程中。
Dear IMGui
https://github.com/ocornut/imgui
Dear Imgui 是目前最好的调试用户界面库之一。使用它可以非常容易地创建包含各种小部件的调试窗口。本指南将使用第 5 章代码中的一些内容,但完全可以独立完成。
Imgui 本身是一个可移植的库,但它本身不做任何用户交互或渲染,你需要将其挂钩到你的渲染器或输入系统中。该库附带一组示例实现,可以将这些挂钩到 imgui 中。我们使用 Vulkan 进行渲染,SDL 用于用户输入事件。这两个都在示例实现中涵盖,因此我们将使用这些实现。
编译
如果你正在使用教程代码,那么已经有一个 imgui Cmake 目标可以编译 imgui。如果你没有使用教程代码,则需要将其添加到你的构建系统中。我们需要编译的文件是
target_sources(imgui PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui.h"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_demo.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_draw.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_widgets.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_impl_vulkan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/imgui_impl_sdl.cpp"
)
还需要将 imgui 目录添加到包含路径中。在这里,我们添加了核心 imgui 库代码 (imgui.cpp, demo.cpp, draw.cpp, widgets.cpp),以及 Vulkan 和 SDL 的实现。
确保你从 cmake 或构建系统链接到该 imgui 目标。你也可以直接将这些文件添加到主可执行文件的构建中。
初始化 Imgui
在使用 Imgui 之前,我们需要初始化一些东西。为此,创建一个 init_imgui()
函数,并确保将其作为初始化的一部分调用。必须在 vulkan 完全初始化之后。
void VulkanEngine::init_imgui()
{
//1: create descriptor pool for IMGUI
// the size of the pool is very oversize, but it's copied from imgui demo itself.
VkDescriptorPoolSize pool_sizes[] =
{
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1000 },
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 },
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 },
{ VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 }
};
VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
pool_info.maxSets = 1000;
pool_info.poolSizeCount = std::size(pool_sizes);
pool_info.pPoolSizes = pool_sizes;
VkDescriptorPool imguiPool;
VK_CHECK(vkCreateDescriptorPool(_device, &pool_info, nullptr, &imguiPool));
// 2: initialize imgui library
//this initializes the core structures of imgui
ImGui::CreateContext();
//this initializes imgui for SDL
ImGui_ImplSDL2_InitForVulkan(_window);
//this initializes imgui for Vulkan
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = _instance;
init_info.PhysicalDevice = _chosenGPU;
init_info.Device = _device;
init_info.Queue = _graphicsQueue;
init_info.DescriptorPool = imguiPool;
init_info.MinImageCount = 3;
init_info.ImageCount = 3;
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
ImGui_ImplVulkan_Init(&init_info, _renderPass);
//execute a gpu command to upload imgui font textures
immediate_submit([&](VkCommandBuffer cmd) {
ImGui_ImplVulkan_CreateFontsTexture(cmd);
});
//clear font textures from cpu data
ImGui_ImplVulkan_DestroyFontUploadObjects();
//add the destroy the imgui created structures
_mainDeletionQueue.push_function([=]() {
vkDestroyDescriptorPool(_device, imguiPool, nullptr);
ImGui_ImplVulkan_Shutdown();
});
}
我们首先创建一个 imgui 需要的描述符池。为 imgui 单独设置一个描述符池是最简单的。虽然这个包含 1000 个各种类型的描述符池可能过大,但这无关紧要。
然后,我们需要调用初始化 imgui 本身以及 Vulkan 和 SDL 实现的函数。在 Vulkan 实现中,我们需要挂钩一些东西。VkInstance
、VkPhysicalDevice
、VkDevice
、用于图形的 VkQueue
以及我们刚刚创建的描述符池。图像计数用于命令的重叠。使用与创建交换链时相同的变量。
一旦 imgui 初始化完成,最后一件事是执行命令上传字体。在这里,我们使用了我们在第 5 章中做的立即提交 lambda,但你可以用引擎中任何阻塞方式执行命令的方法替换它。一旦命令完全完成执行(立即提交在 fence 上阻塞),你可以销毁字体上传对象以清除暂存缓冲区。然后,你添加描述符池和 imgui vulkan 本身的删除。
挂钩 Imgui
在 imgui 初始化后,我们现在将其挂钩到引擎的主循环中。
//main loop
while (!bQuit)
{
//Handle events on queue
while (SDL_PollEvent(&e) != 0)
{
ImGui_ImplSDL2_ProcessEvent(&e);
//other event handling
}
//imgui new frame
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL2_NewFrame(_window);
ImGui::NewFrame();
//imgui commands
ImGui::ShowDemoWindow();
//your draw function
draw();
}
我们首先为 Vulkan、SDL 和基础库调用 NewFrame 函数,之后,我们可以开始执行 imgui 的操作。你可以在 Imgui::NewFrame()
和调用渲染函数之间的任何时间点调用 imgui 函数。
最后一件事是渲染 imgui 对象。
在 draw()
函数中,我们在开始时调用 ImGui::Render();
。
作为主渲染通道的一部分,在结束之前调用 ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd);
。这将使 imgui 作为主通道的一部分进行渲染。如果你有某种 UI 通道,那么这是一个放置它的好地方。
这就是真正需要做的全部,尽情享受使用 imgui 吧!