app/imgui_backend.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | /// @file imgui_backend.cpp | ||
| 3 | /// @brief Creating the ImGui context, setting up its Vulkan backend and recording its draws. | ||
| 4 | #include "ernie/app/imgui_backend.h" | ||
| 5 | |||
| 6 | #include "ernie/util/profiler.h" | ||
| 7 | |||
| 8 | #include <imgui.h> | ||
| 9 | #include <imgui_impl_vulkan.h> | ||
| 10 | |||
| 11 | namespace ernie::app { | ||
| 12 | namespace { | ||
| 13 | |||
| 14 | 12 | void Transition(VkCommandBuffer commands, VkImage image, VkImageLayout from, VkImageLayout to, | |
| 15 | VkPipelineStageFlags2 srcStage, VkAccessFlags2 srcAccess, | ||
| 16 | VkPipelineStageFlags2 dstStage, VkAccessFlags2 dstAccess) { | ||
| 17 | 12 | const VkImageMemoryBarrier2 barrier{ | |
| 18 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, | ||
| 19 | .srcStageMask = srcStage, | ||
| 20 | .srcAccessMask = srcAccess, | ||
| 21 | .dstStageMask = dstStage, | ||
| 22 | .dstAccessMask = dstAccess, | ||
| 23 | .oldLayout = from, | ||
| 24 | .newLayout = to, | ||
| 25 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 26 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | ||
| 27 | .image = image, | ||
| 28 | 12 | .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; | |
| 29 | 12 | const VkDependencyInfo dependency{.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, | |
| 30 | .imageMemoryBarrierCount = 1, | ||
| 31 | 12 | .pImageMemoryBarriers = &barrier}; | |
| 32 | 12 | vkCmdPipelineBarrier2(commands, &dependency); | |
| 33 | 12 | } | |
| 34 | |||
| 35 | } // namespace | ||
| 36 | |||
| 37 | 8 | ImGuiSession::ImGuiSession(const Theme& theme) { | |
| 38 | 8 | ERNIE_ZONE("Set up ImGui"); | |
| 39 | 8 | IMGUI_CHECKVERSION(); | |
| 40 | 8 | context_ = ImGui::CreateContext(); | |
| 41 | 8 | ImGuiIO& io = ImGui::GetIO(); | |
| 42 | // Layouts are the viewer's to save; nothing is written behind its back. | ||
| 43 | 8 | io.IniFilename = nullptr; | |
| 44 | 8 | io.LogFilename = nullptr; | |
| 45 | 8 | io.ConfigFlags |= ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableKeyboard; | |
| 46 | 8 | io.Fonts->AddFontDefaultVector(); | |
| 47 | 8 | ApplyTheme(theme); | |
| 48 | 8 | } | |
| 49 | |||
| 50 | 8 | ImGuiSession::~ImGuiSession() { | |
| 51 | 8 | ImGui::DestroyContext(context_); | |
| 52 | 8 | } | |
| 53 | |||
| 54 | 2 | ImGuiRenderer::ImGuiRenderer(DeviceContext& context, VkFormat format, std::uint32_t imageCount) | |
| 55 | 2 | : context_(context) { | |
| 56 | 2 | ERNIE_ZONE("Set up ImGui's Vulkan backend"); | |
| 57 | 2 | ImGui_ImplVulkan_InitInfo info{}; | |
| 58 | 2 | info.ApiVersion = VK_API_VERSION_1_3; | |
| 59 | 2 | info.Instance = context.Instance(); | |
| 60 | 2 | info.PhysicalDevice = context.Physical(); | |
| 61 | 2 | info.Device = context.Device(); | |
| 62 | 2 | info.QueueFamily = context.Main().Family(); | |
| 63 | // Used only by texture uploads, which borrow the queue (`UpdateTextures`). | ||
| 64 | 2 | info.Queue = context.Main().Handle(); | |
| 65 | 2 | info.DescriptorPoolSize = 64; | |
| 66 | 2 | info.MinImageCount = imageCount; | |
| 67 | 2 | info.ImageCount = imageCount; | |
| 68 | 2 | info.UseDynamicRendering = true; | |
| 69 | 2 | info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; | |
| 70 | 2 | info.PipelineInfoMain.PipelineRenderingCreateInfo = { | |
| 71 | .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, | ||
| 72 | .colorAttachmentCount = 1, | ||
| 73 | .pColorAttachmentFormats = &format}; | ||
| 74 | 2 | info.PipelineCache = context.PipelineCache(); | |
| 75 | 2 | info.CheckVkResultFn = [](VkResult result) { CheckVk(result, "ImGui's Vulkan backend"); }; | |
| 76 | 2 | if (!ImGui_ImplVulkan_Init(&info)) { | |
| 77 | ✗ | throw DeviceError(DeviceErrorKind::Failed, "Could not set up ImGui's Vulkan backend"); | |
| 78 | } | ||
| 79 | 2 | } | |
| 80 | |||
| 81 | 2 | ImGuiRenderer::~ImGuiRenderer() { | |
| 82 | 4 | Quietly([this] { context_.Main().Last().Wait(); }); | |
| 83 | 2 | ImGui_ImplVulkan_Shutdown(); | |
| 84 | 2 | } | |
| 85 | |||
| 86 | 6 | void ImGuiRenderer::NewFrame() { | |
| 87 | 6 | ImGui_ImplVulkan_NewFrame(); | |
| 88 | 6 | } | |
| 89 | |||
| 90 | 4 | ImTextureID ImGuiRenderer::AddTexture(VkImageView view, VkSampler sampler) { | |
| 91 | 4 | return reinterpret_cast<ImTextureID>( | |
| 92 | 4 | ImGui_ImplVulkan_AddTexture(sampler, view, VK_IMAGE_LAYOUT_GENERAL)); | |
| 93 | } | ||
| 94 | |||
| 95 | 2 | void ImGuiRenderer::RemoveTexture(ImTextureID texture) { | |
| 96 | 2 | ImGui_ImplVulkan_RemoveTexture(reinterpret_cast<VkDescriptorSet>(texture)); | |
| 97 | 2 | } | |
| 98 | |||
| 99 | 6 | void ImGuiRenderer::UpdateTextures(ImDrawData& data) { | |
| 100 | 6 | if (data.Textures == nullptr) { | |
| 101 | return; | ||
| 102 | } | ||
| 103 | 12 | for (ImTextureData* texture : *data.Textures) { | |
| 104 | 6 | if (texture->Status != ImTextureStatus_OK) { | |
| 105 | // The font atlas, on the first frame and when it grows; the backend submits the | ||
| 106 | // upload and waits for it itself. | ||
| 107 | 2 | ERNIE_ZONE("Upload a UI texture"); | |
| 108 | 4 | context_.Main().Borrow([&](VkQueue) { ImGui_ImplVulkan_UpdateTexture(texture); }); | |
| 109 | 2 | } | |
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | 6 | void ImGuiRenderer::Record(VkCommandBuffer commands, ImDrawData& data, const FrameTarget& target, | |
| 114 | const glm::vec4& clear) { | ||
| 115 | // Whatever the image held is replaced; earlier work on it, a previous frame's, finishes first. | ||
| 116 | 6 | Transition(commands, target.image, VK_IMAGE_LAYOUT_UNDEFINED, | |
| 117 | VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, | ||
| 118 | VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, | ||
| 119 | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT); | ||
| 120 | 6 | const VkRenderingAttachmentInfo attachment{ | |
| 121 | .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, | ||
| 122 | 6 | .imageView = target.view, | |
| 123 | .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, | ||
| 124 | .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, | ||
| 125 | .storeOp = VK_ATTACHMENT_STORE_OP_STORE, | ||
| 126 | 6 | .clearValue = {.color = {.float32 = {clear.r, clear.g, clear.b, clear.a}}}}; | |
| 127 | 6 | const VkRenderingInfo rendering{.sType = VK_STRUCTURE_TYPE_RENDERING_INFO, | |
| 128 | 6 | .renderArea = {{0, 0}, {target.extent.x, target.extent.y}}, | |
| 129 | .layerCount = 1, | ||
| 130 | .colorAttachmentCount = 1, | ||
| 131 | 6 | .pColorAttachments = &attachment}; | |
| 132 | 6 | vkCmdBeginRendering(commands, &rendering); | |
| 133 | 6 | ImGui_ImplVulkan_RenderDrawData(&data, commands); | |
| 134 | 6 | vkCmdEndRendering(commands); | |
| 135 | 6 | Transition(commands, target.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, target.finalLayout, | |
| 136 | VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, | ||
| 137 | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, | ||
| 138 | kAnyAccess); | ||
| 139 | 6 | } | |
| 140 | |||
| 141 | } // namespace ernie::app | ||
| 142 |