From f6af0bde03e3146be23b16dbf990eb75c09fb342 Mon Sep 17 00:00:00 2001 From: saschawillems Date: Sun, 29 Oct 2017 11:41:43 +0100 Subject: [PATCH] Replaced text overlay with proper (imgui based) UI overlay class --- base/CMakeLists.txt | 4 +- base/VulkanTextOverlay.hpp | 746 --------------------------- base/VulkanUIOverlay.cpp | 555 ++++++++++++++++++++ base/VulkanUIOverlay.h | 90 ++++ base/vulkanexamplebase.cpp | 103 ++-- base/vulkanexamplebase.h | 14 +- data/shaders/base/uioverlay.frag | 13 + data/shaders/base/uioverlay.frag.spv | Bin 0 -> 664 bytes data/shaders/base/uioverlay.vert | 25 + data/shaders/base/uioverlay.vert.spv | Bin 0 -> 1252 bytes 10 files changed, 743 insertions(+), 807 deletions(-) delete mode 100644 base/VulkanTextOverlay.hpp create mode 100644 base/VulkanUIOverlay.cpp create mode 100644 base/VulkanUIOverlay.h create mode 100644 data/shaders/base/uioverlay.frag create mode 100644 data/shaders/base/uioverlay.frag.spv create mode 100644 data/shaders/base/uioverlay.vert create mode 100644 data/shaders/base/uioverlay.vert.spv diff --git a/base/CMakeLists.txt b/base/CMakeLists.txt index 8677b252..6b759cfd 100644 --- a/base/CMakeLists.txt +++ b/base/CMakeLists.txt @@ -1,5 +1,5 @@ -file(GLOB BASE_SRC *.cpp) -file(GLOB BASE_HEADERS *.hpp) +file(GLOB BASE_SRC "*.cpp" "../external/imgui/imgui.cpp" "../external/imgui/imgui_draw.cpp") +file(GLOB BASE_HEADERS "*.hpp") if(WIN32) add_library(base STATIC ${BASE_SRC}) diff --git a/base/VulkanTextOverlay.hpp b/base/VulkanTextOverlay.hpp deleted file mode 100644 index 3c749818..00000000 --- a/base/VulkanTextOverlay.hpp +++ /dev/null @@ -1,746 +0,0 @@ -/* -* Text overlay class for displaying debug information -* -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "VulkanTools.h" -#include "VulkanDebug.h" -#include "VulkanBuffer.hpp" -#include "VulkanDevice.hpp" - -#if defined(__ANDROID__) -#include "VulkanAndroid.h" -#endif - -#include "../external/stb/stb_font_consolas_24_latin1.inl" - -// Defines for the STB font used -// STB font files can be found at http://nothings.org/stb/font/ -#define STB_FONT_NAME stb_font_consolas_24_latin1 -#define STB_FONT_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH -#define STB_FONT_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT -#define STB_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR -#define STB_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS - -// Max. number of chars the text overlay buffer can hold -#define MAX_CHAR_COUNT 1024 - -/** -* @brief Mostly self-contained text overlay class -* @note Will only work with compatible render passes -*/ -class VulkanTextOverlay -{ -private: - vks::VulkanDevice *vulkanDevice; - - VkQueue queue; - VkFormat colorFormat; - VkFormat depthFormat; - - uint32_t *frameBufferWidth; - uint32_t *frameBufferHeight; - - VkSampler sampler; - VkImage image; - VkImageView view; - vks::Buffer vertexBuffer; - VkDeviceMemory imageMemory; - VkDescriptorPool descriptorPool; - VkDescriptorSetLayout descriptorSetLayout; - VkDescriptorSet descriptorSet; - VkPipelineLayout pipelineLayout; - VkPipelineCache pipelineCache; - VkPipeline pipeline; - VkRenderPass renderPass; - VkCommandPool commandPool; - std::vector frameBuffers; - std::vector shaderStages; - VkFence fence; - - // Used during text updates - glm::vec4 *mappedLocal = nullptr; - - stb_fontchar stbFontData[STB_NUM_CHARS]; - uint32_t numLetters; - -public: - - enum TextAlign { alignLeft, alignCenter, alignRight }; - - bool visible = true; - bool invalidated = false; - - float scale = 1.0f; - - std::vector cmdBuffers; - - /** - * Default constructor - * - * @param vulkanDevice Pointer to a valid VulkanDevice - */ - VulkanTextOverlay( - vks::VulkanDevice *vulkanDevice, - VkQueue queue, - std::vector &framebuffers, - VkFormat colorformat, - VkFormat depthformat, - uint32_t *framebufferwidth, - uint32_t *framebufferheight, - std::vector shaderstages) - { - this->vulkanDevice = vulkanDevice; - this->queue = queue; - 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__) - // Scale text on Android devices with high DPI - if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) { - LOGD("XXHIGH"); - scale = 2.0f; - } - else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) { - LOGD("XHIGH"); - scale = 1.5f; - } - else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) { - LOGD("HIGH"); - scale = 1.25f; - }; -#endif - - cmdBuffers.resize(framebuffers.size()); - prepareResources(); - prepareRenderPass(); - preparePipeline(); - } - - /** - * Default destructor, frees up all Vulkan resources acquired by the text overlay - */ - ~VulkanTextOverlay() - { - // Free up all Vulkan resources requested by the text overlay - vertexBuffer.destroy(); - vkDestroySampler(vulkanDevice->logicalDevice, sampler, nullptr); - vkDestroyImage(vulkanDevice->logicalDevice, image, nullptr); - vkDestroyImageView(vulkanDevice->logicalDevice, view, nullptr); - vkFreeMemory(vulkanDevice->logicalDevice, imageMemory, nullptr); - vkDestroyDescriptorSetLayout(vulkanDevice->logicalDevice, descriptorSetLayout, nullptr); - vkDestroyDescriptorPool(vulkanDevice->logicalDevice, descriptorPool, nullptr); - vkDestroyPipelineLayout(vulkanDevice->logicalDevice, pipelineLayout, nullptr); - vkDestroyPipelineCache(vulkanDevice->logicalDevice, pipelineCache, nullptr); - vkDestroyPipeline(vulkanDevice->logicalDevice, pipeline, nullptr); - vkDestroyRenderPass(vulkanDevice->logicalDevice, renderPass, nullptr); - vkFreeCommandBuffers(vulkanDevice->logicalDevice, commandPool, static_cast(cmdBuffers.size()), cmdBuffers.data()); - vkDestroyCommandPool(vulkanDevice->logicalDevice, commandPool, nullptr); - vkDestroyFence(vulkanDevice->logicalDevice, fence, nullptr); - } - - /** - * Prepare all vulkan resources required to render the font - * The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers - */ - void prepareResources() - { - static unsigned char font24pixels[STB_FONT_HEIGHT][STB_FONT_WIDTH]; - STB_FONT_NAME(stbFontData, font24pixels, STB_FONT_HEIGHT); - - // Command buffer - - // Pool - VkCommandPoolCreateInfo cmdPoolInfo = {}; - cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; - cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - VK_CHECK_RESULT(vkCreateCommandPool(vulkanDevice->logicalDevice, &cmdPoolInfo, nullptr, &commandPool)); - - VkCommandBufferAllocateInfo cmdBufAllocateInfo = - vks::initializers::commandBufferAllocateInfo( - commandPool, - VK_COMMAND_BUFFER_LEVEL_PRIMARY, - (uint32_t)cmdBuffers.size()); - - VK_CHECK_RESULT(vkAllocateCommandBuffers(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data())); - - // Vertex buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &vertexBuffer, - MAX_CHAR_COUNT * sizeof(glm::vec4))); - - // Map persistent - vertexBuffer.map(); - - // Font texture - VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo(); - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = VK_FORMAT_R8_UNORM; - imageInfo.extent.width = STB_FONT_WIDTH; - imageInfo.extent.height = STB_FONT_HEIGHT; - 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_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; - VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &imageInfo, nullptr, &image)); - - VkMemoryRequirements memReqs; - VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo(); - vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, image, &memReqs); - allocInfo.allocationSize = memReqs.size; - allocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &allocInfo, nullptr, &imageMemory)); - VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, image, imageMemory, 0)); - - // Staging - vks::Buffer stagingBuffer; - - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &stagingBuffer, - allocInfo.allocationSize)); - - stagingBuffer.map(); - memcpy(stagingBuffer.mapped, &font24pixels[0][0], STB_FONT_WIDTH * STB_FONT_HEIGHT); // Only one channel, so data size = W * H (*R8) - stagingBuffer.unmap(); - - // Copy to image - VkCommandBuffer copyCmd; - cmdBufAllocateInfo.commandBufferCount = 1; - VK_CHECK_RESULT(vkAllocateCommandBuffers(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, ©Cmd)); - - VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); - VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo)); - - // Prepare for transfer - vks::tools::setImageLayout( - copyCmd, - image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_PREINITIALIZED, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - VkBufferImageCopy bufferCopyRegion = {}; - bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - bufferCopyRegion.imageSubresource.mipLevel = 0; - bufferCopyRegion.imageSubresource.layerCount = 1; - bufferCopyRegion.imageExtent.width = STB_FONT_WIDTH; - bufferCopyRegion.imageExtent.height = STB_FONT_HEIGHT; - bufferCopyRegion.imageExtent.depth = 1; - - vkCmdCopyBufferToImage( - copyCmd, - stagingBuffer.buffer, - image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, - &bufferCopyRegion - ); - - // Prepare for shader read - vks::tools::setImageLayout( - copyCmd, - image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd)); - - VkSubmitInfo submitInfo = vks::initializers::submitInfo(); - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = ©Cmd; - - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - VK_CHECK_RESULT(vkQueueWaitIdle(queue)); - - stagingBuffer.destroy(); - - vkFreeCommandBuffers(vulkanDevice->logicalDevice, commandPool, 1, ©Cmd); - - VkImageViewCreateInfo imageViewInfo = vks::initializers::imageViewCreateInfo(); - imageViewInfo.image = image; - imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - imageViewInfo.format = imageInfo.format; - imageViewInfo.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; - imageViewInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &imageViewInfo, nullptr, &view)); - - // 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_REPEAT; - samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; - samplerInfo.mipLodBias = 0.0f; - samplerInfo.compareOp = VK_COMPARE_OP_NEVER; - samplerInfo.minLod = 0.0f; - samplerInfo.maxLod = 1.0f; - samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - samplerInfo.maxAnisotropy = 1.0f; - VK_CHECK_RESULT(vkCreateSampler(vulkanDevice->logicalDevice, &samplerInfo, nullptr, &sampler)); - - // Descriptor - // Font uses a separate descriptor pool - std::array poolSizes; - poolSizes[0] = vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1); - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - static_cast(poolSizes.size()), - poolSizes.data(), - 1); - - VK_CHECK_RESULT(vkCreateDescriptorPool(vulkanDevice->logicalDevice, &descriptorPoolInfo, nullptr, &descriptorPool)); - - // Descriptor set layout - std::array setLayoutBindings; - setLayoutBindings[0] = vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0); - - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = - vks::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - static_cast(setLayoutBindings.size())); - - VK_CHECK_RESULT(vkCreateDescriptorSetLayout(vulkanDevice->logicalDevice, &descriptorSetLayoutInfo, nullptr, &descriptorSetLayout)); - - // Pipeline layout - VkPipelineLayoutCreateInfo pipelineLayoutInfo = - vks::initializers::pipelineLayoutCreateInfo( - &descriptorSetLayout, - 1); - - VK_CHECK_RESULT(vkCreatePipelineLayout(vulkanDevice->logicalDevice, &pipelineLayoutInfo, nullptr, &pipelineLayout)); - - // Descriptor set - VkDescriptorSetAllocateInfo descriptorSetAllocInfo = - vks::initializers::descriptorSetAllocateInfo( - descriptorPool, - &descriptorSetLayout, - 1); - - VK_CHECK_RESULT(vkAllocateDescriptorSets(vulkanDevice->logicalDevice, &descriptorSetAllocInfo, &descriptorSet)); - - VkDescriptorImageInfo texDescriptor = - vks::initializers::descriptorImageInfo( - sampler, - view, - VK_IMAGE_LAYOUT_GENERAL); - - std::array writeDescriptorSets; - writeDescriptorSets[0] = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &texDescriptor); - vkUpdateDescriptorSets(vulkanDevice->logicalDevice, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); - - // Pipeline cache - VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; - pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; - VK_CHECK_RESULT(vkCreatePipelineCache(vulkanDevice->logicalDevice, &pipelineCacheCreateInfo, nullptr, &pipelineCache)); - - // Command buffer execution fence - VkFenceCreateInfo fenceCreateInfo = vks::initializers::fenceCreateInfo(); - VK_CHECK_RESULT(vkCreateFence(vulkanDevice->logicalDevice, &fenceCreateInfo, nullptr, &fence)); - } - - /** - * Prepare a separate pipeline for the font rendering decoupled from the main application - */ - void preparePipeline() - { - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = - vks::initializers::pipelineInputAssemblyStateCreateInfo( - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, - 0, - VK_FALSE); - - VkPipelineRasterizationStateCreateInfo rasterizationState = - vks::initializers::pipelineRasterizationStateCreateInfo( - VK_POLYGON_MODE_FILL, - VK_CULL_MODE_BACK_BIT, - VK_FRONT_FACE_CLOCKWISE, - 0); - - // Enable blending - VkPipelineColorBlendAttachmentState blendAttachmentState = - vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_TRUE); - - blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; - blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; - blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; - blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; - blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE; - blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; - blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - - 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, - 0); - - std::vector dynamicStateEnables = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - - VkPipelineDynamicStateCreateInfo dynamicState = - vks::initializers::pipelineDynamicStateCreateInfo( - dynamicStateEnables.data(), - static_cast(dynamicStateEnables.size()), - 0); - - std::array vertexBindings = {}; - vertexBindings[0] = vks::initializers::vertexInputBindingDescription(0, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX); - vertexBindings[1] = vks::initializers::vertexInputBindingDescription(1, sizeof(glm::vec4), VK_VERTEX_INPUT_RATE_VERTEX); - - std::array vertexAttribs = {}; - // Position - vertexAttribs[0] = vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32_SFLOAT, 0); - // UV - vertexAttribs[1] = vks::initializers::vertexInputAttributeDescription(1, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(glm::vec2)); - - VkPipelineVertexInputStateCreateInfo inputState = vks::initializers::pipelineVertexInputStateCreateInfo(); - inputState.vertexBindingDescriptionCount = static_cast(vertexBindings.size()); - inputState.pVertexBindingDescriptions = vertexBindings.data(); - inputState.vertexAttributeDescriptionCount = static_cast(vertexAttribs.size()); - inputState.pVertexAttributeDescriptions = vertexAttribs.data(); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = - vks::initializers::pipelineCreateInfo( - pipelineLayout, - renderPass, - 0); - - pipelineCreateInfo.pVertexInputState = &inputState; - 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(); - - VK_CHECK_RESULT(vkCreateGraphicsPipelines(vulkanDevice->logicalDevice, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); - } - - /** - * Prepare a separate render pass for rendering the text as an overlay - */ - void 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(vulkanDevice->logicalDevice, &renderPassInfo, nullptr, &renderPass)); - } - - /** - * Maps the buffer, resets letter count - */ - void beginTextUpdate() - { - mappedLocal = (glm::vec4*)vertexBuffer.mapped; - numLetters = 0; - } - - /** - * Add text to the current buffer - * - * @param text Text to add - * @param x x position of the text to add in window coordinate space - * @param y y position of the text to add in window coordinate space - * @param align Alignment for the new text (left, right, center) - */ - void addText(std::string text, float x, float y, TextAlign align) - { - assert(vertexBuffer.mapped != nullptr); - - if (align == alignLeft) { - x *= scale; - }; - - y *= scale; - - const float charW = (1.5f * scale) / *frameBufferWidth; - const float charH = (1.5f * scale) / *frameBufferHeight; - - float fbW = (float)*frameBufferWidth; - float fbH = (float)*frameBufferHeight; - x = (x / fbW * 2.0f) - 1.0f; - y = (y / fbH * 2.0f) - 1.0f; - - // Calculate text width - float textWidth = 0; - for (auto letter : text) - { - stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR]; - textWidth += charData->advance * charW; - } - - switch (align) - { - case alignRight: - x -= textWidth; - break; - case alignCenter: - x -= textWidth / 2.0f; - break; - case alignLeft: - break; - } - - // Generate a uv mapped quad per char in the new text - for (auto letter : text) - { - stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR]; - - mappedLocal->x = (x + (float)charData->x0 * charW); - mappedLocal->y = (y + (float)charData->y0 * charH); - mappedLocal->z = charData->s0; - mappedLocal->w = charData->t0; - mappedLocal++; - - mappedLocal->x = (x + (float)charData->x1 * charW); - mappedLocal->y = (y + (float)charData->y0 * charH); - mappedLocal->z = charData->s1; - mappedLocal->w = charData->t0; - mappedLocal++; - - mappedLocal->x = (x + (float)charData->x0 * charW); - mappedLocal->y = (y + (float)charData->y1 * charH); - mappedLocal->z = charData->s0; - mappedLocal->w = charData->t1; - mappedLocal++; - - mappedLocal->x = (x + (float)charData->x1 * charW); - mappedLocal->y = (y + (float)charData->y1 * charH); - mappedLocal->z = charData->s1; - mappedLocal->w = charData->t1; - mappedLocal++; - - x += charData->advance * charW; - - numLetters++; - } - } - - /** - * Unmap buffer and update command buffers - */ - void endTextUpdate() - { - updateCommandBuffers(); - } - - /** - * Update the command buffers to reflect text changes - */ - void 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; - - 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], "Text overlay", glm::vec4(1.0f, 0.94f, 0.3f, 1.0f)); - } - - vkCmdBeginRenderPass(cmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - - VkViewport viewport = vks::initializers::viewport((float)*frameBufferWidth, (float)*frameBufferHeight, 0.0f, 1.0f); - vkCmdSetViewport(cmdBuffers[i], 0, 1, &viewport); - - VkRect2D scissor = vks::initializers::rect2D(*frameBufferWidth, *frameBufferHeight, 0, 0); - vkCmdSetScissor(cmdBuffers[i], 0, 1, &scissor); - - 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 = 0; - vkCmdBindVertexBuffers(cmdBuffers[i], 0, 1, &vertexBuffer.buffer, &offsets); - vkCmdBindVertexBuffers(cmdBuffers[i], 1, 1, &vertexBuffer.buffer, &offsets); - for (uint32_t j = 0; j < numLetters; j++) - { - vkCmdDraw(cmdBuffers[i], 4, 1, j * 4, 0); - } - - vkCmdEndRenderPass(cmdBuffers[i]); - - if (vks::debugmarker::active) - { - vks::debugmarker::endRegion(cmdBuffers[i]); - } - - VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuffers[i])); - } - } - - /** - * Submit the text command buffers to a queue - */ - void 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(vulkanDevice->logicalDevice, 1, &fence, VK_TRUE, UINT64_MAX)); - VK_CHECK_RESULT(vkResetFences(vulkanDevice->logicalDevice, 1, &fence)); - } - - /** - * Reallocate command buffers for the text overlay - * @note Frees the existing command buffers - */ - void reallocateCommandBuffers() - { - vkFreeCommandBuffers(vulkanDevice->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(vulkanDevice->logicalDevice, &cmdBufAllocateInfo, cmdBuffers.data())); - } - -}; \ No newline at end of file diff --git a/base/VulkanUIOverlay.cpp b/base/VulkanUIOverlay.cpp new file mode 100644 index 00000000..07c729b3 --- /dev/null +++ b/base/VulkanUIOverlay.cpp @@ -0,0 +1,555 @@ +/* +* 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__) + // Scale text on Android devices with high DPI + if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XXHIGH) { + scale = 2.0f; + } + else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_XHIGH) { + scale = 1.5f; + } + else if (vks::android::screenDensity >= ACONFIGURATION_DENSITY_HIGH) { + scale = 1.25f; + }; + #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.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + + cmdBuffers.resize(framebuffers.size()); + prepareResources(); + prepareRenderPass(); + preparePipeline(); + } + + /** Free up all Vulkan resources acquired by the UI overlay */ + UIOverlay::~UIOverlay() + { + vertexBuffer.destroy(); + 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())); + } +} \ No newline at end of file diff --git a/base/VulkanUIOverlay.h b/base/VulkanUIOverlay.h new file mode 100644 index 00000000..0049915e --- /dev/null +++ b/base/VulkanUIOverlay.h @@ -0,0 +1,90 @@ +/* +* 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) +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "VulkanTools.h" +#include "VulkanDebug.h" +#include "VulkanBuffer.hpp" +#include "VulkanDevice.hpp" + +#include "../external/imgui/imgui.h" + +#if defined(__ANDROID__) +#include "VulkanAndroid.h" +#endif + +namespace vks +{ + class UIOverlay + { + private: + vks::VulkanDevice *device; + + VkQueue copyQueue; + VkFormat colorFormat; + VkFormat depthFormat; + + uint32_t *frameBufferWidth; + uint32_t *frameBufferHeight; + + vks::Buffer vertexBuffer; + vks::Buffer indexBuffer; + int32_t vertexCount = 0; + int32_t indexCount = 0; + + VkDescriptorPool descriptorPool; + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorSet descriptorSet; + VkPipelineLayout pipelineLayout; + VkPipelineCache pipelineCache; + VkPipeline pipeline; + VkRenderPass renderPass; + VkCommandPool commandPool; + std::vector frameBuffers; + std::vector shaderStages; + VkFence fence; + + VkDeviceMemory fontMemory = VK_NULL_HANDLE; + VkImage fontImage = VK_NULL_HANDLE; + VkImageView fontView = VK_NULL_HANDLE; + VkSampler sampler; + + struct PushConstBlock { + glm::vec2 scale; + glm::vec2 translate; + } pushConstBlock; + + void prepareResources(); + void preparePipeline(); + void prepareRenderPass(); + void updateCommandBuffers(); + public: + bool visible = true; + float scale = 1.0f; + + std::vector cmdBuffers; + + UIOverlay(vks::VulkanDevice *vulkanDevice, VkQueue copyQueue, std::vector &framebuffers, VkFormat colorformat, VkFormat depthformat, uint32_t *framebufferwidth, uint32_t *framebufferheight, std::vector shaderstages); + ~UIOverlay(); + + void reallocateCommandBuffers(); + void update(); + + void submit(VkQueue queue, uint32_t bufferindex, VkSubmitInfo submitInfo); + }; +} \ No newline at end of file diff --git a/base/vulkanexamplebase.cpp b/base/vulkanexamplebase.cpp index 8a386520..387b724a 100644 --- a/base/vulkanexamplebase.cpp +++ b/base/vulkanexamplebase.cpp @@ -70,8 +70,7 @@ std::string VulkanExampleBase::getWindowTitle() std::string device(deviceProperties.deviceName); std::string windowTitle; windowTitle = title + " - " + device; - if (!enableTextOverlay) - { + if (!settings.overlay) { windowTitle += " - " + std::to_string(frameCounter) + " fps"; } return windowTitle; @@ -187,24 +186,14 @@ void VulkanExampleBase::prepare() setupRenderPass(); createPipelineCache(); setupFrameBuffer(); - enableTextOverlay = enableTextOverlay && (!benchmark.active); - if (enableTextOverlay) - { - // Load the text rendering shaders - std::vector shaderStages; - shaderStages.push_back(loadShader(getAssetPath() + "shaders/base/textoverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT)); - shaderStages.push_back(loadShader(getAssetPath() + "shaders/base/textoverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)); - textOverlay = new VulkanTextOverlay( - vulkanDevice, - queue, - frameBuffers, - swapChain.colorFormat, - depthFormat, - &width, - &height, - shaderStages - ); - updateTextOverlay(); + settings.overlay = settings.overlay && (!benchmark.active); + if (settings.overlay) { + std::vector shaderStages = { + loadShader(getAssetPath() + "shaders/base/uioverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), + loadShader(getAssetPath() + "shaders/base/uioverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT), + }; + UIOverlay = new vks::UIOverlay(vulkanDevice, queue, frameBuffers, swapChain.colorFormat, depthFormat, &width, &height, shaderStages); + updateOverlay(); } } @@ -256,16 +245,17 @@ void VulkanExampleBase::renderFrame() if (fpsTimer > 1000.0f) { #if defined(_WIN32) - if (!enableTextOverlay) { + if (!settings.overlay) { std::string windowTitle = getWindowTitle(); SetWindowText(window, windowTitle.c_str()); } #endif lastFPS = static_cast(1.0f / frameTimer); - updateTextOverlay(); fpsTimer = 0.0f; frameCounter = 0; } + // TODO: Cap UI overlay update rates + updateOverlay(); } void VulkanExampleBase::renderLoop() @@ -545,31 +535,41 @@ void VulkanExampleBase::renderLoop() vkDeviceWaitIdle(device); } -void VulkanExampleBase::updateTextOverlay() +void VulkanExampleBase::updateOverlay() { - if (!enableTextOverlay) + if (!settings.overlay) return; - textOverlay->beginTextUpdate(); + ImGuiIO& io = ImGui::GetIO(); - textOverlay->addText(title, 5.0f, 5.0f, VulkanTextOverlay::alignLeft); + io.DisplaySize = ImVec2((float)width, (float)height); + io.DeltaTime = frameTimer; - std::stringstream ss; - ss << std::fixed << std::setprecision(3) << (frameTimer * 1000.0f) << "ms (" << lastFPS << " fps)"; - textOverlay->addText(ss.str(), 5.0f, 25.0f, VulkanTextOverlay::alignLeft); + io.MousePos = ImVec2(mousePos.x, mousePos.y); + io.MouseDown[0] = mouseButtons.left; + io.MouseDown[1] = mouseButtons.right; - std::string deviceName(deviceProperties.deviceName); -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - deviceName += " (" + androidProduct + ")"; -#endif - textOverlay->addText(deviceName, 5.0f, 45.0f, VulkanTextOverlay::alignLeft); + ImGui::NewFrame(); - getOverlayText(textOverlay); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); + ImGui::SetNextWindowPos(ImVec2(10, 10)); + ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiSetCond_FirstUseEver); + ImGui::Begin("Vulkan Example", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); + ImGui::Text(title.c_str()); + ImGui::Text(deviceProperties.deviceName); + ImGui::Text("%.2f ms/frame (%.1d fps)", (frameTimer * 1000.0f), lastFPS); - textOverlay->endTextUpdate(); + OnUpdateUIOverlay(); + + ImGui::End(); + ImGui::PopStyleVar(); + + ImGui::Render(); + + UIOverlay->update(); } -void VulkanExampleBase::getOverlayText(VulkanTextOverlay*) {} +//void VulkanExampleBase::getOverlayText(VulkanTextOverlay*) {} void VulkanExampleBase::prepareFrame() { @@ -586,10 +586,9 @@ void VulkanExampleBase::prepareFrame() void VulkanExampleBase::submitFrame() { - bool submitTextOverlay = enableTextOverlay && textOverlay->visible; + bool submitOverlay = settings.overlay && UIOverlay->visible; - if (submitTextOverlay) - { + if (submitOverlay) { // Wait for color attachment output to finish before rendering the text overlay VkPipelineStageFlags stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; submitInfo.pWaitDstStageMask = &stageFlags; @@ -604,7 +603,7 @@ void VulkanExampleBase::submitFrame() // Submit current text overlay command buffer submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &textOverlay->cmdBuffers[currentBuffer]; + submitInfo.pCommandBuffers = &UIOverlay->cmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); // Reset stage mask @@ -618,7 +617,7 @@ void VulkanExampleBase::submitFrame() submitInfo.pSignalSemaphores = &semaphores.renderComplete; } - VK_CHECK_RESULT(swapChain.queuePresent(queue, currentBuffer, submitTextOverlay ? semaphores.textOverlayComplete : semaphores.renderComplete)); + VK_CHECK_RESULT(swapChain.queuePresent(queue, currentBuffer, submitOverlay ? semaphores.textOverlayComplete : semaphores.renderComplete)); VK_CHECK_RESULT(vkQueueWaitIdle(queue)); } @@ -732,9 +731,8 @@ VulkanExampleBase::~VulkanExampleBase() vkDestroySemaphore(device, semaphores.renderComplete, nullptr); vkDestroySemaphore(device, semaphores.textOverlayComplete, nullptr); - if (enableTextOverlay) - { - delete textOverlay; + if (UIOverlay) { + delete UIOverlay; } delete vulkanDevice; @@ -1074,9 +1072,8 @@ void VulkanExampleBase::handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPAR paused = !paused; break; case KEY_F1: - if (enableTextOverlay) - { - textOverlay->visible = !textOverlay->visible; + if (settings.overlay) { + UIOverlay->visible = !UIOverlay->visible; } break; case KEY_ESCAPE: @@ -2114,10 +2111,10 @@ void VulkanExampleBase::windowResize() vkDeviceWaitIdle(device); - if (enableTextOverlay) - { - textOverlay->reallocateCommandBuffers(); - updateTextOverlay(); + if (settings.overlay) { + // TODO: Check if still required + UIOverlay->reallocateCommandBuffers(); + updateOverlay(); } camera.updateAspectRatio((float)width / (float)height); @@ -2155,3 +2152,5 @@ void VulkanExampleBase::setupSwapChain() { swapChain.create(&width, &height, settings.vsync); } + +void VulkanExampleBase::OnUpdateUIOverlay() {} \ No newline at end of file diff --git a/base/vulkanexamplebase.h b/base/vulkanexamplebase.h index 09950dd0..4d4033f8 100644 --- a/base/vulkanexamplebase.h +++ b/base/vulkanexamplebase.h @@ -44,11 +44,11 @@ #include "keycodes.hpp" #include "VulkanTools.h" #include "VulkanDebug.h" +#include "VulkanUIOverlay.h" #include "VulkanInitializers.hpp" #include "VulkanDevice.hpp" #include "VulkanSwapChain.hpp" -#include "VulkanTextOverlay.hpp" #include "camera.hpp" #include "benchmark.hpp" @@ -149,6 +149,8 @@ public: bool fullscreen = false; /** @brief Set to true if v-sync will be forced for the swapchain */ bool vsync = false; + /** @brief Enable UI overlay */ + bool overlay = false; } settings; VkClearColorValue defaultClearColor = { { 0.025f, 0.025f, 0.025f, 1.0f } }; @@ -165,8 +167,7 @@ public: bool paused = false; - bool enableTextOverlay = false; - VulkanTextOverlay *textOverlay; + vks::UIOverlay *UIOverlay = nullptr; // Use to adjust mouse rotation speed float rotationSpeed = 1.0f; @@ -381,10 +382,7 @@ public: // Render one frame of a render loop on platforms that sync rendering void renderFrame(); - void updateTextOverlay(); - - /** @brief (Virtual) Called when the text overlay is updating, can be used to add custom text to the overlay */ - virtual void getOverlayText(VulkanTextOverlay*); + void updateOverlay(); // Prepare the frame for workload submission // - Acquires the next image from the swap chain @@ -395,6 +393,8 @@ public: // - Submits the text overlay (if enabled) void submitFrame(); + /** @brief (Virtual) Called when the UI overlay is updating, can be used to add custom elements to the overlay */ + virtual void OnUpdateUIOverlay(); }; // OS specific macros for the example main entry points diff --git a/data/shaders/base/uioverlay.frag b/data/shaders/base/uioverlay.frag new file mode 100644 index 00000000..e0d79957 --- /dev/null +++ b/data/shaders/base/uioverlay.frag @@ -0,0 +1,13 @@ +#version 450 + +layout (binding = 0) uniform sampler2D fontSampler; + +layout (location = 0) in vec2 inUV; +layout (location = 1) in vec4 inColor; + +layout (location = 0) out vec4 outColor; + +void main() +{ + outColor = inColor * texture(fontSampler, inUV); +} \ No newline at end of file diff --git a/data/shaders/base/uioverlay.frag.spv b/data/shaders/base/uioverlay.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..d992a443549e67aa93df7c1bc13de840b590c807 GIT binary patch literal 664 zcmYk2%}PR16orqPW?BAZX%U3hTpCmaQIiHvoJlnnDiHMEFnfevtfy)c^nK^v3vSu& zv({N>?Y(cMe6bxuiC;Y&hb%gw3=W|YYO#(d)5+*_{W`k3zEsf+l_*rx;@uY2T+Zf+ z7dvEwJS5v_y`u@I3eF+?@URNI`KAivyYy@Qm@d*4$EeX~o@A7oHuybF*3;SYWAV0% z8vD$Xr)NqSAanOw=VB_V{>&=wq;$>pT)b$`#kShR-_}&!!`Wm zr1`R6S4~?~t$a?Uyr=nC%%*tt<=jU*FREr%wDR8P2L9WB8VCKX+vGfAXBDd!~49H7khs>^)BMo_q(uu8E>z=dSdsI^j3-OO?k0GTuHpwZwjxavR`H{0Osmx5rz=9_ziUJMFX=;+~vCQ~x^NS>2!YV&A=-`I~)g zebV0{R!{8QcQH@G_agf7`Wf%f9lIBgcm4x2#J_}9^8)5BUy=2hVsp-a33D&z+@Co0 zT_tv|H>i0>zZviC?5XBf&;O#Pn>Pge)kI8!y m_kdW=-As6*Gd#qsHRqn(jnDR*c!c>by|Xilt^bR{bL=0}MoG2+ literal 0 HcmV?d00001