#include "VulkanUIOverlay.h" /* * UI overlay class using ImGui * * Copyright (C) 2017 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #include "VulkanUIOverlay.h" namespace vks { UIOverlay::UIOverlay(vks::VulkanDevice *vulkanDevice, VkQueue copyQueue, std::vector &framebuffers, VkFormat colorformat, VkFormat depthformat, uint32_t *framebufferwidth, uint32_t *framebufferheight, std::vector shaderstages) { this->device = vulkanDevice; this->copyQueue = copyQueue; this->colorFormat = colorformat; this->depthFormat = depthformat; this->frameBuffers.resize(framebuffers.size()); for (uint32_t i = 0; i < framebuffers.size(); i++) { this->frameBuffers[i] = &framebuffers[i]; } this->shaderStages = shaderstages; this->frameBufferWidth = framebufferwidth; this->frameBufferHeight = framebufferheight; #if defined(__ANDROID__) if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) { scale = 3.0f; } else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) { scale = 2.25f; } else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) { scale = 1.875f; }; #endif // Init ImGui // Color scheme ImGuiStyle& style = ImGui::GetStyle(); style.Colors[ImGuiCol_TitleBg] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); style.Colors[ImGuiCol_TitleBgActive] = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.0f, 0.0f, 0.0f, 0.1f); style.Colors[ImGuiCol_MenuBarBg] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f); style.Colors[ImGuiCol_Header] = ImVec4(0.8f, 0.0f, 0.0f, 0.4f); style.Colors[ImGuiCol_HeaderActive] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f); style.Colors[ImGuiCol_HeaderHovered] = ImVec4(1.0f, 0.0f, 0.0f, 0.4f); style.Colors[ImGuiCol_CheckMark] = ImVec4(1.0f, 0.0f, 0.0f, 0.8f); // Dimensions ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2((float)(*framebufferwidth), (float)(*framebufferheight)); io.FontGlobalScale = scale; cmdBuffers.resize(framebuffers.size()); prepareResources(); prepareRenderPass(); preparePipeline(); } /** Free up all Vulkan resources acquired by the UI overlay */ UIOverlay::~UIOverlay() { vertexBuffer.destroy(); indexBuffer.destroy(); vkDestroyImageView(device->logicalDevice, fontView, nullptr); vkDestroyImage(device->logicalDevice, fontImage, nullptr); vkFreeMemory(device->logicalDevice, fontMemory, nullptr); vkDestroySampler(device->logicalDevice, sampler, nullptr); vkDestroyDescriptorSetLayout(device->logicalDevice, descriptorSetLayout, nullptr); vkDestroyDescriptorPool(device->logicalDevice, descriptorPool, nullptr); vkDestroyPipelineLayout(device->logicalDevice, pipelineLayout, nullptr); vkDestroyPipelineCache(device->logicalDevice, pipelineCache, nullptr); vkDestroyPipeline(device->logicalDevice, pipeline, nullptr); vkDestroyRenderPass(device->logicalDevice, renderPass, nullptr); vkFreeCommandBuffers(device->logicalDevice, commandPool, static_cast(cmdBuffers.size()), cmdBuffers.data()); vkDestroyCommandPool(device->logicalDevice, commandPool, nullptr); vkDestroyFence(device->logicalDevice, fence, nullptr); } /** Prepare all vulkan resources required to render the UI overlay */ void UIOverlay::prepareResources() { ImGuiIO& io = ImGui::GetIO(); // Create font texture unsigned char* fontData; int texWidth, texHeight; io.Fonts->GetTexDataAsRGBA32(&fontData, &texWidth, &texHeight); VkDeviceSize uploadSize = texWidth*texHeight * 4 * sizeof(char); // Create target image for copy VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo(); imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; imageInfo.extent.width = texWidth; imageInfo.extent.height = texHeight; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = 1; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageInfo, nullptr, &fontImage)); VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(device->logicalDevice, fontImage, &memReqs); VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &fontMemory)); VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, fontImage, fontMemory, 0)); // Image view VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo(); viewInfo.image = fontImage; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.layerCount = 1; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewInfo, nullptr, &fontView)); // Staging buffers for font data upload vks::Buffer stagingBuffer; VK_CHECK_RESULT(device->createBuffer( VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, uploadSize)); stagingBuffer.map(); memcpy(stagingBuffer.mapped, fontData, uploadSize); stagingBuffer.unmap(); // Copy buffer data to font image VkCommandBuffer copyCmd = device->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); // Prepare for transfer vks::tools::setImageLayout( copyCmd, fontImage, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // Copy VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = texWidth; bufferCopyRegion.imageExtent.height = texHeight; bufferCopyRegion.imageExtent.depth = 1; vkCmdCopyBufferToImage( copyCmd, stagingBuffer.buffer, fontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion ); // Prepare for shader read vks::tools::setImageLayout( copyCmd, fontImage, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); device->flushCommandBuffer(copyCmd, copyQueue, true); stagingBuffer.destroy(); // Font texture Sampler VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo(); samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerInfo, nullptr, &sampler)); // Command buffer VkCommandPoolCreateInfo cmdPoolInfo = {}; cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmdPoolInfo.queueFamilyIndex = device->queueFamilyIndices.graphics; cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VK_CHECK_RESULT(vkCreateCommandPool(device->logicalDevice, &cmdPoolInfo, nullptr, &commandPool)); VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast(cmdBuffers.size())); VK_CHECK_RESULT(vkAllocateCommandBuffers(device->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data())); // Descriptor pool std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1) }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool)); // Descriptor set layout std::vector setLayoutBindings = { vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0), }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device->logicalDevice, &descriptorLayout, nullptr, &descriptorSetLayout)); // Descriptor set VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device->logicalDevice, &allocInfo, &descriptorSet)); VkDescriptorImageInfo fontDescriptor = vks::initializers::descriptorImageInfo( sampler, fontView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ); std::vector writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &fontDescriptor) }; vkUpdateDescriptorSets(device->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); // Pipeline cache VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; VK_CHECK_RESULT(vkCreatePipelineCache(device->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); // Pipeline layout // Push constants for UI rendering parameters VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstBlock), 0); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; VK_CHECK_RESULT(vkCreatePipelineLayout(device->logicalDevice, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); // Command buffer execution fence VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(); VK_CHECK_RESULT(vkCreateFence(device->logicalDevice, &fenceCreateInfo, nullptr, &fence)); } /** Prepare a separate pipeline for the UI overlay rendering decoupled from the main application */ void UIOverlay::preparePipeline() { // Setup graphics pipeline for UI rendering VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE); // Enable blending VkPipelineColorBlendAttachmentState blendAttachmentState{}; blendAttachmentState.blendEnable = VK_TRUE; blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT); std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass); pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.pMultisampleState = &multisampleState; pipelineCreateInfo.pViewportState = &viewportState; pipelineCreateInfo.pDepthStencilState = &depthStencilState; pipelineCreateInfo.pDynamicState = &dynamicState; pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); pipelineCreateInfo.pStages = shaderStages.data(); // Vertex bindings an attributes based on ImGui vertex definition std::vector vertexInputBindings = { vks::initializers::vertexInputBindingDescription(0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX), }; std::vector vertexInputAttributes = { vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, pos)), // Location 0: Position vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(ImDrawVert, uv)), // Location 1: UV vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R8G8B8A8_UNORM, offsetof(ImDrawVert, col)), // Location 0: Color }; VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); pipelineCreateInfo.pVertexInputState = &vertexInputState; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); } /** Prepare a separate render pass for rendering the text as an overlay */ void UIOverlay::prepareRenderPass() { VkAttachmentDescription attachments[2] = {}; // Color attachment attachments[0].format = colorFormat; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; // Don't clear the framebuffer (like the renderpass from the example does) attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // Depth attachment attachments[1].format = depthFormat; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentReference colorReference = {}; colorReference.attachment = 0; colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference depthReference = {}; depthReference.attachment = 1; depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkSubpassDependency subpassDependencies[2] = {}; // Transition from final to initial (VK_SUBPASS_EXTERNAL refers to all commmands executed outside of the actual renderpass) subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; subpassDependencies[0].dstSubpass = 0; subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; subpassDependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; subpassDependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; // Transition from initial to final subpassDependencies[1].srcSubpass = 0; subpassDependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; subpassDependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; subpassDependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; subpassDependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; subpassDependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; subpassDependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; VkSubpassDescription subpassDescription = {}; subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpassDescription.flags = 0; subpassDescription.inputAttachmentCount = 0; subpassDescription.pInputAttachments = NULL; subpassDescription.colorAttachmentCount = 1; subpassDescription.pColorAttachments = &colorReference; subpassDescription.pResolveAttachments = NULL; subpassDescription.pDepthStencilAttachment = &depthReference; subpassDescription.preserveAttachmentCount = 0; subpassDescription.pPreserveAttachments = NULL; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.pNext = NULL; renderPassInfo.attachmentCount = 2; renderPassInfo.pAttachments = attachments; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpassDescription; renderPassInfo.dependencyCount = 2; renderPassInfo.pDependencies = subpassDependencies; VK_CHECK_RESULT(vkCreateRenderPass(device->logicalDevice, &renderPassInfo, nullptr, &renderPass)); } /** Update the command buffers to reflect text changes */ void UIOverlay::updateCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); renderPassBeginInfo.renderPass = renderPass; renderPassBeginInfo.renderArea.extent.width = *frameBufferWidth; renderPassBeginInfo.renderArea.extent.height = *frameBufferHeight; // None of the attachments will be cleared renderPassBeginInfo.clearValueCount = 0; renderPassBeginInfo.pClearValues = nullptr; ImGuiIO& io = ImGui::GetIO(); for (size_t i = 0; i < cmdBuffers.size(); ++i) { renderPassBeginInfo.framebuffer = *frameBuffers[i]; VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffers[i], &cmdBufInfo)); if (vks::debugmarker::active) { vks::debugmarker::beginRegion(cmdBuffers[i], "UI overlay", glm::vec4(1.0f, 0.94f, 0.3f, 1.0f)); } vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindDescriptorSets(cmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &vertexBuffer.buffer, offsets); vkCmdBindIndexBuffer(cmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT16); VkViewport viewport = vks::initializers::viewport(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, 1.0f); vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport); VkRect2D scissor = vks::initializers::rect2D((int32_t)ImGui::GetIO().DisplaySize.x, (int32_t)ImGui::GetIO().DisplaySize.y, 0, 0); vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor); // UI scale and translate via push constants pushConstBlock.scale = glm::vec2(2.0f / io.DisplaySize.x, 2.0f / io.DisplaySize.y); pushConstBlock.translate = glm::vec2(-1.0f); vkCmdPushConstants(cmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstBlock), &pushConstBlock); // Render commands ImDrawData* imDrawData = ImGui::GetDrawData(); int32_t vertexOffset = 0; int32_t indexOffset = 0; for (int32_t j = 0; j < imDrawData->CmdListsCount; j++) { const ImDrawList* cmd_list = imDrawData->CmdLists[j]; for (int32_t k = 0; k < cmd_list->CmdBuffer.Size; k++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[k]; VkRect2D scissorRect; scissorRect.offset.x = std::max((int32_t)(pcmd->ClipRect.x), 0); scissorRect.offset.y = std::max((int32_t)(pcmd->ClipRect.y), 0); scissorRect.extent.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x); scissorRect.extent.height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y); vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissorRect); vkCmdDrawIndexed(cmdBuffers[i], pcmd->ElemCount, 1, indexOffset, vertexOffset, 0); indexOffset += pcmd->ElemCount; } vertexOffset += cmd_list->VtxBuffer.Size; } vkCmdEndRenderPass(cmdBuffers[i]); if (vks::debugmarker::active) { vks::debugmarker::endRegion(cmdBuffers[i]); } VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i])); } } /** Update vertex and index buffer containing the imGui elements when required */ void UIOverlay::update() { ImDrawData* imDrawData = ImGui::GetDrawData(); bool updateCmdBuffers = false; if (!imDrawData) { return; }; // Note: Alignment is done inside buffer creation VkDeviceSize vertexBufferSize = imDrawData->TotalVtxCount * sizeof(ImDrawVert); VkDeviceSize indexBufferSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx); // Update buffers only if vertex or index count has been changed compared to current buffer size // Vertex buffer if ((vertexBuffer.buffer == VK_NULL_HANDLE) || (vertexCount != imDrawData->TotalVtxCount)) { vertexBuffer.unmap(); vertexBuffer.destroy(); VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &vertexBuffer, vertexBufferSize)); vertexCount = imDrawData->TotalVtxCount; vertexBuffer.unmap(); vertexBuffer.map(); updateCmdBuffers = true; } // Index buffer VkDeviceSize indexSize = imDrawData->TotalIdxCount * sizeof(ImDrawIdx); if ((indexBuffer.buffer == VK_NULL_HANDLE) || (indexCount < imDrawData->TotalIdxCount)) { indexBuffer.unmap(); indexBuffer.destroy(); VK_CHECK_RESULT(device->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &indexBuffer, indexBufferSize)); indexCount = imDrawData->TotalIdxCount; indexBuffer.map(); updateCmdBuffers = true; } // Upload data ImDrawVert* vtxDst = (ImDrawVert*)vertexBuffer.mapped; ImDrawIdx* idxDst = (ImDrawIdx*)indexBuffer.mapped; for (int n = 0; n < imDrawData->CmdListsCount; n++) { const ImDrawList* cmd_list = imDrawData->CmdLists[n]; memcpy(vtxDst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); memcpy(idxDst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); vtxDst += cmd_list->VtxBuffer.Size; idxDst += cmd_list->IdxBuffer.Size; } // Flush to make writes visible to GPU vertexBuffer.flush(); indexBuffer.flush(); if (updateCmdBuffers) { updateCommandBuffers(); } } /** Submit the overlay command buffers to a queue */ void UIOverlay::submit(VkQueue queue, uint32_t bufferindex, VkSubmitInfo submitInfo) { if (!visible) { return; } submitInfo.pCommandBuffers = &cmdBuffers[bufferindex]; submitInfo.commandBufferCount = 1; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence)); VK_CHECK_RESULT(vkWaitForFences(device->logicalDevice, 1, &fence, VK_TRUE, UINT64_MAX)); VK_CHECK_RESULT(vkResetFences(device->logicalDevice, 1, &fence)); } /** Reallocate command buffers for the text overlay */ void UIOverlay::reallocateCommandBuffers() { vkFreeCommandBuffers(device->logicalDevice, commandPool, static_cast(cmdBuffers.size()), cmdBuffers.data()); VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast(cmdBuffers.size())); VK_CHECK_RESULT(vkAllocateCommandBuffers(device->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data())); } bool UIOverlay::header(const char *caption) { return ImGui::CollapsingHeader(caption, ImGuiTreeNodeFlags_DefaultOpen); } bool UIOverlay::checkBox(const char *caption, bool *value) { return ImGui::Checkbox(caption, value); } bool UIOverlay::checkBox(const char *caption, int32_t *value) { bool val = (*value == 1); bool res = ImGui::Checkbox(caption, &val); *value = val; return res; } bool UIOverlay::inputFloat(const char *caption, float *value, float step, uint32_t precision) { return ImGui::InputFloat(caption, value, step, step * 10.0f, precision); } bool UIOverlay::sliderFloat(const char* caption, float* value, float min, float max) { return ImGui::SliderFloat(caption, value, min, max); } bool UIOverlay::sliderInt(const char* caption, int32_t* value, int32_t min, int32_t max) { return ImGui::SliderInt(caption, value, min, max); } bool UIOverlay::comboBox(const char *caption, int32_t *itemindex, std::vector items) { if (items.empty()) { return false; } std::vector charitems; charitems.reserve(items.size()); for (size_t i = 0; i < items.size(); i++) { charitems.push_back(items[i].c_str()); } uint32_t itemCount = static_cast(charitems.size()); return ImGui::Combo(caption, itemindex, &charitems[0], itemCount, itemCount); } bool UIOverlay::button(const char *caption) { return ImGui::Button(caption); } void UIOverlay::text(const char *formatstr, ...) { va_list args; va_start(args, formatstr); ImGui::TextV(formatstr, args); va_end(args); } }