[Vulkan+GLFW] ImGUIの導入

#ImGuiとは
C++のGUIを作成するライブラリ(OSS)です。
例えばRenderingで何かを描画する際のデバッグに重宝し、すこしパラメータを変えるためにわざわざコンパイルし直さなくても、実行中に値を直接変えられるので便利です。その他にも、C++でのGUI開発環境を必要としている人には刺さりそうな機能だと思われます。
後にリンクを貼りますが、GitHubからインストールします。ただ導入にすこし癖があり、自分でwindowやRendererを用意しなければなりません。
たとえば(DirectX12+win32)や(Vulkan+GLFW)などの組み合わせが考えられます。
< ImGui GitHubより >
・Renderers: DirectX9, DirectX10, DirectX11, DirectX12, Metal, OpenGL/ES/ES2, Vulkan, WebGPU.
・Platforms: GLFW, SDL2, Win32, Glut, OSX, Android.
・Frameworks: Emscripten, Allegro5, Marmalade.

#出力例
<iframe width="560" height="315" src="https://www.youtube.com/embed/Ry1j408DpAk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

#準備
##ImGuiのインストール
以下のGitHubからdownload/cloneをしてインストールします。
https://github.com/ocornut/imgui

##インクルードとリンク
インストールが完了すればパスを通し、自分のプログラムにincludeします。私の環境では以下をincludeしました。
基本ヘッダー : imgui.h
glfw用ヘッダー : backends/imgui_impl_glfw.h
vulkan用ヘッダー : backends/imgui_impl_vulkan.h

同じ名前のソースファイル(.cpp)もあるので、リンクしておきます。

#README
インストールしたフォルダにexampleフォルダがあります。
さらにその配下のexample_Platform_Rendererのフォルダ内にmain.cppという公式の具体例が用意されているので、基本的にはそれを参照します。(例えばexample_glfw_vulkan/main.cpp)
以降の記事ではこうすれば動いたというのを紹介します。

#環境
ImGui自体はWindows/Linux/Macなどで動作させることができます。この記事では以下の私の環境で初期化するまでに必要だったことを書きます。この環境でない方は、先程の公式例のmain.cppを参考に導入してみてください。
・OS : Linux
・Platforms : GLFW
・Renderers : Vulkan

#前提知識
この記事の目的はImGuiの導入の記事であるため、VulkanやGLFWなどの基本的なGraphics APIの知識がある、つまりVulkan Instanceなどは作成できることを前提とします。Vulkanについては公式Khronosのtutorialのページを見るか、他のQiitaの記事などをご参考ください。

#Vulkan+GLFWでのコード
##Vulkan module
私はmain windowに描画したいgraphicsを出力させ、別のsub windowsにデバッグ用のImGuiを描画させるという目的で使用しています。ですのでこの場合、いくつかのVulkan moduleはmain windowとsub windowで共有できます。
< 共有したmodule >
・Vulkan Instance
・Vulkan Physical Device
・Vulkan Logical Device
・Renderer Queue
・Present Queue

< ImGui用に新たに作成したmodule >
・Window
・Surface
・Swapchain
・Depth Image
・Render Pass
・Frame Buffer
・Descriptor Pool
・Semaphores
・Command pool
・Command buffer

#初期化処理
初期化処理はrendering用のメインループの外で行います。
##init imgui

```c++
mContext = ImGui::CreateContext();
ImGui::SetCurrentContext(mContext);
ImGui_ImplGlfw_InitForVulkan(mWindow, true);
ImGui::StyleColorsDark();
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = instance;
init_info.PhysicalDevice = mPhysicalDevice;
init_info.Device = mDevice;
init_info.QueueFamily = mQueueFamilyIndex;
init_info.Queue = mQueue;
init_info.PipelineCache = VK_NULL_HANDLE;
init_info.DescriptorPool = mPool;
init_info.Allocator = VK_NULL_HANDLE;
init_info.MinImageCount = 2;
init_info.ImageCount = mSwapchainSize;
init_info.CheckVkResultFn = nullptr;
ImGui_ImplVulkan_Init(&init_info, mRenderPass);
```

