|
| 1 | +# class DearImGui |
| 2 | + |
| 3 | +Dear ImGui has its own initialization and loop, which we encapsulate into `class DearImGui`: |
| 4 | + |
| 5 | +```cpp |
| 6 | +struct DearImGuiCreateInfo { |
| 7 | + GLFWwindow* window{}; |
| 8 | + std::uint32_t api_version{}; |
| 9 | + vk::Instance instance{}; |
| 10 | + vk::PhysicalDevice physical_device{}; |
| 11 | + std::uint32_t queue_family{}; |
| 12 | + vk::Device device{}; |
| 13 | + vk::Queue queue{}; |
| 14 | + vk::Format color_format{}; // single color attachment. |
| 15 | + vk::SampleCountFlagBits samples{}; |
| 16 | +}; |
| 17 | + |
| 18 | +class DearImGui { |
| 19 | + public: |
| 20 | + using CreateInfo = DearImGuiCreateInfo; |
| 21 | + |
| 22 | + explicit DearImGui(CreateInfo const& create_info); |
| 23 | + |
| 24 | + void new_frame(); |
| 25 | + void end_frame(); |
| 26 | + void render(vk::CommandBuffer command_buffer) const; |
| 27 | + |
| 28 | + private: |
| 29 | + enum class State : std::int8_t { Ended, Begun }; |
| 30 | + |
| 31 | + struct Deleter { |
| 32 | + void operator()(vk::Device device) const; |
| 33 | + }; |
| 34 | + |
| 35 | + State m_state{}; |
| 36 | + |
| 37 | + Scoped<vk::Device, Deleter> m_device{}; |
| 38 | +}; |
| 39 | +``` |
| 40 | +
|
| 41 | +In the constructor, we start by creating the ImGui Context, loading Vulkan functions, and initializing GLFW for Vulkan: |
| 42 | +
|
| 43 | +```cpp |
| 44 | +IMGUI_CHECKVERSION(); |
| 45 | +ImGui::CreateContext(); |
| 46 | +
|
| 47 | +static auto const load_vk_func = +[](char const* name, void* user_data) { |
| 48 | + return VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr( |
| 49 | + *static_cast<vk::Instance*>(user_data), name); |
| 50 | +}; |
| 51 | +auto instance = create_info.instance; |
| 52 | +ImGui_ImplVulkan_LoadFunctions(create_info.api_version, load_vk_func, |
| 53 | + &instance); |
| 54 | +
|
| 55 | +if (!ImGui_ImplGlfw_InitForVulkan(create_info.window, true)) { |
| 56 | + throw std::runtime_error{"Failed to initialize Dear ImGui"}; |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +Then initialize Dear ImGui for Vulkan: |
| 61 | + |
| 62 | +```cpp |
| 63 | +auto init_info = ImGui_ImplVulkan_InitInfo{}; |
| 64 | +init_info.ApiVersion = create_info.api_version; |
| 65 | +init_info.Instance = create_info.instance; |
| 66 | +init_info.PhysicalDevice = create_info.physical_device; |
| 67 | +init_info.Device = create_info.device; |
| 68 | +init_info.QueueFamily = create_info.queue_family; |
| 69 | +init_info.Queue = create_info.queue; |
| 70 | +init_info.MinImageCount = 2; |
| 71 | +init_info.ImageCount = static_cast<std::uint32_t>(resource_buffering_v); |
| 72 | +init_info.MSAASamples = |
| 73 | + static_cast<VkSampleCountFlagBits>(create_info.samples); |
| 74 | +init_info.DescriptorPoolSize = 2; |
| 75 | +auto pipline_rendering_ci = vk::PipelineRenderingCreateInfo{}; |
| 76 | +pipline_rendering_ci.setColorAttachmentCount(1).setColorAttachmentFormats( |
| 77 | + create_info.color_format); |
| 78 | +init_info.PipelineRenderingCreateInfo = pipline_rendering_ci; |
| 79 | +init_info.UseDynamicRendering = true; |
| 80 | +if (!ImGui_ImplVulkan_Init(&init_info)) { |
| 81 | + throw std::runtime_error{"Failed to initialize Dear ImGui"}; |
| 82 | +} |
| 83 | +ImGui_ImplVulkan_CreateFontsTexture(); |
| 84 | +``` |
| 85 | + |
| 86 | +Since we are using an sRGB format and Dear ImGui is not color-space aware, we need to convert its style colors to linear space (so that they shift back to the original values by gamma correction): |
| 87 | + |
| 88 | +```cpp |
| 89 | +ImGui::StyleColorsDark(); |
| 90 | +// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) |
| 91 | +for (auto& colour : ImGui::GetStyle().Colors) { |
| 92 | + auto const linear = glm::convertSRGBToLinear( |
| 93 | + glm::vec4{colour.x, colour.y, colour.z, colour.w}); |
| 94 | + colour = ImVec4{linear.x, linear.y, linear.z, linear.w}; |
| 95 | +} |
| 96 | +ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.99f; // more opaque |
| 97 | +``` |
| 98 | + |
| 99 | +Finally, create the deleter and its implementation: |
| 100 | + |
| 101 | +```cpp |
| 102 | +m_device = Scoped<vk::Device, Deleter>{create_info.device}; |
| 103 | + |
| 104 | +// ... |
| 105 | +void DearImGui::Deleter::operator()(vk::Device const device) const { |
| 106 | + device.waitIdle(); |
| 107 | + ImGui_ImplVulkan_DestroyFontsTexture(); |
| 108 | + ImGui_ImplVulkan_Shutdown(); |
| 109 | + ImGui_ImplGlfw_Shutdown(); |
| 110 | + ImGui::DestroyContext(); |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +The remaining functions are straightforward: |
| 115 | + |
| 116 | +```cpp |
| 117 | +void DearImGui::new_frame() { |
| 118 | + if (m_state == State::Begun) { end_frame(); } |
| 119 | + ImGui_ImplGlfw_NewFrame(); |
| 120 | + ImGui_ImplVulkan_NewFrame(); |
| 121 | + ImGui::NewFrame(); |
| 122 | + m_state = State::Begun; |
| 123 | +} |
| 124 | + |
| 125 | +void DearImGui::end_frame() { |
| 126 | + if (m_state == State::Ended) { return; } |
| 127 | + ImGui::Render(); |
| 128 | + m_state = State::Ended; |
| 129 | +} |
| 130 | + |
| 131 | +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) |
| 132 | +void DearImGui::render(vk::CommandBuffer const command_buffer) const { |
| 133 | + auto* data = ImGui::GetDrawData(); |
| 134 | + if (data == nullptr) { return; } |
| 135 | + ImGui_ImplVulkan_RenderDrawData(data, command_buffer); |
| 136 | +} |
| 137 | +``` |
0 commit comments