Link

弃用警告

本文对于当前版本的 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 实现中,我们需要挂钩一些东西。VkInstanceVkPhysicalDeviceVkDevice、用于图形的 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 吧!

map