##upload fonts

```c++
//command begin
VulkanCommand::BeginCommand(mCommandBuffer);
//impl fonts
ImGui_ImplVulkan_CreateFontsTexture(mCommandBuffer);
//command end
VulkanCommand::EndCommand(mCommandBuffer);
VkSubmitInfo end_info = {};
end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
end_info.commandBufferCount = 1;
end_info.pCommandBuffers = &mCommandBuffer;
vkQueueSubmit(mQueue, 1, &end_info, VK_NULL_HANDLE);
vkDeviceWaitIdle(mDevice);
ImGui_ImplVulkan_DestroyFontUploadObjects();
```
#メインループ内で行う処理
以下のrender, presentの関数をメインループ内で呼び出します。
##render

```c++
void Render(uint32_t index)
{
	ImGui::Render();
	ImDrawData* drawData = ImGui::GetDrawData();
	VulkanCommand::BeginCommand(mCommandBuffer);
	VulkanCommand::BeginRenderPass(index, mCommandBuffer, mFrameBuffers[index]);
	ImGui_ImplVulkan_RenderDrawData(drawData,mCommandBuffer);
	VulkanCommand::EndRenderPass(mCommandBuffer);
	VulkanCommand::EndCommand(mCommandBuffer);
}
```

##present
```c++
void Present(uint32_t index)
{
	uint32_t imageIndex;
	VkResult result = vkAcquireNextImageKHR(mDevice, mSwapchain,std::numeric_limits<uint64_t>::max(), 
                       mImageSemaphores[index], VK_NULL_HANDLE, &imageIndex);
	//submit info
	VkSemaphore waitSemaphores[] = {mImageSemaphores[index]};
	VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
	VkSemaphore signalSemaphores[] = { mRenderSemaphores[index]};
	VkSubmitInfo submitInfo0 = {};
	submitInfo0.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
	submitInfo0.waitSemaphoreCount = 1;
	submitInfo0.pWaitSemaphores = waitSemaphores;
	submitInfo0.pWaitDstStageMask = waitStages;
	submitInfo0.commandBufferCount = 1;
	submitInfo0.pCommandBuffers = mCommandBuffer;
	submitInfo0.signalSemaphoreCount = 1;
	submitInfo0.pSignalSemaphores = signalSemaphores;
	VkSubmitInfo submitInfo[] = {submitInfo0};
	vkResetFences(*mDevice->GetDevice(), 1, mFences[index]);
	if (vkQueueSubmit(mQueue, 1, submitInfo, mFences[index]) != VK_SUCCESS)
		std::runtime_error("failed to submit draw command buffer");
	//present info
    VkPresentInfoKHR info = {};
    info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    info.waitSemaphoreCount = 1;
    info.pWaitSemaphores = signalSemaphores;
    info.swapchainCount = 1;
    info.pSwapchains = mSwapchain;
    info.pImageIndices = &imageIndex;
    result = vkQueuePresentKHR(mQueuePresent, &info);
}
```

#メインループ

```c++
while(/*your condition*/){
    glfwPollEvents();
    : //other functions
	ImGui_ImplVulkan_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();
    ImGui::Begin("Parameters");
    if (ImGui::Button("reset time"))     
	{
        startTime = lastTime;
		*passedTime = 0.0f;
	}
	if (ImGui::Button("pause"))   
        paused = !paused;
	ImGui::SameLine();
	ImGui::Text("time = %.3f", *passedTime);
	ImGui::InputFloat("wave speed", waterSurface->GetWaveSpeed());
	ImGui::InputFloat("wave freq", waterSurface->GetWaveFreq());
	ImGui::InputFloat("wave amp", waterSurface->GetWaveAmp());
	ImGui::InputFloat("wave dz", waterSurface->GetWaveDz());
    ImGui::End();
	Render(index);
	Present(index);
    :  //other functions
}
```

#参考
以下のような波のrenderingのデバッグ用にImGuiを導入したので、設定パラメータにwave speedなどが入っています。

<iframe width="560" height="315" src="https://www.youtube.com/embed/6s4q_o5jgn4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>